@radaros/core 0.1.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 (48) hide show
  1. package/dist/index.d.ts +887 -0
  2. package/dist/index.js +3462 -0
  3. package/package.json +64 -0
  4. package/src/agent/agent.ts +314 -0
  5. package/src/agent/llm-loop.ts +263 -0
  6. package/src/agent/run-context.ts +35 -0
  7. package/src/agent/types.ts +77 -0
  8. package/src/events/event-bus.ts +45 -0
  9. package/src/events/types.ts +16 -0
  10. package/src/guardrails/types.ts +5 -0
  11. package/src/hooks/types.ts +6 -0
  12. package/src/index.ts +111 -0
  13. package/src/knowledge/knowledge-base.ts +146 -0
  14. package/src/logger/logger.ts +232 -0
  15. package/src/memory/memory.ts +87 -0
  16. package/src/memory/types.ts +13 -0
  17. package/src/models/provider.ts +22 -0
  18. package/src/models/providers/anthropic.ts +330 -0
  19. package/src/models/providers/google.ts +361 -0
  20. package/src/models/providers/ollama.ts +211 -0
  21. package/src/models/providers/openai.ts +323 -0
  22. package/src/models/registry.ts +90 -0
  23. package/src/models/types.ts +112 -0
  24. package/src/session/session-manager.ts +75 -0
  25. package/src/session/types.ts +10 -0
  26. package/src/storage/driver.ts +10 -0
  27. package/src/storage/in-memory.ts +44 -0
  28. package/src/storage/mongodb.ts +70 -0
  29. package/src/storage/postgres.ts +81 -0
  30. package/src/storage/sqlite.ts +81 -0
  31. package/src/team/modes.ts +1 -0
  32. package/src/team/team.ts +323 -0
  33. package/src/team/types.ts +26 -0
  34. package/src/tools/define-tool.ts +20 -0
  35. package/src/tools/tool-executor.ts +131 -0
  36. package/src/tools/types.ts +27 -0
  37. package/src/vector/base.ts +44 -0
  38. package/src/vector/embeddings/google.ts +64 -0
  39. package/src/vector/embeddings/openai.ts +66 -0
  40. package/src/vector/in-memory.ts +115 -0
  41. package/src/vector/mongodb.ts +241 -0
  42. package/src/vector/pgvector.ts +169 -0
  43. package/src/vector/qdrant.ts +203 -0
  44. package/src/vector/types.ts +55 -0
  45. package/src/workflow/step-runner.ts +303 -0
  46. package/src/workflow/types.ts +55 -0
  47. package/src/workflow/workflow.ts +68 -0
  48. package/tsconfig.json +8 -0
@@ -0,0 +1,131 @@
1
+ import { createRequire } from "node:module";
2
+ import type { ToolCall } from "../models/types.js";
3
+ import type { RunContext } from "../agent/run-context.js";
4
+ import type { ToolDef, ToolCallResult } from "./types.js";
5
+
6
+ const _require = createRequire(import.meta.url);
7
+
8
+ export class ToolExecutor {
9
+ private tools: Map<string, ToolDef>;
10
+ private concurrency: number;
11
+
12
+ constructor(tools: ToolDef[], concurrency: number = 5) {
13
+ this.tools = new Map(tools.map((t) => [t.name, t]));
14
+ this.concurrency = concurrency;
15
+ }
16
+
17
+ async executeAll(
18
+ toolCalls: ToolCall[],
19
+ ctx: RunContext
20
+ ): Promise<ToolCallResult[]> {
21
+ const results: ToolCallResult[] = [];
22
+
23
+ for (let i = 0; i < toolCalls.length; i += this.concurrency) {
24
+ const batch = toolCalls.slice(i, i + this.concurrency);
25
+ const batchResults = await Promise.allSettled(
26
+ batch.map((tc) => this.executeSingle(tc, ctx))
27
+ );
28
+
29
+ for (let j = 0; j < batchResults.length; j++) {
30
+ const settled = batchResults[j];
31
+ const tc = batch[j];
32
+
33
+ if (settled.status === "fulfilled") {
34
+ results.push(settled.value);
35
+ } else {
36
+ results.push({
37
+ toolCallId: tc.id,
38
+ toolName: tc.name,
39
+ result: `Error: ${settled.reason?.message ?? "Unknown error"}`,
40
+ error: settled.reason?.message ?? "Unknown error",
41
+ });
42
+ }
43
+ }
44
+ }
45
+
46
+ return results;
47
+ }
48
+
49
+ private async executeSingle(
50
+ toolCall: ToolCall,
51
+ ctx: RunContext
52
+ ): Promise<ToolCallResult> {
53
+ const tool = this.tools.get(toolCall.name);
54
+ if (!tool) {
55
+ return {
56
+ toolCallId: toolCall.id,
57
+ toolName: toolCall.name,
58
+ result: `Error: Tool "${toolCall.name}" not found`,
59
+ error: `Tool "${toolCall.name}" not found`,
60
+ };
61
+ }
62
+
63
+ ctx.eventBus.emit("tool.call", {
64
+ runId: ctx.runId,
65
+ toolName: toolCall.name,
66
+ args: toolCall.arguments,
67
+ });
68
+
69
+ const parsed = tool.parameters.safeParse(toolCall.arguments);
70
+ if (!parsed.success) {
71
+ const errMsg = `Invalid arguments: ${parsed.error.message}`;
72
+ const result: ToolCallResult = {
73
+ toolCallId: toolCall.id,
74
+ toolName: toolCall.name,
75
+ result: errMsg,
76
+ error: errMsg,
77
+ };
78
+
79
+ ctx.eventBus.emit("tool.result", {
80
+ runId: ctx.runId,
81
+ toolName: toolCall.name,
82
+ result: errMsg,
83
+ });
84
+
85
+ return result;
86
+ }
87
+
88
+ const rawResult = await tool.execute(parsed.data, ctx);
89
+ const resultContent =
90
+ typeof rawResult === "string" ? rawResult : rawResult.content;
91
+
92
+ ctx.eventBus.emit("tool.result", {
93
+ runId: ctx.runId,
94
+ toolName: toolCall.name,
95
+ result: resultContent,
96
+ });
97
+
98
+ return {
99
+ toolCallId: toolCall.id,
100
+ toolName: toolCall.name,
101
+ result: rawResult,
102
+ };
103
+ }
104
+
105
+ getToolDefinitions(): Array<{
106
+ name: string;
107
+ description: string;
108
+ parameters: Record<string, unknown>;
109
+ }> {
110
+ const { zodToJsonSchema } = _require("zod-to-json-schema");
111
+ const defs: Array<{
112
+ name: string;
113
+ description: string;
114
+ parameters: Record<string, unknown>;
115
+ }> = [];
116
+
117
+ for (const tool of this.tools.values()) {
118
+ const jsonSchema = zodToJsonSchema(tool.parameters, {
119
+ target: "openApi3",
120
+ });
121
+
122
+ defs.push({
123
+ name: tool.name,
124
+ description: tool.description,
125
+ parameters: jsonSchema as Record<string, unknown>,
126
+ });
127
+ }
128
+
129
+ return defs;
130
+ }
131
+ }
@@ -0,0 +1,27 @@
1
+ import type { z } from "zod";
2
+ import type { RunContext } from "../agent/run-context.js";
3
+
4
+ export interface Artifact {
5
+ type: string;
6
+ data: unknown;
7
+ mimeType?: string;
8
+ }
9
+
10
+ export interface ToolResult {
11
+ content: string;
12
+ artifacts?: Artifact[];
13
+ }
14
+
15
+ export interface ToolDef {
16
+ name: string;
17
+ description: string;
18
+ parameters: z.ZodObject<any>;
19
+ execute: (args: Record<string, unknown>, ctx: RunContext) => Promise<string | ToolResult>;
20
+ }
21
+
22
+ export interface ToolCallResult {
23
+ toolCallId: string;
24
+ toolName: string;
25
+ result: string | ToolResult;
26
+ error?: string;
27
+ }
@@ -0,0 +1,44 @@
1
+ import type {
2
+ VectorStore,
3
+ VectorDocument,
4
+ VectorSearchResult,
5
+ VectorSearchOptions,
6
+ EmbeddingProvider,
7
+ } from "./types.js";
8
+
9
+ export abstract class BaseVectorStore implements VectorStore {
10
+ constructor(protected embedder?: EmbeddingProvider) {}
11
+
12
+ protected async ensureEmbedding(doc: VectorDocument): Promise<number[]> {
13
+ if (doc.embedding) return doc.embedding;
14
+ if (!this.embedder) {
15
+ throw new Error(
16
+ "No embedding provided on document and no EmbeddingProvider configured"
17
+ );
18
+ }
19
+ return this.embedder.embed(doc.content);
20
+ }
21
+
22
+ protected async ensureQueryVector(query: number[] | string): Promise<number[]> {
23
+ if (Array.isArray(query)) return query;
24
+ if (!this.embedder) {
25
+ throw new Error(
26
+ "String query requires an EmbeddingProvider to be configured"
27
+ );
28
+ }
29
+ return this.embedder.embed(query);
30
+ }
31
+
32
+ abstract initialize(): Promise<void>;
33
+ abstract upsert(collection: string, doc: VectorDocument): Promise<void>;
34
+ abstract upsertBatch(collection: string, docs: VectorDocument[]): Promise<void>;
35
+ abstract search(
36
+ collection: string,
37
+ query: number[] | string,
38
+ options?: VectorSearchOptions
39
+ ): Promise<VectorSearchResult[]>;
40
+ abstract delete(collection: string, id: string): Promise<void>;
41
+ abstract get(collection: string, id: string): Promise<VectorDocument | null>;
42
+ abstract dropCollection(collection: string): Promise<void>;
43
+ abstract close(): Promise<void>;
44
+ }
@@ -0,0 +1,64 @@
1
+ import { createRequire } from "node:module";
2
+ import type { EmbeddingProvider } from "../types.js";
3
+
4
+ const _require = createRequire(import.meta.url);
5
+
6
+ export interface GoogleEmbeddingConfig {
7
+ apiKey?: string;
8
+ model?: string;
9
+ dimensions?: number;
10
+ }
11
+
12
+ const MODEL_DIMENSIONS: Record<string, number> = {
13
+ "text-embedding-004": 768,
14
+ "embedding-001": 768,
15
+ };
16
+
17
+ export class GoogleEmbedding implements EmbeddingProvider {
18
+ readonly dimensions: number;
19
+ private ai: any;
20
+ private model: string;
21
+
22
+ constructor(config: GoogleEmbeddingConfig = {}) {
23
+ this.model = config.model ?? "text-embedding-004";
24
+ this.dimensions =
25
+ config.dimensions ?? MODEL_DIMENSIONS[this.model] ?? 768;
26
+
27
+ try {
28
+ const { GoogleGenAI } = _require("@google/genai");
29
+ this.ai = new GoogleGenAI({
30
+ apiKey: config.apiKey ?? process.env.GOOGLE_API_KEY,
31
+ });
32
+ } catch {
33
+ throw new Error(
34
+ "@google/genai is required for GoogleEmbedding. Install it: npm install @google/genai"
35
+ );
36
+ }
37
+ }
38
+
39
+ async embed(text: string): Promise<number[]> {
40
+ const result = await this.ai.models.embedContent({
41
+ model: this.model,
42
+ contents: text,
43
+ ...(this.dimensions !== MODEL_DIMENSIONS[this.model]
44
+ ? { config: { outputDimensionality: this.dimensions } }
45
+ : {}),
46
+ });
47
+ return result.embeddings[0].values;
48
+ }
49
+
50
+ async embedBatch(texts: string[]): Promise<number[][]> {
51
+ const results = await Promise.all(
52
+ texts.map((text) =>
53
+ this.ai.models.embedContent({
54
+ model: this.model,
55
+ contents: text,
56
+ ...(this.dimensions !== MODEL_DIMENSIONS[this.model]
57
+ ? { config: { outputDimensionality: this.dimensions } }
58
+ : {}),
59
+ })
60
+ )
61
+ );
62
+ return results.map((r: any) => r.embeddings[0].values);
63
+ }
64
+ }
@@ -0,0 +1,66 @@
1
+ import { createRequire } from "node:module";
2
+ import type { EmbeddingProvider } from "../types.js";
3
+
4
+ const _require = createRequire(import.meta.url);
5
+
6
+ export interface OpenAIEmbeddingConfig {
7
+ apiKey?: string;
8
+ baseURL?: string;
9
+ model?: string;
10
+ dimensions?: number;
11
+ }
12
+
13
+ const MODEL_DIMENSIONS: Record<string, number> = {
14
+ "text-embedding-3-small": 1536,
15
+ "text-embedding-3-large": 3072,
16
+ "text-embedding-ada-002": 1536,
17
+ };
18
+
19
+ export class OpenAIEmbedding implements EmbeddingProvider {
20
+ readonly dimensions: number;
21
+ private client: any;
22
+ private model: string;
23
+
24
+ constructor(config: OpenAIEmbeddingConfig = {}) {
25
+ this.model = config.model ?? "text-embedding-3-small";
26
+ this.dimensions =
27
+ config.dimensions ?? MODEL_DIMENSIONS[this.model] ?? 1536;
28
+
29
+ try {
30
+ const mod = _require("openai");
31
+ const OpenAI = mod.default ?? mod;
32
+ this.client = new OpenAI({
33
+ apiKey: config.apiKey ?? process.env.OPENAI_API_KEY,
34
+ baseURL: config.baseURL,
35
+ });
36
+ } catch {
37
+ throw new Error(
38
+ "openai package is required for OpenAIEmbedding. Install it: npm install openai"
39
+ );
40
+ }
41
+ }
42
+
43
+ async embed(text: string): Promise<number[]> {
44
+ const response = await this.client.embeddings.create({
45
+ model: this.model,
46
+ input: text,
47
+ ...(this.dimensions !== MODEL_DIMENSIONS[this.model]
48
+ ? { dimensions: this.dimensions }
49
+ : {}),
50
+ });
51
+ return response.data[0].embedding;
52
+ }
53
+
54
+ async embedBatch(texts: string[]): Promise<number[][]> {
55
+ const response = await this.client.embeddings.create({
56
+ model: this.model,
57
+ input: texts,
58
+ ...(this.dimensions !== MODEL_DIMENSIONS[this.model]
59
+ ? { dimensions: this.dimensions }
60
+ : {}),
61
+ });
62
+ return response.data
63
+ .sort((a: any, b: any) => a.index - b.index)
64
+ .map((d: any) => d.embedding);
65
+ }
66
+ }
@@ -0,0 +1,115 @@
1
+ import { BaseVectorStore } from "./base.js";
2
+ import type {
3
+ VectorDocument,
4
+ VectorSearchResult,
5
+ VectorSearchOptions,
6
+ EmbeddingProvider,
7
+ } from "./types.js";
8
+
9
+ interface StoredDoc {
10
+ id: string;
11
+ content: string;
12
+ embedding: number[];
13
+ metadata: Record<string, unknown>;
14
+ }
15
+
16
+ export class InMemoryVectorStore extends BaseVectorStore {
17
+ private collections = new Map<string, Map<string, StoredDoc>>();
18
+
19
+ constructor(embedder?: EmbeddingProvider) {
20
+ super(embedder);
21
+ }
22
+
23
+ async initialize(): Promise<void> {}
24
+
25
+ private getCol(collection: string): Map<string, StoredDoc> {
26
+ let col = this.collections.get(collection);
27
+ if (!col) {
28
+ col = new Map();
29
+ this.collections.set(collection, col);
30
+ }
31
+ return col;
32
+ }
33
+
34
+ async upsert(collection: string, doc: VectorDocument): Promise<void> {
35
+ const embedding = await this.ensureEmbedding(doc);
36
+ this.getCol(collection).set(doc.id, {
37
+ id: doc.id,
38
+ content: doc.content,
39
+ embedding,
40
+ metadata: doc.metadata ?? {},
41
+ });
42
+ }
43
+
44
+ async upsertBatch(collection: string, docs: VectorDocument[]): Promise<void> {
45
+ for (const doc of docs) {
46
+ await this.upsert(collection, doc);
47
+ }
48
+ }
49
+
50
+ async search(
51
+ collection: string,
52
+ query: number[] | string,
53
+ options?: VectorSearchOptions
54
+ ): Promise<VectorSearchResult[]> {
55
+ const vec = await this.ensureQueryVector(query);
56
+ const topK = options?.topK ?? 10;
57
+ const col = this.getCol(collection);
58
+
59
+ const scored: VectorSearchResult[] = [];
60
+ for (const doc of col.values()) {
61
+ const score = this.cosineSimilarity(vec, doc.embedding);
62
+ if (options?.minScore != null && score < options.minScore) continue;
63
+ if (options?.filter) {
64
+ let match = true;
65
+ for (const [k, v] of Object.entries(options.filter)) {
66
+ if (doc.metadata[k] !== v) {
67
+ match = false;
68
+ break;
69
+ }
70
+ }
71
+ if (!match) continue;
72
+ }
73
+ scored.push({
74
+ id: doc.id,
75
+ content: doc.content,
76
+ score,
77
+ metadata: doc.metadata,
78
+ });
79
+ }
80
+
81
+ scored.sort((a, b) => b.score - a.score);
82
+ return scored.slice(0, topK);
83
+ }
84
+
85
+ private cosineSimilarity(a: number[], b: number[]): number {
86
+ let dot = 0;
87
+ let normA = 0;
88
+ let normB = 0;
89
+ for (let i = 0; i < a.length; i++) {
90
+ dot += a[i] * b[i];
91
+ normA += a[i] * a[i];
92
+ normB += b[i] * b[i];
93
+ }
94
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
95
+ return denom === 0 ? 0 : dot / denom;
96
+ }
97
+
98
+ async delete(collection: string, id: string): Promise<void> {
99
+ this.getCol(collection).delete(id);
100
+ }
101
+
102
+ async get(collection: string, id: string): Promise<VectorDocument | null> {
103
+ const doc = this.getCol(collection).get(id);
104
+ if (!doc) return null;
105
+ return { id: doc.id, content: doc.content, metadata: doc.metadata };
106
+ }
107
+
108
+ async dropCollection(collection: string): Promise<void> {
109
+ this.collections.delete(collection);
110
+ }
111
+
112
+ async close(): Promise<void> {
113
+ this.collections.clear();
114
+ }
115
+ }
@@ -0,0 +1,241 @@
1
+ import { createRequire } from "node:module";
2
+ import { BaseVectorStore } from "./base.js";
3
+ import type {
4
+ VectorDocument,
5
+ VectorSearchResult,
6
+ VectorSearchOptions,
7
+ EmbeddingProvider,
8
+ } from "./types.js";
9
+
10
+ const _require = createRequire(import.meta.url);
11
+
12
+ export interface MongoDBVectorConfig {
13
+ uri: string;
14
+ dbName?: string;
15
+ /** Atlas Search index name (must be pre-created for $vectorSearch). Defaults to "vector_index". */
16
+ indexName?: string;
17
+ }
18
+
19
+ export class MongoDBVectorStore extends BaseVectorStore {
20
+ private client: any;
21
+ private db: any;
22
+ private indexName: string;
23
+ private dbName: string;
24
+ private useAtlas: boolean | null = null;
25
+
26
+ constructor(config: MongoDBVectorConfig, embedder?: EmbeddingProvider) {
27
+ super(embedder);
28
+ this.indexName = config.indexName ?? "vector_index";
29
+ this.dbName = config.dbName ?? "radaros_vectors";
30
+ try {
31
+ const { MongoClient } = _require("mongodb");
32
+ this.client = new MongoClient(config.uri);
33
+ } catch {
34
+ throw new Error(
35
+ "mongodb is required for MongoDBVectorStore. Install it: npm install mongodb"
36
+ );
37
+ }
38
+ }
39
+
40
+ async initialize(): Promise<void> {
41
+ await this.client.connect();
42
+ this.db = this.client.db(this.dbName);
43
+ }
44
+
45
+ private col(collection: string) {
46
+ return this.db.collection(collection);
47
+ }
48
+
49
+ async upsert(collection: string, doc: VectorDocument): Promise<void> {
50
+ const embedding = await this.ensureEmbedding(doc);
51
+ await this.col(collection).updateOne(
52
+ { _id: doc.id },
53
+ {
54
+ $set: {
55
+ content: doc.content,
56
+ embedding,
57
+ metadata: doc.metadata ?? {},
58
+ updatedAt: new Date(),
59
+ },
60
+ },
61
+ { upsert: true }
62
+ );
63
+ }
64
+
65
+ async upsertBatch(collection: string, docs: VectorDocument[]): Promise<void> {
66
+ const ops = await Promise.all(
67
+ docs.map(async (doc) => {
68
+ const embedding = await this.ensureEmbedding(doc);
69
+ return {
70
+ updateOne: {
71
+ filter: { _id: doc.id },
72
+ update: {
73
+ $set: {
74
+ content: doc.content,
75
+ embedding,
76
+ metadata: doc.metadata ?? {},
77
+ updatedAt: new Date(),
78
+ },
79
+ },
80
+ upsert: true,
81
+ },
82
+ };
83
+ })
84
+ );
85
+ if (ops.length > 0) {
86
+ await this.col(collection).bulkWrite(ops);
87
+ }
88
+ }
89
+
90
+ async search(
91
+ collection: string,
92
+ query: number[] | string,
93
+ options?: VectorSearchOptions
94
+ ): Promise<VectorSearchResult[]> {
95
+ const vec = await this.ensureQueryVector(query);
96
+
97
+ if (this.useAtlas === true) {
98
+ return this.atlasSearch(collection, vec, options);
99
+ }
100
+
101
+ if (this.useAtlas === false) {
102
+ return this.localSearch(collection, vec, options);
103
+ }
104
+
105
+ // First call: auto-detect Atlas support
106
+ try {
107
+ const results = await this.atlasSearch(collection, vec, options);
108
+ this.useAtlas = true;
109
+ return results;
110
+ } catch {
111
+ this.useAtlas = false;
112
+ return this.localSearch(collection, vec, options);
113
+ }
114
+ }
115
+
116
+ private async atlasSearch(
117
+ collection: string,
118
+ vec: number[],
119
+ options?: VectorSearchOptions
120
+ ): Promise<VectorSearchResult[]> {
121
+ const topK = options?.topK ?? 10;
122
+
123
+ const pipeline: Record<string, unknown>[] = [
124
+ {
125
+ $vectorSearch: {
126
+ index: this.indexName,
127
+ path: "embedding",
128
+ queryVector: vec,
129
+ numCandidates: topK * 10,
130
+ limit: topK,
131
+ ...(options?.filter ? { filter: this.buildFilter(options.filter) } : {}),
132
+ },
133
+ },
134
+ {
135
+ $addFields: {
136
+ score: { $meta: "vectorSearchScore" },
137
+ },
138
+ },
139
+ ];
140
+
141
+ if (options?.minScore != null) {
142
+ pipeline.push({ $match: { score: { $gte: options.minScore } } });
143
+ }
144
+
145
+ pipeline.push({
146
+ $project: { _id: 1, content: 1, score: 1, metadata: 1 },
147
+ });
148
+
149
+ const results = await this.col(collection).aggregate(pipeline).toArray();
150
+
151
+ return results.map((r: any) => ({
152
+ id: String(r._id),
153
+ content: r.content ?? "",
154
+ score: r.score,
155
+ metadata: r.metadata,
156
+ }));
157
+ }
158
+
159
+ private async localSearch(
160
+ collection: string,
161
+ vec: number[],
162
+ options?: VectorSearchOptions
163
+ ): Promise<VectorSearchResult[]> {
164
+ const topK = options?.topK ?? 10;
165
+ const filter: Record<string, unknown> = {};
166
+ if (options?.filter) {
167
+ for (const [k, v] of Object.entries(options.filter)) {
168
+ filter[`metadata.${k}`] = v;
169
+ }
170
+ }
171
+
172
+ const docs = await this.col(collection)
173
+ .find(filter, { projection: { _id: 1, content: 1, embedding: 1, metadata: 1 } })
174
+ .toArray();
175
+
176
+ const scored: VectorSearchResult[] = [];
177
+ for (const doc of docs) {
178
+ if (!doc.embedding) continue;
179
+ const score = cosine(vec, doc.embedding);
180
+ if (options?.minScore != null && score < options.minScore) continue;
181
+ scored.push({
182
+ id: String(doc._id),
183
+ content: doc.content ?? "",
184
+ score,
185
+ metadata: doc.metadata,
186
+ });
187
+ }
188
+
189
+ scored.sort((a, b) => b.score - a.score);
190
+ return scored.slice(0, topK);
191
+ }
192
+
193
+ private buildFilter(
194
+ filter: Record<string, unknown>
195
+ ): Record<string, unknown> {
196
+ const conditions: Record<string, unknown> = {};
197
+ for (const [key, value] of Object.entries(filter)) {
198
+ conditions[`metadata.${key}`] = value;
199
+ }
200
+ return conditions;
201
+ }
202
+
203
+ async delete(collection: string, id: string): Promise<void> {
204
+ await this.col(collection).deleteOne({ _id: id });
205
+ }
206
+
207
+ async get(collection: string, id: string): Promise<VectorDocument | null> {
208
+ const doc = await this.col(collection).findOne({ _id: id });
209
+ if (!doc) return null;
210
+ return {
211
+ id: String(doc._id),
212
+ content: doc.content,
213
+ metadata: doc.metadata,
214
+ };
215
+ }
216
+
217
+ async dropCollection(collection: string): Promise<void> {
218
+ try {
219
+ await this.col(collection).drop();
220
+ } catch {
221
+ // collection may not exist
222
+ }
223
+ }
224
+
225
+ async close(): Promise<void> {
226
+ await this.client.close();
227
+ }
228
+ }
229
+
230
+ function cosine(a: number[], b: number[]): number {
231
+ let dot = 0;
232
+ let normA = 0;
233
+ let normB = 0;
234
+ for (let i = 0; i < a.length; i++) {
235
+ dot += a[i] * b[i];
236
+ normA += a[i] * a[i];
237
+ normB += b[i] * b[i];
238
+ }
239
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
240
+ return denom === 0 ? 0 : dot / denom;
241
+ }