@alexkroman1/aai 0.9.3 → 0.10.1
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/_internal-types.d.ts +49 -22
- package/dist/_internal-types.js +43 -1
- package/dist/_mock-ws.d.ts +1 -2
- package/dist/_run-code.d.ts +31 -0
- package/dist/_session-ctx.d.ts +73 -0
- package/dist/_session-otel.d.ts +43 -0
- package/dist/_session-persist.d.ts +30 -0
- package/dist/_ssrf.d.ts +30 -0
- package/dist/_ssrf.js +123 -0
- package/dist/_utils.d.ts +25 -0
- package/dist/_utils.js +54 -1
- package/dist/builtin-tools.d.ts +5 -34
- package/dist/direct-executor-Ca0wt5H0.js +572 -0
- package/dist/direct-executor.d.ts +34 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/kv.d.ts +30 -38
- package/dist/kv.js +19 -86
- package/dist/matchers.d.ts +20 -0
- package/dist/matchers.js +41 -0
- package/dist/memory-tools.d.ts +39 -0
- package/dist/middleware-core.d.ts +47 -0
- package/dist/middleware-core.js +107 -0
- package/dist/middleware.d.ts +37 -0
- package/dist/protocol.d.ts +44 -24
- package/dist/protocol.js +34 -14
- package/dist/runtime.d.ts +26 -2
- package/dist/runtime.js +44 -7
- package/dist/s2s.d.ts +19 -29
- package/dist/s2s.js +117 -87
- package/dist/server.d.ts +31 -3
- package/dist/server.js +102 -28
- package/dist/session-BkN9u0ni.js +683 -0
- package/dist/session.d.ts +55 -28
- package/dist/session.js +2 -312
- package/dist/sqlite-kv.d.ts +34 -0
- package/dist/sqlite-kv.js +133 -0
- package/dist/sqlite-vector.d.ts +58 -0
- package/dist/sqlite-vector.js +149 -0
- package/dist/system-prompt.d.ts +21 -0
- package/dist/telemetry.d.ts +49 -0
- package/dist/telemetry.js +95 -0
- package/dist/testing-MRl3SXsI.js +519 -0
- package/dist/testing.d.ts +299 -0
- package/dist/testing.js +2 -0
- package/dist/types.d.ts +324 -39
- package/dist/types.js +62 -9
- package/dist/vector.d.ts +18 -22
- package/dist/vector.js +41 -48
- package/dist/worker-entry.d.ts +11 -3
- package/dist/worker-entry.js +19 -8
- package/dist/ws-handler.d.ts +7 -3
- package/dist/ws-handler.js +64 -12
- package/package.json +55 -8
- package/dist/_mock-ws.js +0 -158
- package/dist/builtin-tools.js +0 -270
- package/dist/direct-executor.js +0 -125
package/dist/vector.js
CHANGED
|
@@ -1,56 +1,49 @@
|
|
|
1
1
|
//#region vector.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
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
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 {
|
|
49
|
+
export { validateVectorFilter };
|
package/dist/worker-entry.d.ts
CHANGED
|
@@ -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
|
|
32
|
-
*
|
|
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.
|
package/dist/worker-entry.js
CHANGED
|
@@ -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
|
|
29
|
-
*
|
|
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 `
|
|
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
|
-
|
|
53
|
-
|
|
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
|
}
|
package/dist/ws-handler.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/ws-handler.js
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.10.1",
|
|
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": "./
|
|
31
|
-
"types": "./dist/
|
|
32
|
-
"import": "./dist/
|
|
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
|
-
"
|
|
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.
|
|
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 };
|