@alexkroman1/aai 0.9.3 → 0.10.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.
Files changed (57) hide show
  1. package/dist/_internal-types.d.ts +49 -22
  2. package/dist/_internal-types.js +43 -1
  3. package/dist/_mock-ws.d.ts +1 -2
  4. package/dist/_run-code.d.ts +31 -0
  5. package/dist/_session-ctx.d.ts +73 -0
  6. package/dist/_session-otel.d.ts +43 -0
  7. package/dist/_session-persist.d.ts +30 -0
  8. package/dist/_ssrf.d.ts +30 -0
  9. package/dist/_ssrf.js +123 -0
  10. package/dist/_utils.d.ts +25 -0
  11. package/dist/_utils.js +54 -1
  12. package/dist/builtin-tools.d.ts +5 -34
  13. package/dist/direct-executor-Ca0wt5H0.js +572 -0
  14. package/dist/direct-executor.d.ts +34 -5
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +2 -2
  17. package/dist/kv.d.ts +30 -38
  18. package/dist/kv.js +19 -86
  19. package/dist/matchers.d.ts +20 -0
  20. package/dist/matchers.js +41 -0
  21. package/dist/memory-tools.d.ts +39 -0
  22. package/dist/middleware-core.d.ts +47 -0
  23. package/dist/middleware-core.js +107 -0
  24. package/dist/middleware.d.ts +37 -0
  25. package/dist/protocol.d.ts +44 -24
  26. package/dist/protocol.js +34 -14
  27. package/dist/runtime.d.ts +26 -2
  28. package/dist/runtime.js +44 -7
  29. package/dist/s2s.d.ts +19 -29
  30. package/dist/s2s.js +117 -87
  31. package/dist/server.d.ts +31 -3
  32. package/dist/server.js +102 -28
  33. package/dist/session-BkN9u0ni.js +683 -0
  34. package/dist/session.d.ts +55 -28
  35. package/dist/session.js +2 -312
  36. package/dist/sqlite-kv.d.ts +34 -0
  37. package/dist/sqlite-kv.js +133 -0
  38. package/dist/sqlite-vector.d.ts +58 -0
  39. package/dist/sqlite-vector.js +149 -0
  40. package/dist/system-prompt.d.ts +21 -0
  41. package/dist/telemetry.d.ts +49 -0
  42. package/dist/telemetry.js +95 -0
  43. package/dist/testing-MRl3SXsI.js +519 -0
  44. package/dist/testing.d.ts +299 -0
  45. package/dist/testing.js +2 -0
  46. package/dist/types.d.ts +324 -39
  47. package/dist/types.js +62 -9
  48. package/dist/vector.d.ts +18 -22
  49. package/dist/vector.js +41 -48
  50. package/dist/worker-entry.d.ts +11 -3
  51. package/dist/worker-entry.js +19 -8
  52. package/dist/ws-handler.d.ts +7 -3
  53. package/dist/ws-handler.js +64 -12
  54. package/package.json +55 -8
  55. package/dist/_mock-ws.js +0 -158
  56. package/dist/builtin-tools.js +0 -270
  57. package/dist/direct-executor.js +0 -125
@@ -5,46 +5,73 @@
5
5
  */
6
6
  import type { JSONSchema7 } from "json-schema";
7
7
  import { z } from "zod";
8
- import type { BuiltinTool, ToolChoice, ToolDef } from "./types.ts";
8
+ import { type ToolDef } from "./types.ts";
9
9
  /**
10
- * Serializable agent configuration sent over the wire.
10
+ * Zod schema for serializable agent configuration sent over the wire.
11
11
  *
12
12
  * This is the JSON-safe subset of the agent definition that can be
13
13
  * transmitted between the worker and the host process via structured clone.
14
14
  */
15
- export type AgentConfig = {
15
+ export declare const AgentConfigSchema: z.ZodObject<{
16
+ name: z.ZodString;
17
+ instructions: z.ZodString;
18
+ greeting: z.ZodString;
19
+ sttPrompt: z.ZodOptional<z.ZodString>;
20
+ maxSteps: z.ZodOptional<z.ZodNumber>;
21
+ toolChoice: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
22
+ auto: "auto";
23
+ required: "required";
24
+ none: "none";
25
+ }>, z.ZodObject<{
26
+ type: z.ZodLiteral<"tool">;
27
+ toolName: z.ZodString;
28
+ }, z.core.$strip>]>>;
29
+ builtinTools: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodEnum<{
30
+ web_search: "web_search";
31
+ visit_webpage: "visit_webpage";
32
+ fetch_json: "fetch_json";
33
+ run_code: "run_code";
34
+ vector_search: "vector_search";
35
+ memory: "memory";
36
+ }>>>>;
37
+ idleTimeoutMs: z.ZodOptional<z.ZodNumber>;
38
+ }, z.core.$strip>;
39
+ /** Serializable agent configuration — derived from {@link AgentConfigSchema}. */
40
+ export type AgentConfig = z.infer<typeof AgentConfigSchema>;
41
+ /**
42
+ * Input shape accepted by {@link toAgentConfig}. Covers both `AgentDef`
43
+ * (where `maxSteps` may be a function) and `IsolateConfig` (where it is
44
+ * always a number).
45
+ */
46
+ export interface AgentConfigSource {
16
47
  name: string;
17
48
  instructions: string;
18
49
  greeting: string;
19
50
  sttPrompt?: string | undefined;
20
- maxSteps?: number | undefined;
21
- toolChoice?: ToolChoice | undefined;
22
- builtinTools?: readonly BuiltinTool[] | undefined;
23
- /** Default set of active tools. Can be overridden per-turn via `onBeforeStep`. */
24
- activeTools?: readonly string[] | undefined;
25
- };
51
+ maxSteps?: number | ((...args: never[]) => number) | undefined;
52
+ toolChoice?: AgentConfig["toolChoice"] | undefined;
53
+ builtinTools?: Readonly<AgentConfig["builtinTools"]> | undefined;
54
+ idleTimeoutMs?: number | undefined;
55
+ }
56
+ /** Extract the serializable {@link AgentConfig} subset from a source object. */
57
+ export declare function toAgentConfig(src: AgentConfigSource): AgentConfig;
26
58
  /**
27
- * Serialized tool schema sent over the wire.
59
+ * Zod schema for serialized tool definitions sent over the wire.
60
+ *
28
61
  * `parameters` must be a valid JSON Schema object (with `type`, `properties`,
29
62
  * etc.) — the Vercel AI SDK wraps it via `jsonSchema()`.
30
63
  */
64
+ export declare const ToolSchemaSchema: z.ZodObject<{
65
+ name: z.ZodString;
66
+ description: z.ZodString;
67
+ parameters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
68
+ }, z.core.$strip>;
69
+ /** Serialized tool schema — derived from {@link ToolSchemaSchema}. */
31
70
  export type ToolSchema = {
32
71
  name: string;
33
72
  description: string;
34
73
  parameters: JSONSchema7;
35
74
  };
36
- /**
37
- * Request body for the deploy endpoint.
38
- *
39
- * Sent by the CLI to the server when deploying a bundled agent.
40
- */
41
- export type DeployBody = {
42
- /** Env vars are optional at deploy time — set separately via `aai env add`. */
43
- env?: Readonly<Record<string, string>> | undefined;
44
- worker: string;
45
- /** Client build files keyed by relative path (e.g. "index.html", "assets/index-abc.js"). */
46
- clientFiles: Readonly<Record<string, string>>;
47
- };
48
75
  /** Empty Zod object schema used as default when tools have no parameters. */
49
76
  export declare const EMPTY_PARAMS: z.ZodObject<{}, z.core.$strip>;
50
77
  /**
@@ -1,5 +1,47 @@
1
+ import { BuiltinToolSchema, ToolChoiceSchema } from "./types.js";
1
2
  import { z } from "zod";
2
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
+ });
3
45
  /** Empty Zod object schema used as default when tools have no parameters. */
4
46
  const EMPTY_PARAMS = z.object({});
5
47
  /**
@@ -16,4 +58,4 @@ function agentToolsToSchemas(tools) {
16
58
  }));
17
59
  }
18
60
  //#endregion
19
- export { EMPTY_PARAMS, agentToolsToSchemas };
61
+ export { AgentConfigSchema, EMPTY_PARAMS, ToolSchemaSchema, agentToolsToSchemas, toAgentConfig };
@@ -34,11 +34,10 @@ export declare class MockWebSocket extends EventTarget {
34
34
  * @param _protocols - Ignored; accepted for API compatibility.
35
35
  */
36
36
  constructor(url: string | URL, _protocols?: string | string[] | Record<string, unknown>);
37
- addEventListener(type: "open", listener: () => void): void;
37
+ addEventListener(type: "close" | "open", listener: () => void): void;
38
38
  addEventListener(type: "message", listener: (event: {
39
39
  data: unknown;
40
40
  }) => void): void;
41
- addEventListener(type: "close", listener: () => void): void;
42
41
  addEventListener(type: "close", listener: (event: {
43
42
  code?: number;
44
43
  reason?: string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * run_code built-in tool — executes user JavaScript in a fresh secure-exec
3
+ * V8 isolate with no network, filesystem writes, or env access.
4
+ */
5
+ import { z } from "zod";
6
+ import type { ToolDef } from "./types.ts";
7
+ declare const runCodeParams: z.ZodObject<{
8
+ code: z.ZodString;
9
+ }, z.core.$strip>;
10
+ /**
11
+ * Execute JavaScript code inside a fresh secure-exec V8 isolate.
12
+ *
13
+ * Each invocation spins up a disposable isolate with:
14
+ * - No filesystem writes
15
+ * - No network access
16
+ * - No child process spawning
17
+ * - No environment variable access
18
+ * - 32 MB memory limit
19
+ * - 5 second execution timeout
20
+ *
21
+ * The isolate is disposed immediately after execution, so no state
22
+ * leaks between invocations or across sessions.
23
+ */
24
+ export declare function createRunCode(): ToolDef<typeof runCodeParams>;
25
+ /**
26
+ * Exported for testing — execute user code in a fresh secure-exec V8 isolate.
27
+ */
28
+ export declare function executeInIsolate(code: string): Promise<string | {
29
+ error: string;
30
+ }>;
31
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Session context builder.
3
+ *
4
+ * Extracted from session.ts to keep it under the file-length lint limit.
5
+ * Builds the shared mutable state object threaded through all session helpers.
6
+ */
7
+ import type { AgentConfig } from "./_internal-types.ts";
8
+ import type { HookInvoker } from "./middleware.ts";
9
+ import type { ClientSink } from "./protocol.ts";
10
+ import type { Logger } from "./runtime.ts";
11
+ import type { S2sHandle } from "./s2s.ts";
12
+ import type { Message } from "./types.ts";
13
+ import type { ExecuteTool } from "./worker-entry.ts";
14
+ type PendingTool = {
15
+ callId: string;
16
+ result: string;
17
+ };
18
+ /**
19
+ * Mutable state and dependencies shared across session helper functions.
20
+ *
21
+ * Created once per session by `buildCtx` and threaded through `setupListeners`,
22
+ * `handleToolCall`, and other internal helpers. Contains both immutable
23
+ * dependencies (logger, executor) and mutable per-turn state (pending tools,
24
+ * generation counters).
25
+ */
26
+ export type S2sSessionCtx = {
27
+ readonly id: string;
28
+ readonly agent: string;
29
+ readonly client: ClientSink;
30
+ readonly agentConfig: AgentConfig;
31
+ readonly executeTool: ExecuteTool;
32
+ readonly hookInvoker: HookInvoker | undefined;
33
+ readonly log: Logger;
34
+ s2s: S2sHandle | null;
35
+ pendingTools: PendingTool[];
36
+ toolCallCount: number;
37
+ turnPromise: Promise<void> | null;
38
+ conversationMessages: Message[];
39
+ /** Maximum number of messages to retain in conversationMessages. */
40
+ readonly maxHistory: number;
41
+ /** The `reply_id` from the most recent `reply.started` event. Tool calls
42
+ * capture this at start; finishToolCall only pushes to pendingTools if the
43
+ * reply ID still matches, preventing stale results from interrupted replies
44
+ * from leaking into subsequent replies. Set to `null` on close/reset. */
45
+ currentReplyId: string | null;
46
+ /** Resolve per-turn configuration (dynamic `maxSteps`). */
47
+ resolveTurnConfig(): Promise<{
48
+ maxSteps?: number;
49
+ } | null>;
50
+ /** Increment the tool call counter and check whether the call should be refused. */
51
+ consumeToolCallStep(turnConfig: {
52
+ maxSteps?: number;
53
+ } | null, name: string, replyId: string | null): string | null;
54
+ /** Fire a lifecycle hook asynchronously. Errors are logged but never propagated. */
55
+ fireHook(name: string, fn: (h: HookInvoker) => Promise<void>): void;
56
+ /** Await all in-flight hook promises. Used during shutdown. */
57
+ drainHooks(): Promise<void>;
58
+ /** Push one or more messages and trim to maxHistory. */
59
+ pushMessages(...msgs: Message[]): void;
60
+ /** Sequential promise chain for filterOutput calls, ensuring ordering. */
61
+ filterChain: Promise<void>;
62
+ };
63
+ export declare function buildCtx(opts: {
64
+ id: string;
65
+ agent: string;
66
+ client: ClientSink;
67
+ agentConfig: AgentConfig;
68
+ executeTool: ExecuteTool;
69
+ hookInvoker: HookInvoker | undefined;
70
+ log: Logger;
71
+ maxHistory?: number | undefined;
72
+ }): S2sSessionCtx;
73
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * OpenTelemetry-instrumented session helpers.
3
+ *
4
+ * Extracted from session.ts to keep it under the file-length lint limit.
5
+ * These functions add trace spans and metric counters to the S2S session
6
+ * pipeline: tool calls, user turns, barge-ins, and session lifecycle.
7
+ */
8
+ import type { S2sSessionCtx } from "./_session-ctx.ts";
9
+ import type { S2sHandle, S2sToolCall } from "./s2s.ts";
10
+ export { activeSessionsUpDown, sessionCounter } from "./telemetry.ts";
11
+ /**
12
+ * Orchestrate the full tool call pipeline for a single S2S tool invocation.
13
+ *
14
+ * Steps: resolve per-turn config → check step/tool limits → run middleware
15
+ * `interceptToolCall` (which may block, return a cached result, or modify args)
16
+ * → execute the tool → run `afterToolCall` middleware → record metrics and
17
+ * finish via {@link finishToolCall}. Each step is wrapped in an OpenTelemetry
18
+ * span (`tool.call`) with agent/session/tool attributes.
19
+ *
20
+ * @param ctx - The shared mutable session context (see {@link S2sSessionCtx}).
21
+ * @param detail - The tool call details from the S2S API (call ID, name, parsed args).
22
+ */
23
+ export declare function handleToolCall(ctx: S2sSessionCtx, detail: S2sToolCall): Promise<void>;
24
+ /** Options for customizing S2S event listener behavior. */
25
+ export type SetupListenersOptions = {
26
+ /** Custom handler for session expiration. When provided, replaces the
27
+ * default behavior of closing the handle. Used by resume logic to
28
+ * fall back to a fresh session when S2S resume fails. */
29
+ onSessionExpired?: () => void;
30
+ };
31
+ /**
32
+ * Wire all S2S events to the client sink, hooks, and session state.
33
+ *
34
+ * Registers listeners on the S2S handle for: ready, session expiry, speech
35
+ * start/stop, user/agent transcripts, reply lifecycle, tool calls, audio
36
+ * chunks, errors, and close. Each listener delegates to a focused handler
37
+ * function that updates `ctx` and emits client events.
38
+ *
39
+ * @param ctx - The shared mutable session context.
40
+ * @param handle - The S2S WebSocket handle to listen on.
41
+ * @param opts - Optional overrides for listener behavior.
42
+ */
43
+ export declare function setupListeners(ctx: S2sSessionCtx, handle: S2sHandle, opts?: SetupListenersOptions): void;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Session persistence helpers.
3
+ *
4
+ * Saves and restores session state, conversation messages, and S2S session ID
5
+ * to/from the KV store for cross-reconnect session recovery.
6
+ */
7
+ import type { Kv } from "./kv.ts";
8
+ import type { Logger } from "./runtime.ts";
9
+ import type { Message } from "./types.ts";
10
+ export declare function persistKey(sessionId: string): string;
11
+ export type PersistedSession = {
12
+ s2sSessionId: string | null;
13
+ messages: Message[];
14
+ state: Record<string, unknown>;
15
+ };
16
+ export type SessionPersistence = {
17
+ kv: Kv;
18
+ ttl: number;
19
+ getState: () => Record<string, unknown>;
20
+ setState: (state: Record<string, unknown>) => void;
21
+ };
22
+ type PersistCtx = {
23
+ pushMessages(...msgs: Message[]): void;
24
+ conversationMessages: Message[];
25
+ };
26
+ export declare function restorePersistedSession(persistence: SessionPersistence, resumeFrom: string, ctx: PersistCtx, log: Logger): Promise<string | null>;
27
+ export declare function saveSessionData(persistence: SessionPersistence, sessionId: string, ctx: PersistCtx, s2sSessionId: string | null, log: Logger,
28
+ /** Old session key to clean up (from a previous session we resumed from). */
29
+ cleanupKey?: string): Promise<void>;
30
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Check whether an IP address falls within a private or reserved range.
3
+ *
4
+ * @param ip - An IPv4 or IPv6 address string.
5
+ * @returns `true` if the address is in a private/reserved range.
6
+ */
7
+ export declare function isPrivateIp(ip: string): boolean;
8
+ /**
9
+ * SSRF guard: assert that a URL targets a public internet address.
10
+ *
11
+ * Blocks requests to:
12
+ * - Private IPv4 ranges (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
13
+ * - Loopback (127.0.0.0/8), link-local (169.254.0.0/16), shared (100.64.0.0/10)
14
+ * - IPv4-mapped IPv6 addresses embedding private IPs (e.g. `::ffff:127.0.0.1`)
15
+ * - IPv6 loopback (`::1`), ULA (`fc00::/7`), link-local (`fe80::/10`)
16
+ * - `localhost`, `.local` (mDNS), `.internal` domains
17
+ * - Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`)
18
+ * - Non-http(s) schemes (e.g. `file:`, `ftp:`)
19
+ * - Hostnames that resolve to private IPs via DNS (prevents DNS rebinding)
20
+ *
21
+ * @param url - The URL to validate (must be parseable by `new URL()`).
22
+ * @throws {Error} If the URL targets a private/reserved address or uses a
23
+ * disallowed protocol scheme.
24
+ */
25
+ export declare function assertPublicUrl(url: string): Promise<void>;
26
+ /**
27
+ * Fetch with SSRF-safe redirect handling: validates each redirect URL
28
+ * against private/reserved IP ranges before following.
29
+ */
30
+ export declare function ssrfSafeFetch(url: string, init: RequestInit, fetchFn: typeof globalThis.fetch): Promise<Response>;
package/dist/_ssrf.js ADDED
@@ -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 { assertPublicUrl, isPrivateIp, ssrfSafeFetch };
package/dist/_utils.d.ts CHANGED
@@ -1,3 +1,28 @@
1
1
  /** Shared utility functions. */
2
2
  /** Extract an error message from an unknown thrown value. */
3
3
  export declare function errorMessage(err: unknown): string;
4
+ /** Extract a detailed error string (message + stack) for diagnostic logging. */
5
+ export declare function errorDetail(err: unknown): string;
6
+ /** Filter out undefined values from an env record. */
7
+ export declare function filterEnv(env: Record<string, string | undefined>): Record<string, string>;
8
+ /** Check whether a filesystem operation is a read-only operation. */
9
+ export declare function isReadOnlyFsOp(op: string): boolean;
10
+ /**
11
+ * Safely extract the port from `server.address()`, guarding against the
12
+ * string (pipe/socket) and null return types.
13
+ */
14
+ export declare function getServerPort(addr: unknown): number;
15
+ /**
16
+ * Lazily initialized per-session state manager.
17
+ *
18
+ * On first access for a given session, calls `initState()` (if provided) to
19
+ * create the initial state. Returns `{}` if no initializer and no prior state.
20
+ */
21
+ export declare function createSessionStateMap(initState?: () => Record<string, unknown>): {
22
+ get(sessionId: string): Record<string, unknown>;
23
+ /** Explicitly set the state for a session (used by persistence restore). */
24
+ set(sessionId: string, state: Record<string, unknown>): void;
25
+ delete(sessionId: string): boolean;
26
+ };
27
+ /** Return a JSON error string for the LLM: `'{"error":"<message>"}'`. */
28
+ export declare function toolError(message: string): string;
package/dist/_utils.js CHANGED
@@ -4,5 +4,58 @@
4
4
  function errorMessage(err) {
5
5
  return err instanceof Error ? err.message : String(err);
6
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
+ }
7
60
  //#endregion
8
- export { errorMessage };
61
+ export { createSessionStateMap, errorDetail, errorMessage, filterEnv, getServerPort, isReadOnlyFsOp, toolError };
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Built-in tool definitions for the AAI agent SDK.
3
3
  *
4
- * These tools run inside the sandboxed worker alongside custom tools.
4
+ * In self-hosted mode, these run in-process alongside custom tools.
5
+ * In platform mode, they run on the host process outside the sandbox.
5
6
  * Network requests go through the host's fetch proxy (with SSRF protection).
6
7
  */
7
8
  import { z } from "zod";
8
9
  import { type ToolSchema } from "./_internal-types.ts";
9
- import { type ToolDef } from "./types.ts";
10
+ import type { ToolDef } from "./types.ts";
11
+ export { executeInIsolate } from "./_run-code.ts";
12
+ export { memoryTools } from "./memory-tools.ts";
10
13
  /** Callback for proxying vector search through the host RPC. */
11
14
  export type VectorSearchFn = (query: string, topK: number) => Promise<string>;
12
15
  /** Options for creating built-in tool definitions. */
@@ -24,35 +27,3 @@ type ToolDefRecord = Record<string, ToolDef<z.ZodObject<z.ZodRawShape>>>;
24
27
  export declare function getBuiltinToolDefs(names: readonly string[], opts?: BuiltinToolOptions): ToolDefRecord;
25
28
  /** Returns JSON tool schemas for the specified builtin tools. */
26
29
  export declare function getBuiltinToolSchemas(names: readonly string[]): ToolSchema[];
27
- /**
28
- * Returns a standard set of KV-backed memory tools: `save_memory`,
29
- * `recall_memory`, `list_memories`, and `forget_memory`.
30
- *
31
- * Spread the result into your agent's `tools` record.
32
- *
33
- * @example
34
- * ```ts
35
- * import { defineAgent, memoryTools } from "aai";
36
- *
37
- * export default defineAgent({
38
- * name: "My Agent",
39
- * tools: { ...memoryTools() },
40
- * });
41
- * ```
42
- */
43
- export declare function memoryTools(): {
44
- save_memory: ToolDef<z.ZodObject<{
45
- key: z.ZodString;
46
- value: z.ZodString;
47
- }, z.core.$strip>, Record<string, unknown>>;
48
- recall_memory: ToolDef<z.ZodObject<{
49
- key: z.ZodString;
50
- }, z.core.$strip>, Record<string, unknown>>;
51
- list_memories: ToolDef<z.ZodObject<{
52
- prefix: z.ZodOptional<z.ZodString>;
53
- }, z.core.$strip>, Record<string, unknown>>;
54
- forget_memory: ToolDef<z.ZodObject<{
55
- key: z.ZodString;
56
- }, z.core.$strip>, Record<string, unknown>>;
57
- };
58
- export {};