@agentuity/local 3.0.0-alpha.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.
- package/dist/bun/db.d.ts +4 -0
- package/dist/bun/db.d.ts.map +1 -0
- package/dist/bun/db.js +281 -0
- package/dist/bun/db.js.map +1 -0
- package/dist/bun/email.d.ts +24 -0
- package/dist/bun/email.d.ts.map +1 -0
- package/dist/bun/email.js +58 -0
- package/dist/bun/email.js.map +1 -0
- package/dist/bun/index.d.ts +14 -0
- package/dist/bun/index.d.ts.map +1 -0
- package/dist/bun/index.js +14 -0
- package/dist/bun/index.js.map +1 -0
- package/dist/bun/kv.d.ts +17 -0
- package/dist/bun/kv.d.ts.map +1 -0
- package/dist/bun/kv.js +133 -0
- package/dist/bun/kv.js.map +1 -0
- package/dist/bun/queue.d.ts +10 -0
- package/dist/bun/queue.d.ts.map +1 -0
- package/dist/bun/queue.js +96 -0
- package/dist/bun/queue.js.map +1 -0
- package/dist/bun/stream.d.ts +12 -0
- package/dist/bun/stream.d.ts.map +1 -0
- package/dist/bun/stream.js +266 -0
- package/dist/bun/stream.js.map +1 -0
- package/dist/bun/task.d.ts +55 -0
- package/dist/bun/task.d.ts.map +1 -0
- package/dist/bun/task.js +1248 -0
- package/dist/bun/task.js.map +1 -0
- package/dist/bun/util.d.ts +18 -0
- package/dist/bun/util.d.ts.map +1 -0
- package/dist/bun/util.js +44 -0
- package/dist/bun/util.js.map +1 -0
- package/dist/bun/vector.d.ts +17 -0
- package/dist/bun/vector.d.ts.map +1 -0
- package/dist/bun/vector.js +303 -0
- package/dist/bun/vector.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +20 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +44 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +42 -0
- package/src/bun/db.ts +353 -0
- package/src/bun/email.ts +91 -0
- package/src/bun/index.ts +14 -0
- package/src/bun/kv.ts +174 -0
- package/src/bun/queue.ts +145 -0
- package/src/bun/stream.ts +358 -0
- package/src/bun/task.ts +1711 -0
- package/src/bun/util.ts +55 -0
- package/src/bun/vector.ts +438 -0
- package/src/index.ts +36 -0
- package/src/runtime.ts +56 -0
package/src/bun/util.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StructuredError } from '@agentuity/core';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a project path to an absolute path for consistent DB keys
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeProjectPath(cwd: string = process.cwd()): string {
|
|
8
|
+
return resolve(cwd);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Simple character-based embedding for local vector search
|
|
13
|
+
* Not production-quality, but good enough for local dev/testing
|
|
14
|
+
*/
|
|
15
|
+
export function simpleEmbedding(text: string, dimensions = 128): number[] {
|
|
16
|
+
const vec = new Array(dimensions).fill(0);
|
|
17
|
+
const normalized = text.toLowerCase();
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
20
|
+
const charCode = normalized.charCodeAt(i);
|
|
21
|
+
vec[i % dimensions] += Math.sin(charCode * (i + 1));
|
|
22
|
+
vec[(i * 2) % dimensions] += Math.cos(charCode);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Normalize vector
|
|
26
|
+
const magnitude = Math.sqrt(vec.reduce((sum, v) => sum + v * v, 0));
|
|
27
|
+
return magnitude > 0 ? vec.map((v) => v / magnitude) : vec;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const InvalidVectorError = StructuredError(
|
|
31
|
+
'InvalidVectorError',
|
|
32
|
+
'Vectors must have the same dimension'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Calculate cosine similarity between two vectors
|
|
37
|
+
*/
|
|
38
|
+
export function cosineSimilarity(a: number[], b: number[]): number {
|
|
39
|
+
if (a.length !== b.length) {
|
|
40
|
+
throw new InvalidVectorError();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const dot = a.reduce((sum, ai, i) => sum + ai * (b[i] ?? 0), 0);
|
|
44
|
+
const normA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
|
|
45
|
+
const normB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
|
|
46
|
+
|
|
47
|
+
return normA > 0 && normB > 0 ? dot / (normA * normB) : 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get current timestamp in milliseconds
|
|
52
|
+
*/
|
|
53
|
+
export function now(): number {
|
|
54
|
+
return Date.now();
|
|
55
|
+
}
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import type {
|
|
3
|
+
VectorStorage,
|
|
4
|
+
VectorUpsertParams,
|
|
5
|
+
VectorUpsertResult,
|
|
6
|
+
VectorResult,
|
|
7
|
+
VectorResultNotFound,
|
|
8
|
+
VectorSearchResultWithDocument,
|
|
9
|
+
VectorSearchParams,
|
|
10
|
+
VectorSearchResult,
|
|
11
|
+
VectorNamespaceStats,
|
|
12
|
+
VectorNamespaceStatsWithSamples,
|
|
13
|
+
VectorGetAllStatsParams,
|
|
14
|
+
VectorStatsPaginated,
|
|
15
|
+
} from '@agentuity/core';
|
|
16
|
+
import { now, simpleEmbedding, cosineSimilarity } from './util';
|
|
17
|
+
import { randomUUID } from 'node:crypto';
|
|
18
|
+
|
|
19
|
+
export class LocalVectorStorage implements VectorStorage {
|
|
20
|
+
#db: Database;
|
|
21
|
+
#projectPath: string;
|
|
22
|
+
|
|
23
|
+
constructor(db: Database, projectPath: string) {
|
|
24
|
+
this.#db = db;
|
|
25
|
+
this.#projectPath = projectPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async upsert(name: string, ...documents: VectorUpsertParams[]): Promise<VectorUpsertResult[]> {
|
|
29
|
+
if (!name?.trim()) {
|
|
30
|
+
throw new Error('Vector storage name is required');
|
|
31
|
+
}
|
|
32
|
+
if (documents.length === 0) {
|
|
33
|
+
throw new Error('At least one document is required');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const results: VectorUpsertResult[] = [];
|
|
37
|
+
const stmt = this.#db.prepare(`
|
|
38
|
+
INSERT INTO vector_storage (
|
|
39
|
+
project_path, name, id, key, embedding, document, metadata, created_at, updated_at
|
|
40
|
+
)
|
|
41
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
42
|
+
ON CONFLICT(project_path, name, key)
|
|
43
|
+
DO UPDATE SET
|
|
44
|
+
embedding = excluded.embedding,
|
|
45
|
+
document = excluded.document,
|
|
46
|
+
metadata = excluded.metadata,
|
|
47
|
+
updated_at = excluded.updated_at
|
|
48
|
+
`);
|
|
49
|
+
|
|
50
|
+
for (const doc of documents) {
|
|
51
|
+
if (!doc.key?.trim()) {
|
|
52
|
+
throw new Error('Each document must have a non-empty key');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Generate or use provided embeddings
|
|
56
|
+
let embedding: number[];
|
|
57
|
+
if ('embeddings' in doc && doc.embeddings) {
|
|
58
|
+
if (!Array.isArray(doc.embeddings) || doc.embeddings.length === 0) {
|
|
59
|
+
throw new Error('Embeddings must be a non-empty array');
|
|
60
|
+
}
|
|
61
|
+
embedding = doc.embeddings;
|
|
62
|
+
} else if ('document' in doc && doc.document) {
|
|
63
|
+
if (!doc.document?.trim()) {
|
|
64
|
+
throw new Error('Document text must be non-empty');
|
|
65
|
+
}
|
|
66
|
+
embedding = simpleEmbedding(doc.document);
|
|
67
|
+
} else {
|
|
68
|
+
throw new Error('Each document must have either embeddings or document text');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const id = randomUUID();
|
|
72
|
+
const timestamp = now();
|
|
73
|
+
const embeddingJson = JSON.stringify(embedding);
|
|
74
|
+
const documentText = 'document' in doc ? doc.document : null;
|
|
75
|
+
const metadata = doc.metadata ? JSON.stringify(doc.metadata) : null;
|
|
76
|
+
|
|
77
|
+
stmt.run(
|
|
78
|
+
this.#projectPath,
|
|
79
|
+
name,
|
|
80
|
+
id,
|
|
81
|
+
doc.key,
|
|
82
|
+
embeddingJson,
|
|
83
|
+
documentText ?? null,
|
|
84
|
+
metadata ?? null,
|
|
85
|
+
timestamp,
|
|
86
|
+
timestamp
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const row = this.#db
|
|
90
|
+
.prepare(
|
|
91
|
+
'SELECT id FROM vector_storage WHERE project_path = ? AND name = ? AND key = ?'
|
|
92
|
+
)
|
|
93
|
+
.get(this.#projectPath, name, doc.key) as { id: string } | undefined;
|
|
94
|
+
|
|
95
|
+
const actualId = row?.id ?? id;
|
|
96
|
+
results.push({ key: doc.key, id: actualId });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async get<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
103
|
+
name: string,
|
|
104
|
+
key: string
|
|
105
|
+
): Promise<VectorResult<T>> {
|
|
106
|
+
if (!name?.trim() || !key?.trim()) {
|
|
107
|
+
throw new Error('Vector storage name and key are required');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const query = this.#db.query(`
|
|
111
|
+
SELECT id, key, embedding, document, metadata
|
|
112
|
+
FROM vector_storage
|
|
113
|
+
WHERE project_path = ? AND name = ? AND key = ?
|
|
114
|
+
`);
|
|
115
|
+
|
|
116
|
+
const row = query.get(this.#projectPath, name, key) as {
|
|
117
|
+
id: string;
|
|
118
|
+
key: string;
|
|
119
|
+
embedding: string;
|
|
120
|
+
document: string | null;
|
|
121
|
+
metadata: string | null;
|
|
122
|
+
} | null;
|
|
123
|
+
|
|
124
|
+
if (!row) {
|
|
125
|
+
return { exists: false } as VectorResultNotFound;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
exists: true,
|
|
130
|
+
data: {
|
|
131
|
+
id: row.id,
|
|
132
|
+
key: row.key,
|
|
133
|
+
embeddings: JSON.parse(row.embedding),
|
|
134
|
+
document: row.document || undefined,
|
|
135
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
136
|
+
similarity: 1.0, // Perfect match for direct get
|
|
137
|
+
} as VectorSearchResultWithDocument<T>,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async getMany<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
142
|
+
name: string,
|
|
143
|
+
...keys: string[]
|
|
144
|
+
): Promise<Map<string, VectorSearchResultWithDocument<T>>> {
|
|
145
|
+
if (!name?.trim()) {
|
|
146
|
+
throw new Error('Vector storage name is required');
|
|
147
|
+
}
|
|
148
|
+
if (keys.length === 0) {
|
|
149
|
+
return new Map();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const results = await Promise.all(
|
|
153
|
+
keys.map(async (key) => {
|
|
154
|
+
const result = await this.get<T>(name, key);
|
|
155
|
+
return { key, result };
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const map = new Map<string, VectorSearchResultWithDocument<T>>();
|
|
160
|
+
for (const { key, result } of results) {
|
|
161
|
+
if (result.exists) {
|
|
162
|
+
map.set(key, result.data);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return map;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async search<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
170
|
+
name: string,
|
|
171
|
+
params: VectorSearchParams<T>
|
|
172
|
+
): Promise<VectorSearchResult<T>[]> {
|
|
173
|
+
if (!name?.trim()) {
|
|
174
|
+
throw new Error('Vector storage name is required');
|
|
175
|
+
}
|
|
176
|
+
if (!params.query?.trim()) {
|
|
177
|
+
throw new Error('Query is required');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Fetch all vectors for this name
|
|
181
|
+
const query = this.#db.query(`
|
|
182
|
+
SELECT id, key, embedding, metadata
|
|
183
|
+
FROM vector_storage
|
|
184
|
+
WHERE project_path = ? AND name = ?
|
|
185
|
+
`);
|
|
186
|
+
|
|
187
|
+
const rows = query.all(this.#projectPath, name) as Array<{
|
|
188
|
+
id: string;
|
|
189
|
+
key: string;
|
|
190
|
+
embedding: string;
|
|
191
|
+
metadata: string | null;
|
|
192
|
+
}>;
|
|
193
|
+
|
|
194
|
+
// If no vectors exist, return empty results
|
|
195
|
+
const row = rows[0];
|
|
196
|
+
if (!row) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Detect dimensionality from first stored vector
|
|
201
|
+
const firstEmbedding = JSON.parse(row.embedding);
|
|
202
|
+
const dimensions = firstEmbedding.length;
|
|
203
|
+
|
|
204
|
+
// Generate query embedding with matching dimensions
|
|
205
|
+
const queryEmbedding = simpleEmbedding(params.query, dimensions);
|
|
206
|
+
|
|
207
|
+
// Calculate similarities
|
|
208
|
+
const results: Array<VectorSearchResult<T> & { similarity: number }> = [];
|
|
209
|
+
|
|
210
|
+
for (const row of rows) {
|
|
211
|
+
const embedding = JSON.parse(row.embedding);
|
|
212
|
+
const similarity = cosineSimilarity(queryEmbedding, embedding);
|
|
213
|
+
|
|
214
|
+
// Apply similarity threshold
|
|
215
|
+
if (params.similarity !== undefined && similarity < params.similarity) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Apply metadata filter
|
|
220
|
+
if (params.metadata) {
|
|
221
|
+
const rowMetadata = row.metadata ? JSON.parse(row.metadata) : {};
|
|
222
|
+
const matches = Object.entries(params.metadata).every(
|
|
223
|
+
([key, value]) => rowMetadata[key] === value
|
|
224
|
+
);
|
|
225
|
+
if (!matches) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
results.push({
|
|
231
|
+
id: row.id,
|
|
232
|
+
key: row.key,
|
|
233
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
234
|
+
similarity,
|
|
235
|
+
} as VectorSearchResult<T> & { similarity: number });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Sort by similarity descending
|
|
239
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
240
|
+
|
|
241
|
+
// Apply limit
|
|
242
|
+
const limit = params.limit || 10;
|
|
243
|
+
return results.slice(0, limit);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async delete(name: string, ...keys: string[]): Promise<number> {
|
|
247
|
+
if (!name?.trim()) {
|
|
248
|
+
throw new Error('Vector storage name is required');
|
|
249
|
+
}
|
|
250
|
+
if (keys.length === 0) {
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
255
|
+
const stmt = this.#db.prepare(`
|
|
256
|
+
DELETE FROM vector_storage
|
|
257
|
+
WHERE project_path = ? AND name = ? AND key IN (${placeholders})
|
|
258
|
+
`);
|
|
259
|
+
|
|
260
|
+
const result = stmt.run(this.#projectPath, name, ...keys);
|
|
261
|
+
return result.changes;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async exists(name: string): Promise<boolean> {
|
|
265
|
+
if (!name?.trim()) {
|
|
266
|
+
throw new Error('Vector storage name is required');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const query = this.#db.query(`
|
|
270
|
+
SELECT COUNT(*) as count
|
|
271
|
+
FROM vector_storage
|
|
272
|
+
WHERE project_path = ? AND name = ?
|
|
273
|
+
`);
|
|
274
|
+
|
|
275
|
+
const { count } = query.get(this.#projectPath, name) as { count: number };
|
|
276
|
+
return count > 0;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async getStats(name: string): Promise<VectorNamespaceStatsWithSamples> {
|
|
280
|
+
if (!name?.trim()) {
|
|
281
|
+
throw new Error('Vector storage name is required');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const countQuery = this.#db.query(`
|
|
285
|
+
SELECT COUNT(*) as count,
|
|
286
|
+
MIN(created_at) as created_at, MAX(updated_at) as last_used
|
|
287
|
+
FROM vector_storage
|
|
288
|
+
WHERE project_path = ? AND name = ?
|
|
289
|
+
`);
|
|
290
|
+
|
|
291
|
+
const stats = countQuery.get(this.#projectPath, name) as {
|
|
292
|
+
count: number;
|
|
293
|
+
created_at: number | null;
|
|
294
|
+
last_used: number | null;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (stats.count === 0) {
|
|
298
|
+
return { sum: 0, count: 0 };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const sampleQuery = this.#db.query(`
|
|
302
|
+
SELECT key, embedding, document, metadata, created_at, updated_at
|
|
303
|
+
FROM vector_storage
|
|
304
|
+
WHERE project_path = ? AND name = ?
|
|
305
|
+
LIMIT 20
|
|
306
|
+
`);
|
|
307
|
+
|
|
308
|
+
const samples = sampleQuery.all(this.#projectPath, name) as Array<{
|
|
309
|
+
key: string;
|
|
310
|
+
embedding: string;
|
|
311
|
+
document: string | null;
|
|
312
|
+
metadata: string | null;
|
|
313
|
+
created_at: number;
|
|
314
|
+
updated_at: number;
|
|
315
|
+
}>;
|
|
316
|
+
|
|
317
|
+
const encoder = new TextEncoder();
|
|
318
|
+
let totalSum = 0;
|
|
319
|
+
const sampledResults: VectorNamespaceStatsWithSamples['sampledResults'] = {};
|
|
320
|
+
for (const sample of samples) {
|
|
321
|
+
const embeddingBytes = encoder.encode(sample.embedding).length;
|
|
322
|
+
const documentBytes = sample.document ? encoder.encode(sample.document).length : 0;
|
|
323
|
+
const size = embeddingBytes + documentBytes;
|
|
324
|
+
totalSum += size;
|
|
325
|
+
sampledResults![sample.key] = {
|
|
326
|
+
embedding: JSON.parse(sample.embedding),
|
|
327
|
+
document: sample.document || undefined,
|
|
328
|
+
size,
|
|
329
|
+
metadata: sample.metadata ? JSON.parse(sample.metadata) : undefined,
|
|
330
|
+
firstUsed: sample.created_at,
|
|
331
|
+
lastUsed: sample.updated_at,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Estimate total size based on sampled average if we have more records than samples
|
|
336
|
+
const estimatedSum =
|
|
337
|
+
stats.count <= samples.length
|
|
338
|
+
? totalSum
|
|
339
|
+
: Math.round((totalSum / samples.length) * stats.count);
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
sum: estimatedSum,
|
|
343
|
+
count: stats.count,
|
|
344
|
+
createdAt: stats.created_at || undefined,
|
|
345
|
+
lastUsed: stats.last_used || undefined,
|
|
346
|
+
sampledResults,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async getAllStats(
|
|
351
|
+
_params?: VectorGetAllStatsParams
|
|
352
|
+
): Promise<Record<string, VectorNamespaceStats> | VectorStatsPaginated> {
|
|
353
|
+
const query = this.#db.query(`
|
|
354
|
+
SELECT name, embedding, document
|
|
355
|
+
FROM vector_storage
|
|
356
|
+
WHERE project_path = ?
|
|
357
|
+
`);
|
|
358
|
+
|
|
359
|
+
const rows = query.all(this.#projectPath) as Array<{
|
|
360
|
+
name: string;
|
|
361
|
+
embedding: string;
|
|
362
|
+
document: string | null;
|
|
363
|
+
}>;
|
|
364
|
+
|
|
365
|
+
const encoder = new TextEncoder();
|
|
366
|
+
const namespaceStats = new Map<
|
|
367
|
+
string,
|
|
368
|
+
{ sum: number; count: number; createdAt?: number; lastUsed?: number }
|
|
369
|
+
>();
|
|
370
|
+
|
|
371
|
+
for (const row of rows) {
|
|
372
|
+
const embeddingBytes = encoder.encode(row.embedding).length;
|
|
373
|
+
const documentBytes = row.document ? encoder.encode(row.document).length : 0;
|
|
374
|
+
const size = embeddingBytes + documentBytes;
|
|
375
|
+
|
|
376
|
+
const existing = namespaceStats.get(row.name);
|
|
377
|
+
if (existing) {
|
|
378
|
+
existing.sum += size;
|
|
379
|
+
existing.count += 1;
|
|
380
|
+
} else {
|
|
381
|
+
namespaceStats.set(row.name, { sum: size, count: 1 });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Get timestamps in a separate query
|
|
386
|
+
const timestampQuery = this.#db.query(`
|
|
387
|
+
SELECT name, MIN(created_at) as created_at, MAX(updated_at) as last_used
|
|
388
|
+
FROM vector_storage
|
|
389
|
+
WHERE project_path = ?
|
|
390
|
+
GROUP BY name
|
|
391
|
+
`);
|
|
392
|
+
|
|
393
|
+
const timestamps = timestampQuery.all(this.#projectPath) as Array<{
|
|
394
|
+
name: string;
|
|
395
|
+
created_at: number | null;
|
|
396
|
+
last_used: number | null;
|
|
397
|
+
}>;
|
|
398
|
+
|
|
399
|
+
for (const ts of timestamps) {
|
|
400
|
+
const stats = namespaceStats.get(ts.name);
|
|
401
|
+
if (stats) {
|
|
402
|
+
stats.createdAt = ts.created_at || undefined;
|
|
403
|
+
stats.lastUsed = ts.last_used || undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const results: Record<string, VectorNamespaceStats> = {};
|
|
408
|
+
for (const [name, stats] of namespaceStats) {
|
|
409
|
+
results[name] = stats;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return results;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async getNamespaces(): Promise<string[]> {
|
|
416
|
+
const query = this.#db.query(`
|
|
417
|
+
SELECT DISTINCT name
|
|
418
|
+
FROM vector_storage
|
|
419
|
+
WHERE project_path = ?
|
|
420
|
+
`);
|
|
421
|
+
|
|
422
|
+
const rows = query.all(this.#projectPath) as Array<{ name: string }>;
|
|
423
|
+
return rows.map((row) => row.name);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async deleteNamespace(name: string): Promise<void> {
|
|
427
|
+
if (!name?.trim()) {
|
|
428
|
+
throw new Error('Vector storage name is required');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const stmt = this.#db.prepare(`
|
|
432
|
+
DELETE FROM vector_storage
|
|
433
|
+
WHERE project_path = ? AND name = ?
|
|
434
|
+
`);
|
|
435
|
+
|
|
436
|
+
stmt.run(this.#projectPath, name);
|
|
437
|
+
}
|
|
438
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentuity/local - Local development services
|
|
3
|
+
*
|
|
4
|
+
* Provides local storage implementations for development.
|
|
5
|
+
* Runtime-specific implementations are auto-detected.
|
|
6
|
+
*
|
|
7
|
+
* Users can provide their own implementations via service overrides
|
|
8
|
+
* by implementing these interfaces.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Re-export core interfaces so users can implement their own
|
|
12
|
+
export type {
|
|
13
|
+
KeyValueStorage,
|
|
14
|
+
StreamStorage,
|
|
15
|
+
VectorStorage,
|
|
16
|
+
QueueService,
|
|
17
|
+
EmailService,
|
|
18
|
+
TaskStorage,
|
|
19
|
+
} from '@agentuity/core';
|
|
20
|
+
|
|
21
|
+
// Runtime detection
|
|
22
|
+
export { detectRuntime, isLocalAvailable, getRuntimeName, type Runtime } from './runtime';
|
|
23
|
+
|
|
24
|
+
// Bun implementations (only available when running in Bun)
|
|
25
|
+
export {
|
|
26
|
+
getLocalDB,
|
|
27
|
+
closeLocalDB,
|
|
28
|
+
LocalKeyValueStorage,
|
|
29
|
+
LocalStreamStorage,
|
|
30
|
+
LocalVectorStorage,
|
|
31
|
+
LocalQueueStorage,
|
|
32
|
+
LocalEmailStorage,
|
|
33
|
+
LocalTaskStorage,
|
|
34
|
+
now,
|
|
35
|
+
normalizeProjectPath,
|
|
36
|
+
} from './bun';
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime detection for @agentuity/local
|
|
3
|
+
*
|
|
4
|
+
* Detects the current JavaScript runtime and provides
|
|
5
|
+
* appropriate local storage implementations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type Runtime = 'bun' | 'node' | 'deno' | 'workers' | 'unknown';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect the current runtime environment.
|
|
12
|
+
*/
|
|
13
|
+
export function detectRuntime(): Runtime {
|
|
14
|
+
// Bun has a global Bun object
|
|
15
|
+
if (typeof (globalThis as any).Bun !== 'undefined') {
|
|
16
|
+
return 'bun';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Deno has a global Deno object
|
|
20
|
+
if (typeof (globalThis as any).Deno !== 'undefined') {
|
|
21
|
+
return 'deno';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Cloudflare Workers have caches.default
|
|
25
|
+
if (
|
|
26
|
+
typeof (globalThis as any).caches !== 'undefined' &&
|
|
27
|
+
'default' in (globalThis as any).caches
|
|
28
|
+
) {
|
|
29
|
+
return 'workers';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Node.js has process.versions.node
|
|
33
|
+
if (
|
|
34
|
+
typeof (globalThis as any).process !== 'undefined' &&
|
|
35
|
+
(globalThis as any).process?.versions?.node
|
|
36
|
+
) {
|
|
37
|
+
return 'node';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return 'unknown';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if local services are available for the current runtime.
|
|
45
|
+
*/
|
|
46
|
+
export function isLocalAvailable(): boolean {
|
|
47
|
+
const runtime = detectRuntime();
|
|
48
|
+
return runtime === 'bun'; // Only Bun is supported for now
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the current runtime name for logging.
|
|
53
|
+
*/
|
|
54
|
+
export function getRuntimeName(): string {
|
|
55
|
+
return detectRuntime();
|
|
56
|
+
}
|