@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/kv.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Key-value storage interface and
|
|
2
|
+
* Key-value storage interface and shared utilities.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* A single key-value entry returned by {@link Kv.list}.
|
|
6
6
|
*
|
|
7
7
|
* @typeParam T - The type of the stored value. Defaults to `unknown`.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
8
10
|
*/
|
|
9
11
|
export type KvEntry<T = unknown> = {
|
|
10
12
|
/** The key under which the value is stored. */
|
|
@@ -15,7 +17,10 @@ export type KvEntry<T = unknown> = {
|
|
|
15
17
|
/**
|
|
16
18
|
* Options for listing keys from the KV store.
|
|
17
19
|
*
|
|
18
|
-
* Used with {@link Kv.list}
|
|
20
|
+
* Used with {@link Kv.list} and {@link Kv.keys} to control filtering,
|
|
21
|
+
* ordering, and pagination.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
19
24
|
*/
|
|
20
25
|
export type KvListOptions = {
|
|
21
26
|
/** Maximum number of entries to return. */
|
|
@@ -26,8 +31,8 @@ export type KvListOptions = {
|
|
|
26
31
|
/**
|
|
27
32
|
* Async key-value store interface used by agents.
|
|
28
33
|
*
|
|
29
|
-
* Agents access the KV store via
|
|
30
|
-
*
|
|
34
|
+
* Agents access the KV store via `ToolContext.kv` or
|
|
35
|
+
* `HookContext.kv`. Values are JSON-serialized and stored as
|
|
31
36
|
* strings with an optional TTL.
|
|
32
37
|
*
|
|
33
38
|
* @example
|
|
@@ -42,6 +47,8 @@ export type KvListOptions = {
|
|
|
42
47
|
* },
|
|
43
48
|
* };
|
|
44
49
|
* ```
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
45
52
|
*/
|
|
46
53
|
export type Kv = {
|
|
47
54
|
/**
|
|
@@ -58,19 +65,19 @@ export type Kv = {
|
|
|
58
65
|
*
|
|
59
66
|
* @param key - The key to store the value under.
|
|
60
67
|
* @param value - The value to store. Must be JSON-serializable.
|
|
61
|
-
* @param options - Optional settings. `expireIn` sets the time-to-live in milliseconds
|
|
62
|
-
* automatically removed after this duration.
|
|
63
|
-
* @throws
|
|
68
|
+
* @param options - Optional settings. `expireIn` sets the time-to-live in **milliseconds**
|
|
69
|
+
* (e.g. `60_000` for 1 minute). The entry is automatically removed after this duration.
|
|
70
|
+
* @throws Throws an Error if the serialized value exceeds 65,536 bytes.
|
|
64
71
|
*/
|
|
65
72
|
set(key: string, value: unknown, options?: {
|
|
66
73
|
expireIn?: number;
|
|
67
74
|
}): Promise<void>;
|
|
68
75
|
/**
|
|
69
|
-
* Delete
|
|
76
|
+
* Delete one or more keys.
|
|
70
77
|
*
|
|
71
|
-
* @param
|
|
78
|
+
* @param keys - A single key or array of keys to delete. No-op for keys that do not exist.
|
|
72
79
|
*/
|
|
73
|
-
delete(
|
|
80
|
+
delete(keys: string | string[]): Promise<void>;
|
|
74
81
|
/**
|
|
75
82
|
* List entries whose keys start with the given prefix.
|
|
76
83
|
*
|
|
@@ -83,12 +90,21 @@ export type Kv = {
|
|
|
83
90
|
*/
|
|
84
91
|
list<T = unknown>(prefix: string, options?: KvListOptions): Promise<KvEntry<T>[]>;
|
|
85
92
|
/**
|
|
86
|
-
* List all keys, optionally filtered by a glob-style pattern.
|
|
93
|
+
* List all keys, optionally filtered by a prefix or glob-style pattern.
|
|
87
94
|
*
|
|
88
|
-
* @param pattern - Optional glob pattern (e.g. `"user:*"`).
|
|
95
|
+
* @param pattern - Optional prefix string or glob pattern (e.g. `"user:*"`).
|
|
96
|
+
* A pattern without wildcards (`*`) is treated as a prefix match.
|
|
97
|
+
* If omitted, all keys are returned.
|
|
89
98
|
* @returns An array of matching key strings.
|
|
90
99
|
*/
|
|
91
100
|
keys(pattern?: string): Promise<string[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Close the KV store, releasing any resources (intervals, database handles).
|
|
103
|
+
*
|
|
104
|
+
* After calling `close()`, the store must not be used. This is a no-op
|
|
105
|
+
* for implementations that hold no resources (e.g. in-memory stores).
|
|
106
|
+
*/
|
|
107
|
+
close?(): void;
|
|
92
108
|
};
|
|
93
109
|
export declare const MAX_VALUE_SIZE = 65536;
|
|
94
110
|
/** Sort entries by key and apply reverse/limit options. Mutates the array. */
|
|
@@ -98,29 +114,5 @@ export declare function sortAndPaginate<T extends {
|
|
|
98
114
|
limit?: number;
|
|
99
115
|
reverse?: boolean;
|
|
100
116
|
}): T[];
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
*
|
|
104
|
-
* Data is stored in a plain `Map` and does not persist across restarts.
|
|
105
|
-
* TTL expiration is checked lazily on reads and list operations.
|
|
106
|
-
*
|
|
107
|
-
* @returns A {@link Kv} instance backed by in-memory storage.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```ts
|
|
111
|
-
* import { createMemoryKv } from "./kv.ts";
|
|
112
|
-
*
|
|
113
|
-
* const kv = createMemoryKv();
|
|
114
|
-
* await kv.set("greeting", "hello");
|
|
115
|
-
* const value = await kv.get<string>("greeting"); // "hello"
|
|
116
|
-
* ```
|
|
117
|
-
*
|
|
118
|
-
* @example With TTL
|
|
119
|
-
* ```ts
|
|
120
|
-
* import { createMemoryKv } from "./kv.ts";
|
|
121
|
-
*
|
|
122
|
-
* const kv = createMemoryKv();
|
|
123
|
-
* await kv.set("temp", "expires soon", { expireIn: 5000 });
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
export declare function createMemoryKv(): Kv;
|
|
117
|
+
/** Simple glob matcher — supports `*` as a wildcard for any characters. */
|
|
118
|
+
export declare function matchGlob(key: string, pattern: string): boolean;
|
package/dist/kv.js
CHANGED
|
@@ -2,98 +2,31 @@
|
|
|
2
2
|
const MAX_VALUE_SIZE = 65536;
|
|
3
3
|
/** Sort entries by key and apply reverse/limit options. Mutates the array. */
|
|
4
4
|
function sortAndPaginate(entries, options) {
|
|
5
|
-
entries.sort((a, b) => a.key
|
|
5
|
+
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
6
6
|
if (options?.reverse) entries.reverse();
|
|
7
7
|
if (options?.limit && options.limit > 0) entries.length = Math.min(entries.length, options.limit);
|
|
8
8
|
return entries;
|
|
9
9
|
}
|
|
10
|
+
/** Maximum allowed glob pattern length to prevent ReDoS. */
|
|
11
|
+
const MAX_GLOB_PATTERN_LENGTH = 1024;
|
|
10
12
|
/** Simple glob matcher — supports `*` as a wildcard for any characters. */
|
|
11
13
|
function matchGlob(key, pattern) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
* const kv = createMemoryKv();
|
|
28
|
-
* await kv.set("greeting", "hello");
|
|
29
|
-
* const value = await kv.get<string>("greeting"); // "hello"
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @example With TTL
|
|
33
|
-
* ```ts
|
|
34
|
-
* import { createMemoryKv } from "./kv.ts";
|
|
35
|
-
*
|
|
36
|
-
* const kv = createMemoryKv();
|
|
37
|
-
* await kv.set("temp", "expires soon", { expireIn: 5000 });
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
function createMemoryKv() {
|
|
41
|
-
const store = /* @__PURE__ */ new Map();
|
|
42
|
-
function isExpired(entry) {
|
|
43
|
-
return entry.expiresAt !== void 0 && entry.expiresAt <= Date.now();
|
|
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;
|
|
44
28
|
}
|
|
45
|
-
return
|
|
46
|
-
get(key) {
|
|
47
|
-
const entry = store.get(key);
|
|
48
|
-
if (!entry || isExpired(entry)) {
|
|
49
|
-
if (entry) store.delete(key);
|
|
50
|
-
return Promise.resolve(null);
|
|
51
|
-
}
|
|
52
|
-
return Promise.resolve(JSON.parse(entry.raw));
|
|
53
|
-
},
|
|
54
|
-
set(key, value, options) {
|
|
55
|
-
const raw = JSON.stringify(value);
|
|
56
|
-
if (raw.length > 65536) throw new Error(`Value exceeds max size of ${MAX_VALUE_SIZE} bytes`);
|
|
57
|
-
const expireIn = options?.expireIn;
|
|
58
|
-
const entry = { raw };
|
|
59
|
-
if (expireIn && expireIn > 0) entry.expiresAt = Date.now() + expireIn;
|
|
60
|
-
store.set(key, entry);
|
|
61
|
-
return Promise.resolve();
|
|
62
|
-
},
|
|
63
|
-
delete(key) {
|
|
64
|
-
store.delete(key);
|
|
65
|
-
return Promise.resolve();
|
|
66
|
-
},
|
|
67
|
-
async list(prefix, options) {
|
|
68
|
-
const now = Date.now();
|
|
69
|
-
const entries = [];
|
|
70
|
-
let i = 0;
|
|
71
|
-
for (const [key, entry] of store) {
|
|
72
|
-
if (++i % 500 === 0) await new Promise((r) => setTimeout(r, 0));
|
|
73
|
-
if (entry.expiresAt && entry.expiresAt <= now) {
|
|
74
|
-
store.delete(key);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (key.startsWith(prefix)) entries.push({
|
|
78
|
-
key,
|
|
79
|
-
value: JSON.parse(entry.raw)
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return sortAndPaginate(entries, options);
|
|
83
|
-
},
|
|
84
|
-
async keys(pattern) {
|
|
85
|
-
const now = Date.now();
|
|
86
|
-
const result = [];
|
|
87
|
-
for (const [key, entry] of store) {
|
|
88
|
-
if (entry.expiresAt && entry.expiresAt <= now) {
|
|
89
|
-
store.delete(key);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (!pattern || matchGlob(key, pattern)) result.push(key);
|
|
93
|
-
}
|
|
94
|
-
return result.sort();
|
|
95
|
-
}
|
|
96
|
-
};
|
|
29
|
+
return pos <= end;
|
|
97
30
|
}
|
|
98
31
|
//#endregion
|
|
99
|
-
export { MAX_VALUE_SIZE,
|
|
32
|
+
export { MAX_VALUE_SIZE, matchGlob, sortAndPaginate };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest custom matchers for AAI testing.
|
|
3
|
+
*
|
|
4
|
+
* Add this to your Vitest setup file to enable `expect(turn).toHaveCalledTool()`:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* // vitest.config.ts
|
|
8
|
+
* export default defineConfig({
|
|
9
|
+
* test: { setupFiles: ["@alexkroman1/aai/testing/matchers"] },
|
|
10
|
+
* });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Or import directly in your test file:
|
|
14
|
+
* ```ts
|
|
15
|
+
* import "@alexkroman1/aai/testing/matchers";
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
package/dist/matchers.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { n as TurnResult } from "./testing-MRl3SXsI.js";
|
|
2
|
+
import { expect } from "vitest";
|
|
3
|
+
//#region matchers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Vitest custom matchers for AAI testing.
|
|
6
|
+
*
|
|
7
|
+
* Add this to your Vitest setup file to enable `expect(turn).toHaveCalledTool()`:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // vitest.config.ts
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* test: { setupFiles: ["@alexkroman1/aai/testing/matchers"] },
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Or import directly in your test file:
|
|
17
|
+
* ```ts
|
|
18
|
+
* import "@alexkroman1/aai/testing/matchers";
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @packageDocumentation
|
|
22
|
+
*/
|
|
23
|
+
expect.extend({ toHaveCalledTool(received, toolName, args) {
|
|
24
|
+
if (!(received instanceof TurnResult)) return {
|
|
25
|
+
pass: false,
|
|
26
|
+
message: () => `expected a TurnResult, got ${typeof received}`,
|
|
27
|
+
actual: received,
|
|
28
|
+
expected: "TurnResult"
|
|
29
|
+
};
|
|
30
|
+
const pass = received.toHaveCalledTool(toolName, args);
|
|
31
|
+
const calledTools = received.toolCalls.map((tc) => tc.toolName);
|
|
32
|
+
const argsHint = args ? ` with args ${JSON.stringify(args)}` : "";
|
|
33
|
+
return {
|
|
34
|
+
pass,
|
|
35
|
+
message: () => pass ? `expected turn NOT to have called tool "${toolName}"${argsHint}, but it was called.\nCalled tools: ${JSON.stringify(calledTools)}` : `expected turn to have called tool "${toolName}"${argsHint}, but it was not.\nCalled tools: ${JSON.stringify(calledTools)}`,
|
|
36
|
+
actual: calledTools,
|
|
37
|
+
expected: toolName
|
|
38
|
+
};
|
|
39
|
+
} });
|
|
40
|
+
//#endregion
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KV-backed memory tools for agent persistent state.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/**
|
|
6
|
+
* Returns a standard set of KV-backed memory tools: `save_memory`,
|
|
7
|
+
* `recall_memory`, `list_memories`, and `forget_memory`.
|
|
8
|
+
*
|
|
9
|
+
* Spread the result into your agent's `tools` record.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { defineAgent, memoryTools } from "aai";
|
|
14
|
+
*
|
|
15
|
+
* export default defineAgent({
|
|
16
|
+
* name: "My Agent",
|
|
17
|
+
* tools: { ...memoryTools() },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @returns A record with four tool definitions: `save_memory`, `recall_memory`,
|
|
22
|
+
* `list_memories`, and `forget_memory`.
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export declare function memoryTools(): {
|
|
26
|
+
save_memory: import("./types.ts").ToolDef<z.ZodObject<{
|
|
27
|
+
key: z.ZodString;
|
|
28
|
+
value: z.ZodString;
|
|
29
|
+
}, z.core.$strip>, Record<string, unknown>>;
|
|
30
|
+
recall_memory: import("./types.ts").ToolDef<z.ZodObject<{
|
|
31
|
+
key: z.ZodString;
|
|
32
|
+
}, z.core.$strip>, Record<string, unknown>>;
|
|
33
|
+
list_memories: import("./types.ts").ToolDef<z.ZodObject<{
|
|
34
|
+
prefix: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>, Record<string, unknown>>;
|
|
36
|
+
forget_memory: import("./types.ts").ToolDef<z.ZodObject<{
|
|
37
|
+
key: z.ZodString;
|
|
38
|
+
}, z.core.$strip>, Record<string, unknown>>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure middleware runner functions — zero runtime dependencies.
|
|
3
|
+
*
|
|
4
|
+
* This module is intentionally dependency-free so it can be bundled into
|
|
5
|
+
* the secure-exec isolate harness (which has no access to node_modules).
|
|
6
|
+
* Only `import type` statements are allowed here.
|
|
7
|
+
*/
|
|
8
|
+
import type { HookContext, Middleware, MiddlewareBlockResult } from "./types.ts";
|
|
9
|
+
/** Result from a middleware tool call interceptor. */
|
|
10
|
+
export type ToolInterceptResult = {
|
|
11
|
+
type: "block";
|
|
12
|
+
reason: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: "result";
|
|
15
|
+
result: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "args";
|
|
18
|
+
args: Record<string, unknown>;
|
|
19
|
+
} | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Run all `beforeInput` middleware in order, piping the text through each.
|
|
22
|
+
* Symmetric to {@link runOutputFilters} but for user input.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runInputFilters(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Run all `beforeTurn` middleware in order. Returns a block result if any
|
|
27
|
+
* middleware blocks the turn, or `undefined` to proceed.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runBeforeTurnMiddleware(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<MiddlewareBlockResult | undefined>;
|
|
30
|
+
/**
|
|
31
|
+
* Run all `afterTurn` middleware in reverse order.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runAfterTurnMiddleware(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Run all `beforeToolCall` middleware in order. Returns a result that
|
|
36
|
+
* may block execution, provide a cached result, or transform args.
|
|
37
|
+
* Returns `undefined` to proceed with normal execution.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runToolCallInterceptors(middleware: readonly Middleware[], toolName: string, args: Readonly<Record<string, unknown>>, ctx: HookContext): Promise<ToolInterceptResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Run all `afterToolCall` middleware in reverse order.
|
|
42
|
+
*/
|
|
43
|
+
export declare function runAfterToolCallMiddleware(middleware: readonly Middleware[], toolName: string, args: Readonly<Record<string, unknown>>, result: string, ctx: HookContext): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Run all `beforeOutput` middleware in order, piping the text through each.
|
|
46
|
+
*/
|
|
47
|
+
export declare function runOutputFilters(middleware: readonly Middleware[], text: string, ctx: HookContext): Promise<string>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//#region middleware-core.ts
|
|
2
|
+
/**
|
|
3
|
+
* Run all `beforeInput` middleware in order, piping the text through each.
|
|
4
|
+
* Symmetric to {@link runOutputFilters} but for user input.
|
|
5
|
+
*/
|
|
6
|
+
async function runInputFilters(middleware, text, ctx) {
|
|
7
|
+
let filtered = text;
|
|
8
|
+
for (const mw of middleware) {
|
|
9
|
+
if (!mw.beforeInput) continue;
|
|
10
|
+
try {
|
|
11
|
+
filtered = await mw.beforeInput(filtered, ctx);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.warn("Middleware beforeInput failed:", err);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return filtered;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run all `beforeTurn` middleware in order. Returns a block result if any
|
|
20
|
+
* middleware blocks the turn, or `undefined` to proceed.
|
|
21
|
+
*/
|
|
22
|
+
async function runBeforeTurnMiddleware(middleware, text, ctx) {
|
|
23
|
+
for (const mw of middleware) {
|
|
24
|
+
if (!mw.beforeTurn) continue;
|
|
25
|
+
try {
|
|
26
|
+
const result = await mw.beforeTurn(text, ctx);
|
|
27
|
+
if (result && "block" in result && result.block) return result;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.warn("Middleware beforeTurn failed:", err);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run all `afterTurn` middleware in reverse order.
|
|
35
|
+
*/
|
|
36
|
+
async function runAfterTurnMiddleware(middleware, text, ctx) {
|
|
37
|
+
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
38
|
+
const mw = middleware[i];
|
|
39
|
+
if (!mw?.afterTurn) continue;
|
|
40
|
+
try {
|
|
41
|
+
await mw.afterTurn(text, ctx);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.warn("Middleware afterTurn failed:", err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Run all `beforeToolCall` middleware in order. Returns a result that
|
|
49
|
+
* may block execution, provide a cached result, or transform args.
|
|
50
|
+
* Returns `undefined` to proceed with normal execution.
|
|
51
|
+
*/
|
|
52
|
+
async function runToolCallInterceptors(middleware, toolName, args, ctx) {
|
|
53
|
+
let currentArgs = args;
|
|
54
|
+
for (const mw of middleware) {
|
|
55
|
+
if (!mw.beforeToolCall) continue;
|
|
56
|
+
try {
|
|
57
|
+
const result = await mw.beforeToolCall(toolName, currentArgs, ctx);
|
|
58
|
+
if (!result) continue;
|
|
59
|
+
if ("block" in result && result.block) return {
|
|
60
|
+
type: "block",
|
|
61
|
+
reason: result.reason
|
|
62
|
+
};
|
|
63
|
+
if ("result" in result) return {
|
|
64
|
+
type: "result",
|
|
65
|
+
result: result.result
|
|
66
|
+
};
|
|
67
|
+
if ("args" in result) currentArgs = result.args;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.warn("Middleware beforeToolCall failed:", err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (currentArgs !== args) return {
|
|
73
|
+
type: "args",
|
|
74
|
+
args: currentArgs
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Run all `afterToolCall` middleware in reverse order.
|
|
79
|
+
*/
|
|
80
|
+
async function runAfterToolCallMiddleware(middleware, toolName, args, result, ctx) {
|
|
81
|
+
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
82
|
+
const mw = middleware[i];
|
|
83
|
+
if (!mw?.afterToolCall) continue;
|
|
84
|
+
try {
|
|
85
|
+
await mw.afterToolCall(toolName, args, result, ctx);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.warn("Middleware afterToolCall failed:", err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Run all `beforeOutput` middleware in order, piping the text through each.
|
|
93
|
+
*/
|
|
94
|
+
async function runOutputFilters(middleware, text, ctx) {
|
|
95
|
+
let filtered = text;
|
|
96
|
+
for (const mw of middleware) {
|
|
97
|
+
if (!mw.beforeOutput) continue;
|
|
98
|
+
try {
|
|
99
|
+
filtered = await mw.beforeOutput(filtered, ctx);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.warn("Middleware beforeOutput failed:", err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return filtered;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { runAfterToolCallMiddleware, runAfterTurnMiddleware, runBeforeTurnMiddleware, runInputFilters, runOutputFilters, runToolCallInterceptors };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware runner — executes middleware chains for turns, tool calls,
|
|
3
|
+
* and output filtering.
|
|
4
|
+
*
|
|
5
|
+
* Pure runner logic lives in middleware-core.ts (isolate-safe, zero deps).
|
|
6
|
+
* This module re-exports it and adds the HookInvoker interface.
|
|
7
|
+
*/
|
|
8
|
+
import type { StepInfo } from "./types.ts";
|
|
9
|
+
export { runAfterToolCallMiddleware, runAfterTurnMiddleware, runBeforeTurnMiddleware, runInputFilters, runOutputFilters, runToolCallInterceptors, type ToolInterceptResult, } from "./middleware-core.ts";
|
|
10
|
+
/** Generic interface for invoking agent lifecycle hooks, including middleware. */
|
|
11
|
+
export type HookInvoker = {
|
|
12
|
+
onConnect(sessionId: string, timeoutMs?: number): Promise<void>;
|
|
13
|
+
onDisconnect(sessionId: string, timeoutMs?: number): Promise<void>;
|
|
14
|
+
onTurn(sessionId: string, text: string, timeoutMs?: number): Promise<void>;
|
|
15
|
+
onError(sessionId: string, error: {
|
|
16
|
+
message: string;
|
|
17
|
+
}, timeoutMs?: number): Promise<void>;
|
|
18
|
+
onStep(sessionId: string, step: StepInfo, timeoutMs?: number): Promise<void>;
|
|
19
|
+
resolveTurnConfig(sid: string, ms?: number): Promise<{
|
|
20
|
+
maxSteps?: number;
|
|
21
|
+
} | null>;
|
|
22
|
+
filterInput?(sid: string, text: string, ms?: number): Promise<string>;
|
|
23
|
+
beforeTurn?(sid: string, text: string, ms?: number): Promise<string | undefined>;
|
|
24
|
+
afterTurn?(sid: string, text: string, ms?: number): Promise<void>;
|
|
25
|
+
interceptToolCall?(sid: string, tool: string, args: Readonly<Record<string, unknown>>, ms?: number): Promise<{
|
|
26
|
+
type: "block";
|
|
27
|
+
reason: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "result";
|
|
30
|
+
result: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "args";
|
|
33
|
+
args: Record<string, unknown>;
|
|
34
|
+
} | undefined>;
|
|
35
|
+
afterToolCall?(sid: string, tool: string, args: Readonly<Record<string, unknown>>, result: string, ms?: number): Promise<void>;
|
|
36
|
+
filterOutput?(sid: string, text: string, ms?: number): Promise<string>;
|
|
37
|
+
};
|