@alexkroman1/aai 0.9.2 → 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
package/dist/vector.js CHANGED
@@ -1,56 +1,49 @@
1
1
  //#region vector.ts
2
2
  /**
3
- * Create an in-memory vector store for testing and local development.
4
- *
5
- * Uses brute-force substring matching instead of real vector similarity.
6
- * Good enough for testing the plumbing but not for production use.
3
+ * Vector store interface.
4
+ */
5
+ /**
6
+ * Maximum allowed length for a vector filter expression.
7
+ * @internal
8
+ */
9
+ const MAX_FILTER_LENGTH = 1e3;
10
+ /**
11
+ * SQL/query keywords that must not appear in filter expressions.
12
+ * Checked case-insensitively as whole words (word-boundary match).
13
+ * @internal
14
+ */
15
+ const DANGEROUS_KEYWORDS = /\b(SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|EXEC|EXECUTE|UNION|INTO|TRUNCATE|GRANT|REVOKE|CALL)\b/i;
16
+ /**
17
+ * Patterns that indicate injection attempts in filter strings.
18
+ * @internal
19
+ */
20
+ const DANGEROUS_PATTERNS = [
21
+ /;/,
22
+ /--/,
23
+ /\/\*/,
24
+ /\*\//,
25
+ /\0/
26
+ ];
27
+ /**
28
+ * Validate a vector filter expression to prevent injection attacks.
7
29
  *
8
- * @returns A {@link VectorStore} instance backed by in-memory storage.
30
+ * Rejects filters containing SQL keywords (SELECT, DROP, etc.),
31
+ * statement terminators, comments, and null bytes. Enforces a
32
+ * maximum length of 1000 characters.
9
33
  *
10
- * @example
11
- * ```ts
12
- * import { createMemoryVectorStore } from "aai";
34
+ * @param filter - The raw filter string from user input.
35
+ * @returns The validated filter string (trimmed).
36
+ * @throws Error if the filter contains dangerous patterns.
13
37
  *
14
- * const vector = createMemoryVectorStore();
15
- * await vector.upsert("doc-1", "The capital of France is Paris.");
16
- * const results = await vector.query("France capital");
17
- * ```
38
+ * @public
18
39
  */
19
- function createMemoryVectorStore() {
20
- const store = /* @__PURE__ */ new Map();
21
- return {
22
- upsert(id, data, metadata) {
23
- store.set(id, {
24
- data,
25
- metadata
26
- });
27
- return Promise.resolve();
28
- },
29
- async query(text, options) {
30
- const topK = options?.topK ?? 10;
31
- const words = text.toLowerCase().split(/\s+/).filter(Boolean);
32
- const results = [];
33
- let i = 0;
34
- for (const [id, entry] of store) {
35
- if (++i % 500 === 0) await new Promise((r) => setTimeout(r, 0));
36
- const data = entry.data.toLowerCase();
37
- const matches = words.filter((w) => data.includes(w)).length;
38
- if (matches > 0) results.push({
39
- id,
40
- score: matches / Math.max(words.length, 1),
41
- data: entry.data,
42
- metadata: entry.metadata
43
- });
44
- }
45
- results.sort((a, b) => b.score - a.score);
46
- return results.slice(0, topK);
47
- },
48
- remove(ids) {
49
- const idArray = Array.isArray(ids) ? ids : [ids];
50
- for (const id of idArray) store.delete(id);
51
- return Promise.resolve();
52
- }
53
- };
40
+ function validateVectorFilter(filter) {
41
+ const trimmed = filter.trim();
42
+ if (trimmed.length === 0) throw new Error("Vector filter must not be empty");
43
+ if (trimmed.length > MAX_FILTER_LENGTH) throw new Error(`Vector filter exceeds maximum length of ${MAX_FILTER_LENGTH} characters`);
44
+ if (DANGEROUS_KEYWORDS.test(trimmed)) throw new Error("Vector filter contains disallowed SQL keyword");
45
+ for (const pattern of DANGEROUS_PATTERNS) if (pattern.test(trimmed)) throw new Error("Vector filter contains disallowed characters");
46
+ return trimmed;
54
47
  }
55
48
  //#endregion
56
- export { createMemoryVectorStore };
49
+ export { validateVectorFilter };
@@ -2,6 +2,7 @@
2
2
  * Worker entry point — shared tool execution logic.
3
3
  */
4
4
  import type { Kv } from "./kv.ts";
5
+ import type { Logger } from "./runtime.ts";
5
6
  import type { Message, ToolDef } from "./types.ts";
6
7
  import type { VectorStore } from "./vector.ts";
7
8
  /**
@@ -13,23 +14,30 @@ import type { VectorStore } from "./vector.ts";
13
14
  * @param messages - Optional conversation history for context-aware tools.
14
15
  * @returns The tool's string result, or an error message string.
15
16
  */
16
- export type ExecuteTool = (name: string, args: Readonly<Record<string, unknown>>, sessionId?: string, messages?: readonly Message[]) => Promise<string>;
17
+ export type ExecuteTool = (name: string, args: Readonly<Record<string, unknown>>, sessionId?: string, messages?: readonly Message[], onUpdate?: (data: unknown) => void) => Promise<string>;
17
18
  /** Options for {@link executeToolCall}. */
18
19
  export type ExecuteToolCallOptions = {
19
20
  tool: ToolDef;
20
21
  env: Readonly<Record<string, string>>;
21
22
  state?: Record<string, unknown>;
23
+ sessionId?: string | undefined;
22
24
  kv?: Kv | undefined;
23
25
  vector?: VectorStore | undefined;
24
26
  messages?: readonly Message[] | undefined;
27
+ logger?: Logger | undefined;
28
+ /** Callback for intermediate UI updates from `ctx.sendUpdate()`. */
29
+ onUpdate?: ((data: unknown) => void) | undefined;
30
+ /** Override fetch implementation for the tool context. */
31
+ fetch?: typeof globalThis.fetch | undefined;
25
32
  };
26
33
  /**
27
34
  * Execute a tool call with argument validation and error handling.
28
35
  *
29
36
  * Validates the provided arguments against the tool's Zod parameter schema,
30
37
  * constructs a {@link ToolContext}, invokes the tool's `execute` function,
31
- * and serializes the result to a string. Errors are caught and returned as
32
- * `"Error: ..."` strings rather than thrown.
38
+ * and serializes the result to a string. Errors (validation failures,
39
+ * execution throws, timeouts) are caught and returned as JSON strings
40
+ * via {@link toolError} (`'{"error":"<message>"}'`) rather than thrown.
33
41
  *
34
42
  * @param name - The name of the tool being invoked.
35
43
  * @param args - Raw arguments from the LLM to validate and pass to the tool.
@@ -1,11 +1,11 @@
1
1
  import { EMPTY_PARAMS } from "./_internal-types.js";
2
- import { errorMessage } from "./_utils.js";
2
+ import { errorDetail, errorMessage, toolError } from "./_utils.js";
3
3
  import { TOOL_EXECUTION_TIMEOUT_MS } from "./protocol.js";
4
4
  //#region worker-entry.ts
5
5
  /** Yield to the event loop so pending I/O (e.g. WebSocket frames) can be processed. */
6
6
  const yieldTick = () => new Promise((r) => setTimeout(r, 0));
7
7
  function buildToolContext(opts) {
8
- const { env, state, kv, vector, messages } = opts;
8
+ const { env, state, kv, vector, messages, onUpdate, fetch: fetchFn, sessionId } = opts;
9
9
  return {
10
10
  env: { ...env },
11
11
  state: state ?? {},
@@ -17,7 +17,12 @@ function buildToolContext(opts) {
17
17
  if (!vector) throw new Error("Vector store not available");
18
18
  return vector;
19
19
  },
20
- messages: messages ?? []
20
+ messages: messages ?? [],
21
+ sendUpdate(data) {
22
+ onUpdate?.(data);
23
+ },
24
+ fetch: fetchFn ?? globalThis.fetch,
25
+ sessionId: sessionId ?? ""
21
26
  };
22
27
  }
23
28
  /**
@@ -25,8 +30,9 @@ function buildToolContext(opts) {
25
30
  *
26
31
  * Validates the provided arguments against the tool's Zod parameter schema,
27
32
  * constructs a {@link ToolContext}, invokes the tool's `execute` function,
28
- * and serializes the result to a string. Errors are caught and returned as
29
- * `"Error: ..."` strings rather than thrown.
33
+ * and serializes the result to a string. Errors (validation failures,
34
+ * execution throws, timeouts) are caught and returned as JSON strings
35
+ * via {@link toolError} (`'{"error":"<message>"}'`) rather than thrown.
30
36
  *
31
37
  * @param name - The name of the tool being invoked.
32
38
  * @param args - Raw arguments from the LLM to validate and pass to the tool.
@@ -36,7 +42,7 @@ function buildToolContext(opts) {
36
42
  async function executeToolCall(name, args, options) {
37
43
  const { tool } = options;
38
44
  const parsed = (tool.parameters ?? EMPTY_PARAMS).safeParse(args);
39
- if (!parsed.success) return `Error: Invalid arguments for tool "${name}": ${(parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".")}: ${i.message}`).join(", ")}`;
45
+ if (!parsed.success) return toolError(`Invalid arguments for tool "${name}": ${(parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".")}: ${i.message}`).join(", ")}`);
40
46
  let timer;
41
47
  try {
42
48
  const ctx = buildToolContext(options);
@@ -49,8 +55,13 @@ async function executeToolCall(name, args, options) {
49
55
  if (result == null) return "null";
50
56
  return typeof result === "string" ? result : JSON.stringify(result);
51
57
  } catch (err) {
52
- console.warn(`[tool-executor] Tool execution failed: ${name}`, err);
53
- return `Error: ${errorMessage(err)}`;
58
+ const log = options.logger;
59
+ if (log) log.warn("Tool execution failed", {
60
+ tool: name,
61
+ error: errorDetail(err)
62
+ });
63
+ else console.warn(`[tool-executor] Tool execution failed: ${name}`, err);
64
+ return toolError(errorMessage(err));
54
65
  } finally {
55
66
  clearTimeout(timer);
56
67
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WebSocket session lifecycle handler.
3
3
  *
4
- * Audio validation is handled at the host transport layer (see host.ts).
4
+ * Audio validation is handled at the host transport layer (see server.ts).
5
5
  */
6
6
  import type { ClientSink, ReadyConfig } from "./protocol.ts";
7
7
  import type { Logger } from "./runtime.ts";
@@ -14,11 +14,10 @@ import type { Session } from "./session.ts";
14
14
  export type SessionWebSocket = {
15
15
  readonly readyState: number;
16
16
  send(data: string | ArrayBuffer | Uint8Array): void;
17
- addEventListener(type: "open", listener: () => void): void;
17
+ addEventListener(type: "close" | "open", listener: () => void): void;
18
18
  addEventListener(type: "message", listener: (event: {
19
19
  data: unknown;
20
20
  }) => void): void;
21
- addEventListener(type: "close", listener: () => void): void;
22
21
  addEventListener(type: "error", listener: (event: {
23
22
  message?: string;
24
23
  }) => void): void;
@@ -39,6 +38,11 @@ export type WsSessionOptions = {
39
38
  onClose?: () => void;
40
39
  /** Logger instance. Defaults to console. */
41
40
  logger?: Logger;
41
+ /** Timeout in ms for session.start(). Defaults to 10 000 (10s). */
42
+ sessionStartTimeoutMs?: number;
43
+ /** Old session ID to resume from. When set, the persisted session data
44
+ * (state, messages, S2S session ID) is restored from KV. */
45
+ resumeFrom?: string;
42
46
  };
43
47
  /**
44
48
  * Attaches session lifecycle handlers to a native WebSocket using
@@ -1,24 +1,31 @@
1
- import { errorMessage } from "./_utils.js";
1
+ import { errorDetail, errorMessage } from "./_utils.js";
2
2
  import { ClientMessageSchema } from "./protocol.js";
3
3
  import { consoleLogger } from "./runtime.js";
4
+ import { tracer, wsSendDroppedCounter } from "./telemetry.js";
4
5
  //#region ws-handler.ts
5
6
  /**
6
7
  * WebSocket session lifecycle handler.
7
8
  *
8
- * Audio validation is handled at the host transport layer (see host.ts).
9
+ * Audio validation is handled at the host transport layer (see server.ts).
9
10
  */
11
+ /** Default timeout for session.start() in milliseconds. */
12
+ const DEFAULT_SESSION_START_TIMEOUT_MS = 1e4;
10
13
  /**
11
14
  * Creates a {@link ClientSink} backed by a plain WebSocket.
12
15
  *
13
16
  * Text events are sent as JSON text frames; audio chunks are sent as
14
17
  * binary frames (zero-copy).
15
18
  */
16
- function createClientSink(ws) {
17
- /** Send data over ws, tolerating races where the socket closes between readyState check and send. */
19
+ function createClientSink(ws, log) {
20
+ /** Send data over ws, silently dropping if the socket is not open. */
18
21
  function safeSend(data) {
19
22
  try {
20
- if (ws.readyState === 1) ws.send(data);
21
- } catch {}
23
+ if (ws.readyState !== 1) return;
24
+ ws.send(data);
25
+ } catch (err) {
26
+ log.debug?.("safeSend: socket closed between readyState check and send", { error: err instanceof Error ? err.message : String(err) });
27
+ wsSendDroppedCounter.add(1);
28
+ }
22
29
  }
23
30
  return {
24
31
  get open() {
@@ -81,6 +88,7 @@ function handleTextMessage(data, session, log, ctx, sid) {
81
88
  case "history":
82
89
  session.onHistory(msg.messages);
83
90
  break;
91
+ default: break;
84
92
  }
85
93
  }
86
94
  /**
@@ -94,40 +102,75 @@ function handleTextMessage(data, session, log, ctx, sid) {
94
102
  */
95
103
  function wireSessionSocket(ws, opts) {
96
104
  const { sessions, logger: log = consoleLogger } = opts;
97
- const sessionId = crypto.randomUUID();
105
+ const sessionId = opts.resumeFrom ?? crypto.randomUUID();
98
106
  const sid = sessionId.slice(0, 8);
99
107
  const ctx = opts.logContext ?? {};
100
108
  let session = null;
109
+ /** Set to true once session.start() resolves. Messages arriving before
110
+ * this flag is set are buffered and replayed once the session is ready,
111
+ * preventing audio/text from being dispatched to a half-initialized session. */
112
+ let sessionReady = false;
113
+ let messageBuffer = [];
114
+ const sessionSpan = tracer.startSpan("ws.session", { attributes: { "aai.session.id": sessionId } });
115
+ function drainBuffer() {
116
+ if (!(session && messageBuffer)) return;
117
+ const buf = messageBuffer;
118
+ messageBuffer = null;
119
+ for (const event of buf) {
120
+ const { data } = event;
121
+ if (handleBinaryAudio(data, session)) continue;
122
+ handleTextMessage(data, session, log, ctx, sid);
123
+ }
124
+ }
101
125
  function onOpen() {
102
126
  opts.onOpen?.();
103
127
  log.info("Session connected", {
104
128
  ...ctx,
105
129
  sid
106
130
  });
107
- const client = createClientSink(ws);
131
+ sessionSpan.addEvent("ws.open");
132
+ const client = createClientSink(ws, log);
108
133
  session = opts.createSession(sessionId, client);
109
134
  sessions.set(sessionId, session);
110
135
  ws.send(JSON.stringify({
111
136
  type: "config",
112
- ...opts.readyConfig
137
+ ...opts.readyConfig,
138
+ sessionId
113
139
  }));
114
- session.start().then(() => {
140
+ const timeoutMs = opts.sessionStartTimeoutMs ?? DEFAULT_SESSION_START_TIMEOUT_MS;
141
+ Promise.race([session.start(), new Promise((_resolve, reject) => {
142
+ setTimeout(() => reject(/* @__PURE__ */ new Error(`session.start() timed out after ${timeoutMs}ms`)), timeoutMs);
143
+ })]).then(() => {
115
144
  log.info("Session ready", {
116
145
  ...ctx,
117
146
  sid
118
147
  });
148
+ sessionSpan.addEvent("session.ready");
149
+ sessionReady = true;
150
+ drainBuffer();
119
151
  }).catch((err) => {
120
152
  log.error("Session start failed", {
121
153
  ...ctx,
122
154
  sid,
123
- error: errorMessage(err)
155
+ error: errorDetail(err)
124
156
  });
157
+ sessionSpan.setStatus({
158
+ code: 2,
159
+ message: errorMessage(err)
160
+ });
161
+ sessions.delete(sessionId);
162
+ session = null;
163
+ messageBuffer = null;
125
164
  });
126
165
  }
127
166
  if (ws.readyState === 1) onOpen();
128
167
  else ws.addEventListener("open", onOpen);
129
168
  ws.addEventListener("message", (event) => {
130
169
  if (!session) return;
170
+ if (!sessionReady) {
171
+ messageBuffer?.push(event);
172
+ return;
173
+ }
131
174
  const { data } = event;
132
175
  if (handleBinaryAudio(data, session)) return;
133
176
  handleTextMessage(data, session, log, ctx, sid);
@@ -137,7 +180,15 @@ function wireSessionSocket(ws, opts) {
137
180
  ...ctx,
138
181
  sid
139
182
  });
140
- if (session) session.stop().finally(() => {
183
+ sessionSpan.addEvent("ws.close");
184
+ sessionSpan.end();
185
+ if (session) session.stop().catch((err) => {
186
+ log.error("Session stop failed", {
187
+ ...ctx,
188
+ sid,
189
+ error: errorDetail(err)
190
+ });
191
+ }).finally(() => {
141
192
  sessions.delete(sessionId);
142
193
  });
143
194
  opts.onClose?.();
@@ -149,6 +200,7 @@ function wireSessionSocket(ws, opts) {
149
200
  sid,
150
201
  error: msg
151
202
  });
203
+ sessionSpan.recordException(new Error(msg));
152
204
  });
153
205
  }
154
206
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -27,9 +27,14 @@
27
27
  "import": "./dist/vector.js"
28
28
  },
29
29
  "./testing": {
30
- "source": "./_mock-ws.ts",
31
- "types": "./dist/_mock-ws.d.ts",
32
- "import": "./dist/_mock-ws.js"
30
+ "source": "./testing.ts",
31
+ "types": "./dist/testing.d.ts",
32
+ "import": "./dist/testing.js"
33
+ },
34
+ "./testing/matchers": {
35
+ "source": "./matchers.ts",
36
+ "types": "./dist/matchers.d.ts",
37
+ "import": "./dist/matchers.js"
33
38
  },
34
39
  "./server": {
35
40
  "source": "./server.ts",
@@ -71,42 +76,84 @@
71
76
  "types": "./dist/ws-handler.d.ts",
72
77
  "import": "./dist/ws-handler.js"
73
78
  },
79
+ "./telemetry": {
80
+ "source": "./telemetry.ts",
81
+ "types": "./dist/telemetry.d.ts",
82
+ "import": "./dist/telemetry.js"
83
+ },
74
84
  "./utils": {
75
85
  "source": "./_utils.ts",
76
86
  "types": "./dist/_utils.d.ts",
77
87
  "import": "./dist/_utils.js"
88
+ },
89
+ "./ssrf": {
90
+ "source": "./_ssrf.ts",
91
+ "types": "./dist/_ssrf.d.ts",
92
+ "import": "./dist/_ssrf.js"
93
+ },
94
+ "./middleware-core": {
95
+ "source": "./middleware-core.ts",
96
+ "types": "./dist/middleware-core.d.ts",
97
+ "import": "./dist/middleware-core.js"
98
+ },
99
+ "./sqlite-kv": {
100
+ "source": "./sqlite-kv.ts",
101
+ "types": "./dist/sqlite-kv.d.ts",
102
+ "import": "./dist/sqlite-kv.js"
103
+ },
104
+ "./sqlite-vector": {
105
+ "source": "./sqlite-vector.ts",
106
+ "types": "./dist/sqlite-vector.d.ts",
107
+ "import": "./dist/sqlite-vector.js"
78
108
  }
79
109
  },
80
110
  "dependencies": {
111
+ "@huggingface/transformers": "^3.8.1",
81
112
  "html-to-text": "^9.0.5",
82
113
  "nanoevents": "^9.1.0",
114
+ "secure-exec": "^0.1.0",
83
115
  "ws": "^8.20.0",
84
116
  "zod": "^4.3.6"
85
117
  },
86
118
  "peerDependencies": {
87
119
  "@hono/node-server": "^1.19.11",
88
- "hono": "^4.12.9"
120
+ "@opentelemetry/api": "^1.9.1",
121
+ "hono": "^4.12.9",
122
+ "vitest": "^4.1.1"
89
123
  },
90
124
  "peerDependenciesMeta": {
91
125
  "@hono/node-server": {
92
126
  "optional": true
93
127
  },
128
+ "@opentelemetry/api": {
129
+ "optional": true
130
+ },
94
131
  "hono": {
95
132
  "optional": true
133
+ },
134
+ "vitest": {
135
+ "optional": true
96
136
  }
97
137
  },
98
138
  "devDependencies": {
99
139
  "@types/html-to-text": "^9.0.4",
100
140
  "@types/json-schema": "^7.0.15",
101
141
  "@types/ws": "^8.18.1",
102
- "tsdown": "^0.21.4"
142
+ "tsdown": "^0.21.5"
103
143
  },
104
144
  "engines": {
105
- "node": ">=22"
145
+ "node": ">=22.6"
146
+ },
147
+ "repository": {
148
+ "type": "git",
149
+ "url": "https://github.com/alexkroman/agent.git",
150
+ "directory": "packages/aai"
106
151
  },
107
152
  "scripts": {
108
153
  "build": "tsdown && tsc -p tsconfig.build.json",
109
154
  "typecheck": "tsc --noEmit",
110
- "lint": "biome check ."
155
+ "lint": "biome check .",
156
+ "check:api": "api-extractor run -c api-extractor.json",
157
+ "check:attw": "attw --pack --profile esm-only"
111
158
  }
112
159
  }
package/dist/_mock-ws.js DELETED
@@ -1,158 +0,0 @@
1
- //#region _mock-ws.ts
2
- /**
3
- * A mock WebSocket implementation for testing.
4
- *
5
- * Extends `EventTarget` to simulate WebSocket behavior without a real
6
- * network connection. Records all sent messages in the {@link sent}
7
- * array and provides helper methods to simulate incoming messages,
8
- * connection events, and errors.
9
- *
10
- * @example
11
- * ```ts
12
- * const ws = new MockWebSocket("wss://example.com");
13
- * ws.send(JSON.stringify({ type: "ping" }));
14
- * ws.simulateMessage(JSON.stringify({ type: "pong" }));
15
- * assertEquals(ws.sentJson(), [{ type: "ping" }]);
16
- * ```
17
- */
18
- var MockWebSocket = class MockWebSocket extends EventTarget {
19
- static CONNECTING = 0;
20
- static OPEN = 1;
21
- static CLOSING = 2;
22
- static CLOSED = 3;
23
- readyState = MockWebSocket.CONNECTING;
24
- binaryType = "arraybuffer";
25
- /** All messages passed to {@link send}, in order. */
26
- sent = [];
27
- url;
28
- /**
29
- * Create a new MockWebSocket.
30
- *
31
- * Automatically transitions to `OPEN` state on the next microtask,
32
- * dispatching an `"open"` event.
33
- *
34
- * @param url - The WebSocket URL.
35
- * @param _protocols - Ignored; accepted for API compatibility.
36
- */
37
- constructor(url, _protocols) {
38
- super();
39
- this.url = typeof url === "string" ? url : url.toString();
40
- queueMicrotask(() => {
41
- if (this.readyState === MockWebSocket.CONNECTING) {
42
- this.readyState = MockWebSocket.OPEN;
43
- this.dispatchEvent(new Event("open"));
44
- }
45
- });
46
- }
47
- addEventListener(type, listener) {
48
- super.addEventListener(type, listener);
49
- }
50
- /**
51
- * Record a sent message without transmitting it.
52
- *
53
- * @param data - The message data to record.
54
- */
55
- send(data) {
56
- this.sent.push(data);
57
- }
58
- /**
59
- * Transition to `CLOSED` state and dispatch a `"close"` event.
60
- *
61
- * @param code - The close code (defaults to 1000).
62
- * @param _reason - Ignored; accepted for API compatibility.
63
- */
64
- close(code, _reason) {
65
- this.readyState = MockWebSocket.CLOSED;
66
- const ev = new Event("close");
67
- ev.code = code ?? 1e3;
68
- this.dispatchEvent(ev);
69
- }
70
- /**
71
- * Simulate receiving a message from the server.
72
- *
73
- * @param data - The message data (string or binary).
74
- */
75
- simulateMessage(data) {
76
- this.dispatchEvent(new MessageEvent("message", { data }));
77
- }
78
- /** Transition to `OPEN` state and dispatch an `"open"` event. */
79
- open() {
80
- this.readyState = MockWebSocket.OPEN;
81
- this.dispatchEvent(new Event("open"));
82
- }
83
- /**
84
- * Shorthand for {@link simulateMessage}.
85
- *
86
- * @param data - The message data to dispatch.
87
- */
88
- msg(data) {
89
- this.simulateMessage(data);
90
- }
91
- /**
92
- * Simulate a connection close from the server.
93
- *
94
- * @param code - The close code (defaults to 1000).
95
- */
96
- disconnect(code = 1e3) {
97
- const ev = new Event("close");
98
- ev.code = code;
99
- this.dispatchEvent(ev);
100
- }
101
- /** Dispatch an `"error"` event on this socket. */
102
- error() {
103
- this.dispatchEvent(new Event("error"));
104
- }
105
- /**
106
- * Return all sent string messages parsed as JSON objects.
107
- *
108
- * Binary messages are filtered out.
109
- *
110
- * @returns An array of parsed JSON objects from sent string messages.
111
- */
112
- sentJson() {
113
- return this.sent.filter((d) => typeof d === "string").map((s) => JSON.parse(s));
114
- }
115
- };
116
- const g = globalThis;
117
- /**
118
- * Replace `globalThis.WebSocket` with {@link MockWebSocket} for testing.
119
- *
120
- * Returns a handle that tracks all created mock sockets and can restore the
121
- * original `WebSocket` constructor. Supports the `using` declaration via
122
- * `Symbol.dispose` for automatic cleanup.
123
- *
124
- * @returns An object with `created` array, `lastWs` getter, `restore()`, and `[Symbol.dispose]()`.
125
- *
126
- * @example
127
- * ```ts
128
- * using mock = installMockWebSocket();
129
- * const session = new Session("wss://example.com");
130
- * const ws = mock.lastWs!;
131
- * ws.simulateMessage(JSON.stringify({ type: "ready" }));
132
- * // mock automatically restores WebSocket when disposed
133
- * ```
134
- */
135
- function installMockWebSocket() {
136
- const saved = globalThis.WebSocket;
137
- const created = [];
138
- g.WebSocket = class extends MockWebSocket {
139
- constructor(url, protocols) {
140
- super(url, protocols);
141
- created.push(this);
142
- }
143
- };
144
- return {
145
- created,
146
- get lastWs() {
147
- return created.at(-1) ?? null;
148
- },
149
- restore() {
150
- globalThis.WebSocket = saved;
151
- },
152
- [Symbol.dispose]() {
153
- this.restore();
154
- }
155
- };
156
- }
157
- //#endregion
158
- export { MockWebSocket, installMockWebSocket };