@alexkroman1/aai 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_embeddings.d.ts +31 -0
- package/dist/_internal-types-IfPcaJd5.js +61 -0
- package/dist/_internal-types.js +1 -60
- package/dist/_ssrf-DCp_27V4.js +123 -0
- package/dist/_ssrf.js +1 -122
- package/dist/_utils-DgzpOMSV.js +61 -0
- package/dist/_utils.js +1 -60
- package/dist/{direct-executor-Ca0wt5H0.js → direct-executor-B-5mq3cu.js} +15 -17
- package/dist/index.js +1 -1
- package/dist/kv-iXtikQmR.js +32 -0
- package/dist/kv.js +1 -31
- package/dist/matchers.js +1 -1
- package/dist/middleware-core-BwyBIPed.js +107 -0
- package/dist/middleware-core.js +1 -106
- package/dist/protocol-B-H2Q4ox.js +162 -0
- package/dist/protocol.js +1 -161
- package/dist/runtime-CxcwaK68.js +58 -0
- package/dist/runtime.js +1 -52
- package/dist/s2s-M7JqtgFw.js +272 -0
- package/dist/s2s.js +1 -271
- package/dist/server.d.ts +6 -6
- package/dist/server.js +47 -43
- package/dist/{session-BkN9u0ni.js → session-BYlwcrya.js} +6 -6
- package/dist/session.js +1 -1
- package/dist/telemetry-CJlaDFNc.js +95 -0
- package/dist/telemetry.js +1 -94
- package/dist/{testing-MRl3SXsI.js → testing-BbitshLb.js} +7 -9
- package/dist/testing.js +1 -1
- package/dist/types-D8ZBxTL_.js +192 -0
- package/dist/types.js +1 -191
- package/dist/unstorage-kv-CDgP-frt.js +64 -0
- package/dist/unstorage-kv.d.ts +33 -0
- package/dist/unstorage-kv.js +2 -0
- package/dist/unstorage-vector-Cj5llNhg.js +172 -0
- package/dist/unstorage-vector.d.ts +47 -0
- package/dist/unstorage-vector.js +2 -0
- package/dist/vector.d.ts +3 -2
- package/dist/worker-entry-2jaiqIj0.js +70 -0
- package/dist/worker-entry.js +1 -69
- package/dist/ws-handler-C0Q6eSay.js +207 -0
- package/dist/ws-handler.js +1 -206
- package/package.json +14 -9
- package/dist/sqlite-kv.d.ts +0 -34
- package/dist/sqlite-kv.js +0 -133
- package/dist/sqlite-vector.d.ts +0 -58
- package/dist/sqlite-vector.js +0 -149
package/dist/types.js
CHANGED
|
@@ -1,192 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region types.ts
|
|
3
|
-
/**
|
|
4
|
-
* Core type definitions for the AAI agent SDK.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Identity helper that preserves the Zod schema generic for type inference.
|
|
8
|
-
*
|
|
9
|
-
* When tools are defined inline in `defineAgent({ tools: { ... } })`, the
|
|
10
|
-
* generic `P` gets widened to the base `ZodObject` type, so `args` in
|
|
11
|
-
* `execute` loses its specific shape. Wrapping a tool definition in
|
|
12
|
-
* `defineTool()` lets TypeScript infer `P` from `parameters` and type
|
|
13
|
-
* `args` correctly.
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```ts
|
|
17
|
-
* import { defineAgent, defineTool } from "aai";
|
|
18
|
-
* import { z } from "zod";
|
|
19
|
-
*
|
|
20
|
-
* export default defineAgent({
|
|
21
|
-
* name: "my-agent",
|
|
22
|
-
* tools: {
|
|
23
|
-
* greet: defineTool({
|
|
24
|
-
* description: "Greet the user",
|
|
25
|
-
* parameters: z.object({ name: z.string() }),
|
|
26
|
-
* execute: ({ name }) => `Hello, ${name}!`, // name is string
|
|
27
|
-
* }),
|
|
28
|
-
* },
|
|
29
|
-
* });
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @public
|
|
33
|
-
*/
|
|
34
|
-
function defineTool(def) {
|
|
35
|
-
return def;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Create a typed `defineTool` helper with the session state type baked in.
|
|
39
|
-
*
|
|
40
|
-
* When tools need access to typed session state, you'd normally have to write
|
|
41
|
-
* verbose generics on every `defineTool` call. `createToolFactory` eliminates
|
|
42
|
-
* that boilerplate by returning a `defineTool` variant that already knows `S`.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* import { createToolFactory, defineAgent } from "aai";
|
|
47
|
-
* import { z } from "zod";
|
|
48
|
-
*
|
|
49
|
-
* interface PortfolioState { holdings: Map<string, number> }
|
|
50
|
-
*
|
|
51
|
-
* const tool = createToolFactory<PortfolioState>();
|
|
52
|
-
*
|
|
53
|
-
* export default defineAgent<PortfolioState>({
|
|
54
|
-
* name: "portfolio",
|
|
55
|
-
* state: () => ({ holdings: new Map() }),
|
|
56
|
-
* tools: {
|
|
57
|
-
* buy: tool({
|
|
58
|
-
* description: "Buy shares",
|
|
59
|
-
* parameters: z.object({ symbol: z.string(), qty: z.number() }),
|
|
60
|
-
* execute: (args, ctx) => {
|
|
61
|
-
* // args.symbol is string, ctx.state is PortfolioState
|
|
62
|
-
* ctx.state.holdings.set(args.symbol, args.qty);
|
|
63
|
-
* },
|
|
64
|
-
* }),
|
|
65
|
-
* },
|
|
66
|
-
* });
|
|
67
|
-
* ```
|
|
68
|
-
*
|
|
69
|
-
* @public
|
|
70
|
-
*/
|
|
71
|
-
function createToolFactory() {
|
|
72
|
-
return (def) => def;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Default system prompt used when `instructions` is not provided.
|
|
76
|
-
*
|
|
77
|
-
* Optimized for voice-first interactions: short sentences, no visual
|
|
78
|
-
* formatting, confident tone, and concise answers.
|
|
79
|
-
*/
|
|
80
|
-
const DEFAULT_INSTRUCTIONS = `\
|
|
81
|
-
You are AAI, a helpful AI assistant.
|
|
82
|
-
|
|
83
|
-
Voice-First Rules:
|
|
84
|
-
- Optimize for natural speech. Avoid jargon unless central to the answer. \
|
|
85
|
-
Use short, punchy sentences.
|
|
86
|
-
- Never mention "search results," "sources," or "the provided text." \
|
|
87
|
-
Speak as if the knowledge is your own.
|
|
88
|
-
- No visual formatting. Do not say "bullet point," "bold," or "bracketed one." \
|
|
89
|
-
If you need to list items, say "First," "Next," and "Finally."
|
|
90
|
-
- Start with the most important information. No introductory filler.
|
|
91
|
-
- Be concise. Keep answers to 1-3 sentences. For complex topics, provide a high-level summary.
|
|
92
|
-
- Be confident. Avoid hedging phrases like "It seems that" or "I believe."
|
|
93
|
-
- If you don't have enough information, say so directly rather than guessing.
|
|
94
|
-
- Never use exclamation points. Keep your tone calm and conversational.`;
|
|
95
|
-
/** Default greeting spoken when a session starts. */
|
|
96
|
-
const DEFAULT_GREETING = "Hey there. I'm a voice assistant. What can I help you with?";
|
|
97
|
-
/** @internal Zod schema for {@link BuiltinTool}. Exported for reuse in internal schemas. */
|
|
98
|
-
const BuiltinToolSchema = z.enum([
|
|
99
|
-
"web_search",
|
|
100
|
-
"visit_webpage",
|
|
101
|
-
"fetch_json",
|
|
102
|
-
"run_code",
|
|
103
|
-
"vector_search",
|
|
104
|
-
"memory"
|
|
105
|
-
]);
|
|
106
|
-
/** @internal Zod schema for {@link ToolChoice}. Exported for reuse in internal schemas. */
|
|
107
|
-
const ToolChoiceSchema = z.union([z.enum([
|
|
108
|
-
"auto",
|
|
109
|
-
"required",
|
|
110
|
-
"none"
|
|
111
|
-
]), z.object({
|
|
112
|
-
type: z.literal("tool"),
|
|
113
|
-
toolName: z.string().min(1)
|
|
114
|
-
})]);
|
|
115
|
-
const ToolDefSchema = z.object({
|
|
116
|
-
description: z.string().min(1, "Tool description must be non-empty"),
|
|
117
|
-
parameters: z.custom((val) => val === void 0 || val instanceof z.ZodType, "Expected a Zod schema").optional(),
|
|
118
|
-
execute: z.function()
|
|
119
|
-
});
|
|
120
|
-
/** Default TTL for persisted session data: 1 hour. */
|
|
121
|
-
const DEFAULT_PERSIST_TTL = 36e5;
|
|
122
|
-
const AgentOptionsSchema = z.object({
|
|
123
|
-
name: z.string().min(1, "Agent name must be non-empty"),
|
|
124
|
-
instructions: z.string().optional(),
|
|
125
|
-
greeting: z.string().optional(),
|
|
126
|
-
sttPrompt: z.string().optional(),
|
|
127
|
-
maxSteps: z.union([z.number().int().positive(), z.function()]).optional(),
|
|
128
|
-
toolChoice: ToolChoiceSchema.optional(),
|
|
129
|
-
builtinTools: z.array(BuiltinToolSchema).optional(),
|
|
130
|
-
tools: z.record(z.string(), ToolDefSchema).optional(),
|
|
131
|
-
state: z.function().optional(),
|
|
132
|
-
persistence: z.union([z.literal(true), z.object({ ttl: z.number().int().positive().optional() })]).optional(),
|
|
133
|
-
onConnect: z.function().optional(),
|
|
134
|
-
onDisconnect: z.function().optional(),
|
|
135
|
-
onError: z.function().optional(),
|
|
136
|
-
onTurn: z.function().optional(),
|
|
137
|
-
onStep: z.function().optional(),
|
|
138
|
-
middleware: z.array(z.object({
|
|
139
|
-
name: z.string().min(1, "Middleware name must be non-empty"),
|
|
140
|
-
beforeInput: z.function().optional(),
|
|
141
|
-
beforeTurn: z.function().optional(),
|
|
142
|
-
afterTurn: z.function().optional(),
|
|
143
|
-
beforeToolCall: z.function().optional(),
|
|
144
|
-
afterToolCall: z.function().optional(),
|
|
145
|
-
beforeOutput: z.function().optional()
|
|
146
|
-
})).optional(),
|
|
147
|
-
idleTimeoutMs: z.number().nonnegative().optional()
|
|
148
|
-
});
|
|
149
|
-
/**
|
|
150
|
-
* Create an agent definition from the given options, applying sensible defaults.
|
|
151
|
-
*
|
|
152
|
-
* This is the main entry point for defining a voice agent. The returned
|
|
153
|
-
* `AgentDef` is consumed by the AAI server at deploy time.
|
|
154
|
-
*
|
|
155
|
-
* @param options - Configuration for the agent including name, instructions,
|
|
156
|
-
* tools, hooks, and other settings.
|
|
157
|
-
* @returns A fully resolved agent definition with all defaults applied.
|
|
158
|
-
*
|
|
159
|
-
* @public
|
|
160
|
-
*
|
|
161
|
-
* @example Basic agent with a custom tool
|
|
162
|
-
* ```ts
|
|
163
|
-
* import { defineAgent } from "aai";
|
|
164
|
-
* import { z } from "zod";
|
|
165
|
-
*
|
|
166
|
-
* export default defineAgent({
|
|
167
|
-
* name: "greeter",
|
|
168
|
-
* instructions: "You greet people warmly.",
|
|
169
|
-
* tools: {
|
|
170
|
-
* greet: {
|
|
171
|
-
* description: "Greet a user by name",
|
|
172
|
-
* parameters: z.object({ name: z.string() }),
|
|
173
|
-
* execute: ({ name }) => `Hello, ${name}!`,
|
|
174
|
-
* },
|
|
175
|
-
* },
|
|
176
|
-
* });
|
|
177
|
-
* ```
|
|
178
|
-
*/
|
|
179
|
-
function defineAgent(options) {
|
|
180
|
-
AgentOptionsSchema.parse(options);
|
|
181
|
-
const persistence = options.persistence ? { ttl: typeof options.persistence === "object" ? options.persistence.ttl ?? DEFAULT_PERSIST_TTL : DEFAULT_PERSIST_TTL } : void 0;
|
|
182
|
-
return {
|
|
183
|
-
...options,
|
|
184
|
-
instructions: options.instructions ?? DEFAULT_INSTRUCTIONS,
|
|
185
|
-
greeting: options.greeting ?? "Hey there. I'm a voice assistant. What can I help you with?",
|
|
186
|
-
maxSteps: options.maxSteps ?? 5,
|
|
187
|
-
tools: options.tools ?? {},
|
|
188
|
-
persistence
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
//#endregion
|
|
1
|
+
import { a as createToolFactory, i as ToolChoiceSchema, n as DEFAULT_GREETING, o as defineAgent, r as DEFAULT_INSTRUCTIONS, s as defineTool, t as BuiltinToolSchema } from "./types-D8ZBxTL_.js";
|
|
192
2
|
export { BuiltinToolSchema, DEFAULT_GREETING, DEFAULT_INSTRUCTIONS, ToolChoiceSchema, createToolFactory, defineAgent, defineTool, defineTool as tool };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { n as matchGlob, r as sortAndPaginate, t as MAX_VALUE_SIZE } from "./kv-iXtikQmR.js";
|
|
2
|
+
import { prefixStorage } from "unstorage";
|
|
3
|
+
//#region unstorage-kv.ts
|
|
4
|
+
/**
|
|
5
|
+
* Key-value store backed by unstorage.
|
|
6
|
+
*
|
|
7
|
+
* Works with any unstorage driver (memory, fs, S3/R2, etc.).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Create a KV store backed by any unstorage driver.
|
|
11
|
+
*
|
|
12
|
+
* @param options - See {@link UnstorageKvOptions}.
|
|
13
|
+
* @returns A {@link Kv} instance.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createStorage } from "unstorage";
|
|
18
|
+
* import { createUnstorageKv } from "@alexkroman1/aai/unstorage-kv";
|
|
19
|
+
*
|
|
20
|
+
* const kv = createUnstorageKv({ storage: createStorage() });
|
|
21
|
+
* await kv.set("greeting", "hello");
|
|
22
|
+
* const value = await kv.get<string>("greeting"); // "hello"
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function createUnstorageKv(options) {
|
|
26
|
+
const store = options.prefix ? prefixStorage(options.storage, options.prefix) : options.storage;
|
|
27
|
+
return {
|
|
28
|
+
async get(key) {
|
|
29
|
+
return await store.getItem(key) ?? null;
|
|
30
|
+
},
|
|
31
|
+
async set(key, value, setOptions) {
|
|
32
|
+
if (JSON.stringify(value).length > 65536) throw new Error(`Value exceeds max size of ${MAX_VALUE_SIZE} bytes`);
|
|
33
|
+
const storable = value;
|
|
34
|
+
if (setOptions?.expireIn && setOptions.expireIn > 0) await store.setItem(key, storable, { ttl: Math.ceil(setOptions.expireIn / 1e3) });
|
|
35
|
+
else await store.setItem(key, storable);
|
|
36
|
+
},
|
|
37
|
+
async delete(keys) {
|
|
38
|
+
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
39
|
+
await Promise.all(keyArray.map((k) => store.removeItem(k)));
|
|
40
|
+
},
|
|
41
|
+
async list(listPrefix, listOptions) {
|
|
42
|
+
const allKeys = await store.getKeys(listPrefix);
|
|
43
|
+
const entries = [];
|
|
44
|
+
for (const key of allKeys) {
|
|
45
|
+
const value = await store.getItem(key);
|
|
46
|
+
if (value != null) entries.push({
|
|
47
|
+
key,
|
|
48
|
+
value
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return sortAndPaginate(entries, listOptions);
|
|
52
|
+
},
|
|
53
|
+
async keys(pattern) {
|
|
54
|
+
const allKeys = await store.getKeys();
|
|
55
|
+
if (!pattern) return allKeys.sort((a, b) => a.localeCompare(b));
|
|
56
|
+
return allKeys.filter((key) => matchGlob(key, pattern)).sort((a, b) => a.localeCompare(b));
|
|
57
|
+
},
|
|
58
|
+
close() {
|
|
59
|
+
store.dispose();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { createUnstorageKv as t };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key-value store backed by unstorage.
|
|
3
|
+
*
|
|
4
|
+
* Works with any unstorage driver (memory, fs, S3/R2, etc.).
|
|
5
|
+
*/
|
|
6
|
+
import { type Storage } from "unstorage";
|
|
7
|
+
import type { Kv } from "./kv.ts";
|
|
8
|
+
/**
|
|
9
|
+
* Options for creating an unstorage-backed KV store.
|
|
10
|
+
*/
|
|
11
|
+
export type UnstorageKvOptions = {
|
|
12
|
+
/** Configured unstorage Storage instance. */
|
|
13
|
+
storage: Storage;
|
|
14
|
+
/** Key prefix prepended to all operations (e.g. `"agents/my-agent/kv"`). */
|
|
15
|
+
prefix?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Create a KV store backed by any unstorage driver.
|
|
19
|
+
*
|
|
20
|
+
* @param options - See {@link UnstorageKvOptions}.
|
|
21
|
+
* @returns A {@link Kv} instance.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { createStorage } from "unstorage";
|
|
26
|
+
* import { createUnstorageKv } from "@alexkroman1/aai/unstorage-kv";
|
|
27
|
+
*
|
|
28
|
+
* const kv = createUnstorageKv({ storage: createStorage() });
|
|
29
|
+
* await kv.set("greeting", "hello");
|
|
30
|
+
* const value = await kv.get<string>("greeting"); // "hello"
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function createUnstorageKv(options: UnstorageKvOptions): Kv;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const DEFAULT_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
2
|
+
/**
|
|
3
|
+
* Create a local embedding function using `all-MiniLM-L6-v2`.
|
|
4
|
+
*
|
|
5
|
+
* The model is downloaded on first use (~86 MB) and cached locally.
|
|
6
|
+
* Subsequent calls load from cache in ~90ms. Each embedding takes <2ms.
|
|
7
|
+
*/
|
|
8
|
+
function createLocalEmbedFn(cacheDir) {
|
|
9
|
+
let pipelinePromise = null;
|
|
10
|
+
async function getPipeline() {
|
|
11
|
+
if (!pipelinePromise) pipelinePromise = (async () => {
|
|
12
|
+
const { pipeline, env } = await import("@huggingface/transformers");
|
|
13
|
+
env.cacheDir = cacheDir;
|
|
14
|
+
return pipeline("feature-extraction", DEFAULT_MODEL);
|
|
15
|
+
})();
|
|
16
|
+
return pipelinePromise;
|
|
17
|
+
}
|
|
18
|
+
return async (text) => {
|
|
19
|
+
const output = await (await getPipeline())(text, {
|
|
20
|
+
pooling: "mean",
|
|
21
|
+
normalize: true
|
|
22
|
+
});
|
|
23
|
+
return Array.from(output.data);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a deterministic hash-based embedding function for testing.
|
|
28
|
+
*
|
|
29
|
+
* Produces repeatable vectors where similar text yields similar embeddings.
|
|
30
|
+
* Not suitable for production — use the default local model instead.
|
|
31
|
+
*
|
|
32
|
+
* @param dimensions - Vector dimensions (default: 384).
|
|
33
|
+
*/
|
|
34
|
+
function createTestEmbedFn(dimensions = 384) {
|
|
35
|
+
return async (text) => {
|
|
36
|
+
const vec = new Float32Array(dimensions);
|
|
37
|
+
const words = text.toLowerCase().split(/\s+/).filter(Boolean);
|
|
38
|
+
for (const word of words) {
|
|
39
|
+
let hash = 0;
|
|
40
|
+
for (let i = 0; i < word.length; i++) hash = (hash << 5) - hash + word.charCodeAt(i) | 0;
|
|
41
|
+
for (let i = 0; i < 8; i++) {
|
|
42
|
+
const idx = Math.abs((hash + i * 31337) % dimensions);
|
|
43
|
+
vec[idx] = (vec[idx] ?? 0) + 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
let norm = 0;
|
|
47
|
+
for (let i = 0; i < dimensions; i++) norm += (vec[i] ?? 0) * (vec[i] ?? 0);
|
|
48
|
+
norm = Math.sqrt(norm) || 1;
|
|
49
|
+
return Array.from(vec, (v) => v / norm);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Cosine similarity between two Float32Array vectors. */
|
|
53
|
+
function cosineSimilarity(a, b) {
|
|
54
|
+
let dot = 0;
|
|
55
|
+
let normA = 0;
|
|
56
|
+
let normB = 0;
|
|
57
|
+
for (let i = 0; i < a.length; i++) {
|
|
58
|
+
const ai = a[i] ?? 0;
|
|
59
|
+
const bi = b[i] ?? 0;
|
|
60
|
+
dot += ai * bi;
|
|
61
|
+
normA += ai * ai;
|
|
62
|
+
normB += bi * bi;
|
|
63
|
+
}
|
|
64
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
65
|
+
return denom === 0 ? 0 : dot / denom;
|
|
66
|
+
}
|
|
67
|
+
/** Encode an embedding as a base64 string. */
|
|
68
|
+
function encodeEmbedding(vec) {
|
|
69
|
+
const f32 = vec instanceof Float32Array ? vec : new Float32Array(vec);
|
|
70
|
+
return Buffer.from(f32.buffer, f32.byteOffset, f32.byteLength).toString("base64");
|
|
71
|
+
}
|
|
72
|
+
/** Decode a base64 string back to a Float32Array embedding. */
|
|
73
|
+
function decodeEmbedding(b64) {
|
|
74
|
+
const buf = Buffer.from(b64, "base64");
|
|
75
|
+
return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region unstorage-vector.ts
|
|
79
|
+
/**
|
|
80
|
+
* Create a vector store backed by any unstorage driver.
|
|
81
|
+
*
|
|
82
|
+
* All vectors for the scope are stored in a single blob. The blob is loaded
|
|
83
|
+
* lazily on first operation and cached in memory. Mutations are written
|
|
84
|
+
* through immediately.
|
|
85
|
+
*
|
|
86
|
+
* @param options - See {@link UnstorageVectorOptions}.
|
|
87
|
+
* @returns A {@link VectorStore} instance.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { createStorage } from "unstorage";
|
|
92
|
+
* import { createUnstorageVectorStore } from "@alexkroman1/aai/unstorage-vector";
|
|
93
|
+
*
|
|
94
|
+
* const vector = createUnstorageVectorStore({ storage: createStorage() });
|
|
95
|
+
* await vector.upsert("doc-1", "The capital of France is Paris.");
|
|
96
|
+
* const results = await vector.query("France capital");
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function createUnstorageVectorStore(options) {
|
|
100
|
+
const { storage, blobKey = "vectors.json", dimensions = 384, modelCacheDir = ".aai/models" } = options;
|
|
101
|
+
const embedFn = options.embedFn ?? createLocalEmbedFn(modelCacheDir);
|
|
102
|
+
let cache = null;
|
|
103
|
+
async function loadCache() {
|
|
104
|
+
if (cache) return cache;
|
|
105
|
+
cache = /* @__PURE__ */ new Map();
|
|
106
|
+
const raw = await storage.getItem(blobKey);
|
|
107
|
+
if (raw != null) {
|
|
108
|
+
const blob = raw;
|
|
109
|
+
for (const [id, entry] of Object.entries(blob.entries)) {
|
|
110
|
+
const cached = {
|
|
111
|
+
data: entry.data,
|
|
112
|
+
embedding: decodeEmbedding(entry.emb)
|
|
113
|
+
};
|
|
114
|
+
if (entry.meta) cached.metadata = entry.meta;
|
|
115
|
+
cache.set(id, cached);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return cache;
|
|
119
|
+
}
|
|
120
|
+
async function flushCache() {
|
|
121
|
+
if (!cache) return;
|
|
122
|
+
const entries = {};
|
|
123
|
+
for (const [id, entry] of cache) entries[id] = {
|
|
124
|
+
data: entry.data,
|
|
125
|
+
...entry.metadata ? { meta: entry.metadata } : {},
|
|
126
|
+
emb: encodeEmbedding(entry.embedding)
|
|
127
|
+
};
|
|
128
|
+
const blob = {
|
|
129
|
+
v: 1,
|
|
130
|
+
d: dimensions,
|
|
131
|
+
entries
|
|
132
|
+
};
|
|
133
|
+
await storage.setItem(blobKey, blob);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
async upsert(id, data, metadata) {
|
|
137
|
+
const c = await loadCache();
|
|
138
|
+
const vector = await embedFn(data);
|
|
139
|
+
const entry = {
|
|
140
|
+
data,
|
|
141
|
+
embedding: new Float32Array(vector)
|
|
142
|
+
};
|
|
143
|
+
if (metadata) entry.metadata = metadata;
|
|
144
|
+
c.set(id, entry);
|
|
145
|
+
await flushCache();
|
|
146
|
+
},
|
|
147
|
+
async query(text, queryOptions) {
|
|
148
|
+
const topK = queryOptions?.topK ?? 10;
|
|
149
|
+
if (queryOptions?.filter) throw new Error("Metadata filter is not supported by the unstorage vector store");
|
|
150
|
+
if (!text.trim()) return [];
|
|
151
|
+
const c = await loadCache();
|
|
152
|
+
const queryVec = new Float32Array(await embedFn(text));
|
|
153
|
+
const scored = [];
|
|
154
|
+
for (const [id, entry] of c) scored.push({
|
|
155
|
+
id,
|
|
156
|
+
score: cosineSimilarity(queryVec, entry.embedding),
|
|
157
|
+
data: entry.data,
|
|
158
|
+
metadata: entry.metadata
|
|
159
|
+
});
|
|
160
|
+
scored.sort((a, b) => b.score - a.score);
|
|
161
|
+
return scored.slice(0, topK);
|
|
162
|
+
},
|
|
163
|
+
async delete(ids) {
|
|
164
|
+
const c = await loadCache();
|
|
165
|
+
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
166
|
+
for (const id of idArray) c.delete(id);
|
|
167
|
+
await flushCache();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
export { createTestEmbedFn as n, createUnstorageVectorStore as t };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector store backed by unstorage.
|
|
3
|
+
*
|
|
4
|
+
* Stores all vectors for a scope in a single blob (JSON with base64-encoded
|
|
5
|
+
* embeddings). Works with any unstorage driver. Brute-force cosine similarity
|
|
6
|
+
* — fast for <10k vectors.
|
|
7
|
+
*/
|
|
8
|
+
import type { Storage } from "unstorage";
|
|
9
|
+
import { type EmbedFn } from "./_embeddings.ts";
|
|
10
|
+
import type { VectorStore } from "./vector.ts";
|
|
11
|
+
export { createTestEmbedFn, type EmbedFn } from "./_embeddings.ts";
|
|
12
|
+
/**
|
|
13
|
+
* Options for creating an unstorage-backed vector store.
|
|
14
|
+
*/
|
|
15
|
+
export type UnstorageVectorOptions = {
|
|
16
|
+
/** Configured unstorage Storage instance. */
|
|
17
|
+
storage: Storage;
|
|
18
|
+
/** Key for the vector blob in storage. Defaults to `"vectors.json"`. */
|
|
19
|
+
blobKey?: string;
|
|
20
|
+
/** Custom embedding function. Defaults to local `all-MiniLM-L6-v2` model. */
|
|
21
|
+
embedFn?: EmbedFn;
|
|
22
|
+
/** Embedding dimensions. Must match the embedFn output. Defaults to 384. */
|
|
23
|
+
dimensions?: number;
|
|
24
|
+
/** Directory for caching downloaded models. Defaults to `.aai/models`. */
|
|
25
|
+
modelCacheDir?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Create a vector store backed by any unstorage driver.
|
|
29
|
+
*
|
|
30
|
+
* All vectors for the scope are stored in a single blob. The blob is loaded
|
|
31
|
+
* lazily on first operation and cached in memory. Mutations are written
|
|
32
|
+
* through immediately.
|
|
33
|
+
*
|
|
34
|
+
* @param options - See {@link UnstorageVectorOptions}.
|
|
35
|
+
* @returns A {@link VectorStore} instance.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { createStorage } from "unstorage";
|
|
40
|
+
* import { createUnstorageVectorStore } from "@alexkroman1/aai/unstorage-vector";
|
|
41
|
+
*
|
|
42
|
+
* const vector = createUnstorageVectorStore({ storage: createStorage() });
|
|
43
|
+
* await vector.upsert("doc-1", "The capital of France is Paris.");
|
|
44
|
+
* const results = await vector.query("France capital");
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function createUnstorageVectorStore(options: UnstorageVectorOptions): VectorStore;
|
package/dist/vector.d.ts
CHANGED
|
@@ -34,8 +34,9 @@ export type VectorEntry = {
|
|
|
34
34
|
* Async vector store interface used by agents.
|
|
35
35
|
*
|
|
36
36
|
* Agents access the vector store via `ToolContext.vector` or
|
|
37
|
-
* `HookContext.vector`. Backed by
|
|
38
|
-
*
|
|
37
|
+
* `HookContext.vector`. Backed by unstorage with local embeddings
|
|
38
|
+
* via `all-MiniLM-L6-v2` by default. Pluggable storage driver and
|
|
39
|
+
* embedding function.
|
|
39
40
|
*
|
|
40
41
|
* @example
|
|
41
42
|
* ```ts
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { n as EMPTY_PARAMS } from "./_internal-types-IfPcaJd5.js";
|
|
2
|
+
import { n as errorDetail, r as errorMessage, s as toolError } from "./_utils-DgzpOMSV.js";
|
|
3
|
+
import { f as TOOL_EXECUTION_TIMEOUT_MS } from "./protocol-B-H2Q4ox.js";
|
|
4
|
+
//#region worker-entry.ts
|
|
5
|
+
/** Yield to the event loop so pending I/O (e.g. WebSocket frames) can be processed. */
|
|
6
|
+
const yieldTick = () => new Promise((r) => setTimeout(r, 0));
|
|
7
|
+
function buildToolContext(opts) {
|
|
8
|
+
const { env, state, kv, vector, messages, onUpdate, fetch: fetchFn, sessionId } = opts;
|
|
9
|
+
return {
|
|
10
|
+
env: { ...env },
|
|
11
|
+
state: state ?? {},
|
|
12
|
+
get kv() {
|
|
13
|
+
if (!kv) throw new Error("KV not available");
|
|
14
|
+
return kv;
|
|
15
|
+
},
|
|
16
|
+
get vector() {
|
|
17
|
+
if (!vector) throw new Error("Vector store not available");
|
|
18
|
+
return vector;
|
|
19
|
+
},
|
|
20
|
+
messages: messages ?? [],
|
|
21
|
+
sendUpdate(data) {
|
|
22
|
+
onUpdate?.(data);
|
|
23
|
+
},
|
|
24
|
+
fetch: fetchFn ?? globalThis.fetch,
|
|
25
|
+
sessionId: sessionId ?? ""
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Execute a tool call with argument validation and error handling.
|
|
30
|
+
*
|
|
31
|
+
* Validates the provided arguments against the tool's Zod parameter schema,
|
|
32
|
+
* constructs a {@link ToolContext}, invokes the tool's `execute` function,
|
|
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.
|
|
36
|
+
*
|
|
37
|
+
* @param name - The name of the tool being invoked.
|
|
38
|
+
* @param args - Raw arguments from the LLM to validate and pass to the tool.
|
|
39
|
+
* @param options - Tool definition, environment, and optional context.
|
|
40
|
+
* @returns The tool's result serialized as a string, or an error message.
|
|
41
|
+
*/
|
|
42
|
+
async function executeToolCall(name, args, options) {
|
|
43
|
+
const { tool } = options;
|
|
44
|
+
const parsed = (tool.parameters ?? EMPTY_PARAMS).safeParse(args);
|
|
45
|
+
if (!parsed.success) return toolError(`Invalid arguments for tool "${name}": ${(parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".")}: ${i.message}`).join(", ")}`);
|
|
46
|
+
let timer;
|
|
47
|
+
try {
|
|
48
|
+
const ctx = buildToolContext(options);
|
|
49
|
+
await yieldTick();
|
|
50
|
+
const timeout = new Promise((_, reject) => {
|
|
51
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Tool "${name}" timed out after ${TOOL_EXECUTION_TIMEOUT_MS}ms`)), TOOL_EXECUTION_TIMEOUT_MS);
|
|
52
|
+
});
|
|
53
|
+
const result = await Promise.race([Promise.resolve(tool.execute(parsed.data, ctx)), timeout]);
|
|
54
|
+
await yieldTick();
|
|
55
|
+
if (result == null) return "null";
|
|
56
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
57
|
+
} catch (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));
|
|
65
|
+
} finally {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { executeToolCall as t };
|
package/dist/worker-entry.js
CHANGED
|
@@ -1,70 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { errorDetail, errorMessage, toolError } from "./_utils.js";
|
|
3
|
-
import { TOOL_EXECUTION_TIMEOUT_MS } from "./protocol.js";
|
|
4
|
-
//#region worker-entry.ts
|
|
5
|
-
/** Yield to the event loop so pending I/O (e.g. WebSocket frames) can be processed. */
|
|
6
|
-
const yieldTick = () => new Promise((r) => setTimeout(r, 0));
|
|
7
|
-
function buildToolContext(opts) {
|
|
8
|
-
const { env, state, kv, vector, messages, onUpdate, fetch: fetchFn, sessionId } = opts;
|
|
9
|
-
return {
|
|
10
|
-
env: { ...env },
|
|
11
|
-
state: state ?? {},
|
|
12
|
-
get kv() {
|
|
13
|
-
if (!kv) throw new Error("KV not available");
|
|
14
|
-
return kv;
|
|
15
|
-
},
|
|
16
|
-
get vector() {
|
|
17
|
-
if (!vector) throw new Error("Vector store not available");
|
|
18
|
-
return vector;
|
|
19
|
-
},
|
|
20
|
-
messages: messages ?? [],
|
|
21
|
-
sendUpdate(data) {
|
|
22
|
-
onUpdate?.(data);
|
|
23
|
-
},
|
|
24
|
-
fetch: fetchFn ?? globalThis.fetch,
|
|
25
|
-
sessionId: sessionId ?? ""
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Execute a tool call with argument validation and error handling.
|
|
30
|
-
*
|
|
31
|
-
* Validates the provided arguments against the tool's Zod parameter schema,
|
|
32
|
-
* constructs a {@link ToolContext}, invokes the tool's `execute` function,
|
|
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.
|
|
36
|
-
*
|
|
37
|
-
* @param name - The name of the tool being invoked.
|
|
38
|
-
* @param args - Raw arguments from the LLM to validate and pass to the tool.
|
|
39
|
-
* @param options - Tool definition, environment, and optional context.
|
|
40
|
-
* @returns The tool's result serialized as a string, or an error message.
|
|
41
|
-
*/
|
|
42
|
-
async function executeToolCall(name, args, options) {
|
|
43
|
-
const { tool } = options;
|
|
44
|
-
const parsed = (tool.parameters ?? EMPTY_PARAMS).safeParse(args);
|
|
45
|
-
if (!parsed.success) return toolError(`Invalid arguments for tool "${name}": ${(parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".")}: ${i.message}`).join(", ")}`);
|
|
46
|
-
let timer;
|
|
47
|
-
try {
|
|
48
|
-
const ctx = buildToolContext(options);
|
|
49
|
-
await yieldTick();
|
|
50
|
-
const timeout = new Promise((_, reject) => {
|
|
51
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Tool "${name}" timed out after ${TOOL_EXECUTION_TIMEOUT_MS}ms`)), TOOL_EXECUTION_TIMEOUT_MS);
|
|
52
|
-
});
|
|
53
|
-
const result = await Promise.race([Promise.resolve(tool.execute(parsed.data, ctx)), timeout]);
|
|
54
|
-
await yieldTick();
|
|
55
|
-
if (result == null) return "null";
|
|
56
|
-
return typeof result === "string" ? result : JSON.stringify(result);
|
|
57
|
-
} catch (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));
|
|
65
|
-
} finally {
|
|
66
|
-
clearTimeout(timer);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
//#endregion
|
|
1
|
+
import { t as executeToolCall } from "./worker-entry-2jaiqIj0.js";
|
|
70
2
|
export { executeToolCall };
|