@agentuity/runtime 0.0.101 → 0.0.103
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/AGENTS.md +34 -212
- package/dist/_process-protection.d.ts.map +1 -1
- package/dist/_process-protection.js +12 -2
- package/dist/_process-protection.js.map +1 -1
- package/dist/agent.d.ts +7 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +45 -18
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts +61 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js.map +1 -1
- package/dist/eval.d.ts +16 -13
- package/dist/eval.d.ts.map +1 -1
- package/dist/eval.js +13 -1
- package/dist/eval.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +59 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +133 -24
- package/dist/middleware.js.map +1 -1
- package/dist/services/local/vector.d.ts +5 -1
- package/dist/services/local/vector.d.ts.map +1 -1
- package/dist/services/local/vector.js +112 -0
- package/dist/services/local/vector.js.map +1 -1
- package/package.json +5 -5
- package/src/_process-protection.ts +12 -2
- package/src/agent.ts +82 -28
- package/src/app.ts +65 -0
- package/src/eval.ts +22 -16
- package/src/index.ts +9 -3
- package/src/middleware.ts +146 -25
- package/src/services/local/vector.ts +160 -0
package/src/app.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { type Env as HonoEnv } from 'hono';
|
|
3
3
|
import type { cors } from 'hono/cors';
|
|
4
|
+
import type { compress } from 'hono/compress';
|
|
4
5
|
import type { Logger } from './logger';
|
|
5
6
|
import type { Meter, Tracer } from '@opentelemetry/api';
|
|
6
7
|
import type {
|
|
@@ -14,14 +15,78 @@ import type {
|
|
|
14
15
|
import type { Email } from './io/email';
|
|
15
16
|
import type { ThreadProvider, SessionProvider, Session, Thread } from './session';
|
|
16
17
|
import type WaitUntilHandler from './_waituntil';
|
|
18
|
+
import type { Context } from 'hono';
|
|
17
19
|
|
|
18
20
|
type CorsOptions = Parameters<typeof cors>[0];
|
|
21
|
+
type HonoCompressOptions = Parameters<typeof compress>[0];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration options for response compression middleware.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const app = await createApp({
|
|
29
|
+
* compression: {
|
|
30
|
+
* enabled: true,
|
|
31
|
+
* threshold: 1024,
|
|
32
|
+
* }
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface CompressionConfig {
|
|
37
|
+
/**
|
|
38
|
+
* Enable or disable compression globally.
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Minimum response body size in bytes before compression is attempted.
|
|
45
|
+
* Responses smaller than this threshold will not be compressed.
|
|
46
|
+
* @default 1024
|
|
47
|
+
*/
|
|
48
|
+
threshold?: number;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Optional filter function to skip compression for specific requests.
|
|
52
|
+
* Return false to skip compression for the request.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* filter: (c) => !c.req.path.startsWith('/internal')
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
filter?: (c: Context) => boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Raw options passed through to Hono's compress middleware.
|
|
63
|
+
* These are merged with Agentuity's defaults.
|
|
64
|
+
*/
|
|
65
|
+
honoOptions?: HonoCompressOptions;
|
|
66
|
+
}
|
|
19
67
|
|
|
20
68
|
export interface AppConfig<TAppState = Record<string, never>> {
|
|
21
69
|
/**
|
|
22
70
|
* Override the default cors settings
|
|
23
71
|
*/
|
|
24
72
|
cors?: CorsOptions;
|
|
73
|
+
/**
|
|
74
|
+
* Configure response compression.
|
|
75
|
+
* Set to `false` to disable compression entirely.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const app = await createApp({
|
|
80
|
+
* compression: {
|
|
81
|
+
* threshold: 2048,
|
|
82
|
+
* }
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* // Or disable compression:
|
|
86
|
+
* const app = await createApp({ compression: false });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
compression?: CompressionConfig | false;
|
|
25
90
|
/**
|
|
26
91
|
* Override the default services
|
|
27
92
|
*/
|
package/src/eval.ts
CHANGED
|
@@ -1,34 +1,40 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import type { StandardSchemaV1, InferInput, InferOutput } from '@agentuity/core';
|
|
3
3
|
import type { AgentContext } from './agent';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
|
|
5
6
|
// Eval SDK types
|
|
6
7
|
export type EvalContext = AgentContext<any, any, any>;
|
|
7
8
|
|
|
8
9
|
export type EvalRunResultMetadata = {
|
|
9
|
-
reason: string;
|
|
10
10
|
// biome-ignore lint/suspicious/noExplicitAny: metadata can contain any type of data
|
|
11
11
|
[key: string]: any;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
metadata:
|
|
18
|
-
};
|
|
14
|
+
export const EvalHandlerResultSchema = z.object({
|
|
15
|
+
passed: z.boolean(),
|
|
16
|
+
score: z.number().min(0).max(1).optional(),
|
|
17
|
+
metadata: z.record(z.string(), z.any()),
|
|
18
|
+
});
|
|
19
19
|
|
|
20
|
-
export type
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
export type EvalHandlerResult = z.infer<typeof EvalHandlerResultSchema>;
|
|
21
|
+
|
|
22
|
+
// Internal types for catalyst (include success field)
|
|
23
|
+
export const EvalRunResultSuccessSchema = z.object({
|
|
24
|
+
success: z.literal(true),
|
|
25
|
+
passed: z.boolean(),
|
|
26
|
+
score: z.number().min(0).max(1).optional(),
|
|
27
|
+
metadata: z.record(z.string(), z.any()),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type EvalRunResultSuccess = z.infer<typeof EvalRunResultSuccessSchema>;
|
|
25
31
|
|
|
26
32
|
export type EvalRunResultError = {
|
|
27
33
|
success: false;
|
|
28
34
|
error: string;
|
|
29
35
|
};
|
|
30
36
|
|
|
31
|
-
export type EvalRunResult =
|
|
37
|
+
export type EvalRunResult = EvalRunResultSuccess | EvalRunResultError;
|
|
32
38
|
|
|
33
39
|
export type CreateEvalRunRequest = {
|
|
34
40
|
projectId: string;
|
|
@@ -80,11 +86,11 @@ type InferSchemaOutput<T> = T extends StandardSchemaV1 ? InferOutput<T> : any;
|
|
|
80
86
|
|
|
81
87
|
export type EvalFunction<TInput = any, TOutput = any> = [TInput] extends [undefined]
|
|
82
88
|
? [TOutput] extends [undefined]
|
|
83
|
-
? (ctx: EvalContext) => Promise<
|
|
84
|
-
: (ctx: EvalContext, output: TOutput) => Promise<
|
|
89
|
+
? (ctx: EvalContext) => Promise<EvalHandlerResult>
|
|
90
|
+
: (ctx: EvalContext, output: TOutput) => Promise<EvalHandlerResult>
|
|
85
91
|
: [TOutput] extends [undefined]
|
|
86
|
-
? (ctx: EvalContext, input: TInput) => Promise<
|
|
87
|
-
: (ctx: EvalContext, input: TInput, output: TOutput) => Promise<
|
|
92
|
+
? (ctx: EvalContext, input: TInput) => Promise<EvalHandlerResult>
|
|
93
|
+
: (ctx: EvalContext, input: TInput, output: TOutput) => Promise<EvalHandlerResult>;
|
|
88
94
|
|
|
89
95
|
/**
|
|
90
96
|
* The Eval handler interface.
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export {
|
|
|
28
28
|
// app.ts exports (all app-related functionality)
|
|
29
29
|
export {
|
|
30
30
|
type AppConfig,
|
|
31
|
+
type CompressionConfig,
|
|
31
32
|
type Variables,
|
|
32
33
|
type TriggerType,
|
|
33
34
|
type PrivateVariables,
|
|
@@ -43,7 +44,12 @@ export {
|
|
|
43
44
|
export { addEventListener, removeEventListener } from './_events';
|
|
44
45
|
|
|
45
46
|
// middleware.ts exports (Vite-native)
|
|
46
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
createBaseMiddleware,
|
|
49
|
+
createCorsMiddleware,
|
|
50
|
+
createOtelMiddleware,
|
|
51
|
+
createCompressionMiddleware,
|
|
52
|
+
} from './middleware';
|
|
47
53
|
|
|
48
54
|
// Internal exports needed by generated entry files
|
|
49
55
|
export { register } from './otel/config';
|
|
@@ -63,8 +69,8 @@ export { type HonoEnv, type WebSocketConnection, createRouter } from './router';
|
|
|
63
69
|
export {
|
|
64
70
|
type EvalContext,
|
|
65
71
|
type EvalRunResultMetadata,
|
|
66
|
-
type
|
|
67
|
-
type
|
|
72
|
+
type EvalHandlerResult,
|
|
73
|
+
type EvalRunResultSuccess,
|
|
68
74
|
type EvalRunResultError,
|
|
69
75
|
type EvalRunResult,
|
|
70
76
|
type CreateEvalRunRequest,
|
package/src/middleware.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { createMiddleware } from 'hono/factory';
|
|
7
7
|
import { cors } from 'hono/cors';
|
|
8
|
-
import
|
|
8
|
+
import { compress } from 'hono/compress';
|
|
9
|
+
import type { Env, CompressionConfig } from './app';
|
|
9
10
|
import type { Logger } from './logger';
|
|
11
|
+
import { getAppConfig } from './app';
|
|
10
12
|
import { generateId } from './session';
|
|
11
13
|
import { runInHTTPContext } from './_context';
|
|
12
14
|
import { DURATION_HEADER, TOKENS_HEADER } from './_tokens';
|
|
@@ -124,31 +126,77 @@ export function createBaseMiddleware(config: MiddlewareConfig) {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
/**
|
|
127
|
-
* Create CORS middleware
|
|
129
|
+
* Create CORS middleware with lazy config resolution.
|
|
130
|
+
*
|
|
131
|
+
* Handles Cross-Origin Resource Sharing (CORS) headers for API routes.
|
|
132
|
+
* Config is resolved at request time, allowing it to be set via createApp().
|
|
133
|
+
* Static options passed here take precedence over app config.
|
|
134
|
+
*
|
|
135
|
+
* Default behavior:
|
|
136
|
+
* - Reflects the request origin (allows any origin)
|
|
137
|
+
* - Allows common headers: Content-Type, Authorization, Accept, Origin, X-Requested-With
|
|
138
|
+
* - Allows all standard HTTP methods
|
|
139
|
+
* - Enables credentials
|
|
140
|
+
* - Sets max-age to 600 seconds (10 minutes)
|
|
141
|
+
*
|
|
142
|
+
* @param staticOptions - Optional static CORS options that override app config
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* // Use with default settings
|
|
147
|
+
* app.use('/api/*', createCorsMiddleware());
|
|
148
|
+
*
|
|
149
|
+
* // Or configure via createApp
|
|
150
|
+
* const app = await createApp({
|
|
151
|
+
* cors: {
|
|
152
|
+
* origin: 'https://example.com',
|
|
153
|
+
* allowHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header'],
|
|
154
|
+
* maxAge: 3600,
|
|
155
|
+
* }
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* // Or pass static options directly (overrides app config)
|
|
159
|
+
* app.use('/api/*', createCorsMiddleware({
|
|
160
|
+
* origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
161
|
+
* credentials: true,
|
|
162
|
+
* }));
|
|
163
|
+
* ```
|
|
128
164
|
*/
|
|
129
|
-
export function createCorsMiddleware(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
165
|
+
export function createCorsMiddleware(staticOptions?: Parameters<typeof cors>[0]) {
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
167
|
+
return createMiddleware<Env<any>>(async (c, next) => {
|
|
168
|
+
// Lazy resolve: merge app config with static options
|
|
169
|
+
const appConfig = getAppConfig();
|
|
170
|
+
const corsOptions = {
|
|
171
|
+
...appConfig?.cors,
|
|
172
|
+
...staticOptions,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const corsMiddleware = cors({
|
|
176
|
+
origin: corsOptions?.origin ?? ((origin: string) => origin),
|
|
177
|
+
allowHeaders: corsOptions?.allowHeaders ?? [
|
|
178
|
+
'Content-Type',
|
|
179
|
+
'Authorization',
|
|
180
|
+
'Accept',
|
|
181
|
+
'Origin',
|
|
182
|
+
'X-Requested-With',
|
|
183
|
+
THREAD_HEADER,
|
|
184
|
+
],
|
|
185
|
+
allowMethods: ['POST', 'GET', 'OPTIONS', 'HEAD', 'PUT', 'DELETE', 'PATCH'],
|
|
186
|
+
exposeHeaders: [
|
|
187
|
+
'Content-Length',
|
|
188
|
+
TOKENS_HEADER,
|
|
189
|
+
DURATION_HEADER,
|
|
190
|
+
THREAD_HEADER,
|
|
191
|
+
SESSION_HEADER,
|
|
192
|
+
DEPLOYMENT_HEADER,
|
|
193
|
+
],
|
|
194
|
+
maxAge: 600,
|
|
195
|
+
credentials: true,
|
|
196
|
+
...(corsOptions ?? {}),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return corsMiddleware(c, next);
|
|
152
200
|
});
|
|
153
201
|
}
|
|
154
202
|
|
|
@@ -321,3 +369,76 @@ export function createOtelMiddleware() {
|
|
|
321
369
|
});
|
|
322
370
|
});
|
|
323
371
|
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Create compression middleware with lazy config resolution.
|
|
375
|
+
*
|
|
376
|
+
* Compresses response bodies using gzip or deflate based on the Accept-Encoding header.
|
|
377
|
+
* Config is resolved at request time, allowing it to be set via createApp().
|
|
378
|
+
*
|
|
379
|
+
* @param staticConfig - Optional static config that overrides app config
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```typescript
|
|
383
|
+
* // Use with default settings
|
|
384
|
+
* app.use('*', createCompressionMiddleware());
|
|
385
|
+
*
|
|
386
|
+
* // Or configure via createApp
|
|
387
|
+
* const app = await createApp({
|
|
388
|
+
* compression: {
|
|
389
|
+
* threshold: 2048,
|
|
390
|
+
* }
|
|
391
|
+
* });
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
export function createCompressionMiddleware(staticConfig?: CompressionConfig) {
|
|
395
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
396
|
+
return createMiddleware<Env<any>>(async (c, next) => {
|
|
397
|
+
// Lazy resolve: merge app config with static config
|
|
398
|
+
const appConfig = getAppConfig();
|
|
399
|
+
const appCompressionConfig = appConfig?.compression;
|
|
400
|
+
|
|
401
|
+
// Check if compression is explicitly disabled
|
|
402
|
+
if (appCompressionConfig === false || staticConfig?.enabled === false) {
|
|
403
|
+
return next();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Merge configs: static config takes precedence over app config
|
|
407
|
+
const config: CompressionConfig = {
|
|
408
|
+
...(typeof appCompressionConfig === 'object' ? appCompressionConfig : {}),
|
|
409
|
+
...staticConfig,
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const { enabled = true, threshold = 1024, filter, honoOptions } = config;
|
|
413
|
+
|
|
414
|
+
// Skip if explicitly disabled
|
|
415
|
+
if (!enabled) {
|
|
416
|
+
return next();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Skip WebSocket upgrade requests
|
|
420
|
+
const upgrade = c.req.header('upgrade');
|
|
421
|
+
if (upgrade && upgrade.toLowerCase() === 'websocket') {
|
|
422
|
+
return next();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Skip if no Accept-Encoding header
|
|
426
|
+
const acceptEncoding = c.req.header('accept-encoding');
|
|
427
|
+
if (!acceptEncoding) {
|
|
428
|
+
return next();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check custom filter
|
|
432
|
+
if (filter && !filter(c)) {
|
|
433
|
+
return next();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Create and run the Hono compress middleware
|
|
437
|
+
const compressMiddleware = compress({
|
|
438
|
+
threshold,
|
|
439
|
+
...honoOptions,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await compressMiddleware(c, next);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
VectorSearchResultWithDocument,
|
|
9
9
|
VectorSearchParams,
|
|
10
10
|
VectorSearchResult,
|
|
11
|
+
VectorNamespaceStats,
|
|
12
|
+
VectorNamespaceStatsWithSamples,
|
|
11
13
|
} from '@agentuity/core';
|
|
12
14
|
import { randomUUID } from 'node:crypto';
|
|
13
15
|
import { simpleEmbedding, cosineSimilarity, now } from './_util';
|
|
@@ -271,4 +273,162 @@ export class LocalVectorStorage implements VectorStorage {
|
|
|
271
273
|
const { count } = query.get(this.#projectPath, name) as { count: number };
|
|
272
274
|
return count > 0;
|
|
273
275
|
}
|
|
276
|
+
|
|
277
|
+
async getStats(name: string): Promise<VectorNamespaceStatsWithSamples> {
|
|
278
|
+
if (!name?.trim()) {
|
|
279
|
+
throw new Error('Vector storage name is required');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const countQuery = this.#db.query(`
|
|
283
|
+
SELECT COUNT(*) as count,
|
|
284
|
+
MIN(created_at) as created_at, MAX(updated_at) as last_used
|
|
285
|
+
FROM vector_storage
|
|
286
|
+
WHERE project_path = ? AND name = ?
|
|
287
|
+
`);
|
|
288
|
+
|
|
289
|
+
const stats = countQuery.get(this.#projectPath, name) as {
|
|
290
|
+
count: number;
|
|
291
|
+
created_at: number | null;
|
|
292
|
+
last_used: number | null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (stats.count === 0) {
|
|
296
|
+
return { sum: 0, count: 0 };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const sampleQuery = this.#db.query(`
|
|
300
|
+
SELECT key, embedding, document, metadata, created_at, updated_at
|
|
301
|
+
FROM vector_storage
|
|
302
|
+
WHERE project_path = ? AND name = ?
|
|
303
|
+
LIMIT 20
|
|
304
|
+
`);
|
|
305
|
+
|
|
306
|
+
const samples = sampleQuery.all(this.#projectPath, name) as Array<{
|
|
307
|
+
key: string;
|
|
308
|
+
embedding: string;
|
|
309
|
+
document: string | null;
|
|
310
|
+
metadata: string | null;
|
|
311
|
+
created_at: number;
|
|
312
|
+
updated_at: number;
|
|
313
|
+
}>;
|
|
314
|
+
|
|
315
|
+
const encoder = new TextEncoder();
|
|
316
|
+
let totalSum = 0;
|
|
317
|
+
const sampledResults: VectorNamespaceStatsWithSamples['sampledResults'] = {};
|
|
318
|
+
for (const sample of samples) {
|
|
319
|
+
const embeddingBytes = encoder.encode(sample.embedding).length;
|
|
320
|
+
const documentBytes = sample.document ? encoder.encode(sample.document).length : 0;
|
|
321
|
+
const size = embeddingBytes + documentBytes;
|
|
322
|
+
totalSum += size;
|
|
323
|
+
sampledResults![sample.key] = {
|
|
324
|
+
embedding: JSON.parse(sample.embedding),
|
|
325
|
+
document: sample.document || undefined,
|
|
326
|
+
size,
|
|
327
|
+
metadata: sample.metadata ? JSON.parse(sample.metadata) : undefined,
|
|
328
|
+
firstUsed: sample.created_at,
|
|
329
|
+
lastUsed: sample.updated_at,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Estimate total size based on sampled average if we have more records than samples
|
|
334
|
+
const estimatedSum =
|
|
335
|
+
stats.count <= samples.length
|
|
336
|
+
? totalSum
|
|
337
|
+
: Math.round((totalSum / samples.length) * stats.count);
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
sum: estimatedSum,
|
|
341
|
+
count: stats.count,
|
|
342
|
+
createdAt: stats.created_at || undefined,
|
|
343
|
+
lastUsed: stats.last_used || undefined,
|
|
344
|
+
sampledResults,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async getAllStats(): Promise<Record<string, VectorNamespaceStats>> {
|
|
349
|
+
const query = this.#db.query(`
|
|
350
|
+
SELECT name, embedding, document
|
|
351
|
+
FROM vector_storage
|
|
352
|
+
WHERE project_path = ?
|
|
353
|
+
`);
|
|
354
|
+
|
|
355
|
+
const rows = query.all(this.#projectPath) as Array<{
|
|
356
|
+
name: string;
|
|
357
|
+
embedding: string;
|
|
358
|
+
document: string | null;
|
|
359
|
+
}>;
|
|
360
|
+
|
|
361
|
+
const encoder = new TextEncoder();
|
|
362
|
+
const namespaceStats = new Map<
|
|
363
|
+
string,
|
|
364
|
+
{ sum: number; count: number; createdAt?: number; lastUsed?: number }
|
|
365
|
+
>();
|
|
366
|
+
|
|
367
|
+
for (const row of rows) {
|
|
368
|
+
const embeddingBytes = encoder.encode(row.embedding).length;
|
|
369
|
+
const documentBytes = row.document ? encoder.encode(row.document).length : 0;
|
|
370
|
+
const size = embeddingBytes + documentBytes;
|
|
371
|
+
|
|
372
|
+
const existing = namespaceStats.get(row.name);
|
|
373
|
+
if (existing) {
|
|
374
|
+
existing.sum += size;
|
|
375
|
+
existing.count += 1;
|
|
376
|
+
} else {
|
|
377
|
+
namespaceStats.set(row.name, { sum: size, count: 1 });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Get timestamps in a separate query
|
|
382
|
+
const timestampQuery = this.#db.query(`
|
|
383
|
+
SELECT name, MIN(created_at) as created_at, MAX(updated_at) as last_used
|
|
384
|
+
FROM vector_storage
|
|
385
|
+
WHERE project_path = ?
|
|
386
|
+
GROUP BY name
|
|
387
|
+
`);
|
|
388
|
+
|
|
389
|
+
const timestamps = timestampQuery.all(this.#projectPath) as Array<{
|
|
390
|
+
name: string;
|
|
391
|
+
created_at: number | null;
|
|
392
|
+
last_used: number | null;
|
|
393
|
+
}>;
|
|
394
|
+
|
|
395
|
+
for (const ts of timestamps) {
|
|
396
|
+
const stats = namespaceStats.get(ts.name);
|
|
397
|
+
if (stats) {
|
|
398
|
+
stats.createdAt = ts.created_at || undefined;
|
|
399
|
+
stats.lastUsed = ts.last_used || undefined;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const results: Record<string, VectorNamespaceStats> = {};
|
|
404
|
+
for (const [name, stats] of namespaceStats) {
|
|
405
|
+
results[name] = stats;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return results;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async getNamespaces(): Promise<string[]> {
|
|
412
|
+
const query = this.#db.query(`
|
|
413
|
+
SELECT DISTINCT name
|
|
414
|
+
FROM vector_storage
|
|
415
|
+
WHERE project_path = ?
|
|
416
|
+
`);
|
|
417
|
+
|
|
418
|
+
const rows = query.all(this.#projectPath) as Array<{ name: string }>;
|
|
419
|
+
return rows.map((row) => row.name);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async deleteNamespace(name: string): Promise<void> {
|
|
423
|
+
if (!name?.trim()) {
|
|
424
|
+
throw new Error('Vector storage name is required');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const stmt = this.#db.prepare(`
|
|
428
|
+
DELETE FROM vector_storage
|
|
429
|
+
WHERE project_path = ? AND name = ?
|
|
430
|
+
`);
|
|
431
|
+
|
|
432
|
+
stmt.run(this.#projectPath, name);
|
|
433
|
+
}
|
|
274
434
|
}
|