@agentuity/runtime 0.0.42 → 0.0.44

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 (134) hide show
  1. package/AGENTS.md +11 -9
  2. package/README.md +4 -4
  3. package/dist/_context.d.ts +12 -4
  4. package/dist/_context.d.ts.map +1 -1
  5. package/dist/_server.d.ts +7 -4
  6. package/dist/_server.d.ts.map +1 -1
  7. package/dist/_services.d.ts +13 -2
  8. package/dist/_services.d.ts.map +1 -1
  9. package/dist/_util.d.ts +1 -1
  10. package/dist/_util.d.ts.map +1 -1
  11. package/dist/_waituntil.d.ts +1 -3
  12. package/dist/_waituntil.d.ts.map +1 -1
  13. package/dist/agent.d.ts +41 -14
  14. package/dist/agent.d.ts.map +1 -1
  15. package/dist/app.d.ts +90 -8
  16. package/dist/app.d.ts.map +1 -1
  17. package/dist/eval.d.ts +79 -0
  18. package/dist/eval.d.ts.map +1 -0
  19. package/dist/index.d.ts +6 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/io/email.d.ts +77 -0
  22. package/dist/io/email.d.ts.map +1 -0
  23. package/dist/logger/console.d.ts +21 -1
  24. package/dist/logger/console.d.ts.map +1 -1
  25. package/dist/logger/index.d.ts +0 -1
  26. package/dist/logger/index.d.ts.map +1 -1
  27. package/dist/logger/user.d.ts +2 -2
  28. package/dist/logger/user.d.ts.map +1 -1
  29. package/dist/otel/config.d.ts +3 -1
  30. package/dist/otel/config.d.ts.map +1 -1
  31. package/dist/otel/console.d.ts +2 -1
  32. package/dist/otel/console.d.ts.map +1 -1
  33. package/dist/otel/exporters/index.d.ts +4 -0
  34. package/dist/otel/exporters/index.d.ts.map +1 -0
  35. package/dist/otel/exporters/jsonl-log-exporter.d.ts +36 -0
  36. package/dist/otel/exporters/jsonl-log-exporter.d.ts.map +1 -0
  37. package/dist/otel/exporters/jsonl-metric-exporter.d.ts +40 -0
  38. package/dist/otel/exporters/jsonl-metric-exporter.d.ts.map +1 -0
  39. package/dist/otel/exporters/jsonl-trace-exporter.d.ts +36 -0
  40. package/dist/otel/exporters/jsonl-trace-exporter.d.ts.map +1 -0
  41. package/dist/otel/http.d.ts.map +1 -1
  42. package/dist/otel/logger.d.ts +15 -11
  43. package/dist/otel/logger.d.ts.map +1 -1
  44. package/dist/otel/otel.d.ts +8 -2
  45. package/dist/otel/otel.d.ts.map +1 -1
  46. package/dist/router.d.ts +4 -1
  47. package/dist/router.d.ts.map +1 -1
  48. package/dist/services/evalrun/composite.d.ts +21 -0
  49. package/dist/services/evalrun/composite.d.ts.map +1 -0
  50. package/dist/services/evalrun/http.d.ts +24 -0
  51. package/dist/services/evalrun/http.d.ts.map +1 -0
  52. package/dist/services/evalrun/index.d.ts +5 -0
  53. package/dist/services/evalrun/index.d.ts.map +1 -0
  54. package/dist/services/evalrun/json.d.ts +21 -0
  55. package/dist/services/evalrun/json.d.ts.map +1 -0
  56. package/dist/services/evalrun/local.d.ts +19 -0
  57. package/dist/services/evalrun/local.d.ts.map +1 -0
  58. package/dist/services/local/_db.d.ts +4 -0
  59. package/dist/services/local/_db.d.ts.map +1 -0
  60. package/dist/services/local/_router.d.ts +3 -0
  61. package/dist/services/local/_router.d.ts.map +1 -0
  62. package/dist/services/local/_util.d.ts +18 -0
  63. package/dist/services/local/_util.d.ts.map +1 -0
  64. package/dist/services/local/index.d.ts +8 -0
  65. package/dist/services/local/index.d.ts.map +1 -0
  66. package/dist/services/local/keyvalue.d.ts +10 -0
  67. package/dist/services/local/keyvalue.d.ts.map +1 -0
  68. package/dist/services/local/objectstore.d.ts +11 -0
  69. package/dist/services/local/objectstore.d.ts.map +1 -0
  70. package/dist/services/local/stream.d.ts +10 -0
  71. package/dist/services/local/stream.d.ts.map +1 -0
  72. package/dist/services/local/vector.d.ts +13 -0
  73. package/dist/services/local/vector.d.ts.map +1 -0
  74. package/dist/services/session/composite.d.ts +21 -0
  75. package/dist/services/session/composite.d.ts.map +1 -0
  76. package/dist/services/session/http.d.ts +23 -0
  77. package/dist/services/session/http.d.ts.map +1 -0
  78. package/dist/services/session/index.d.ts +5 -0
  79. package/dist/services/session/index.d.ts.map +1 -0
  80. package/dist/services/session/json.d.ts +22 -0
  81. package/dist/services/session/json.d.ts.map +1 -0
  82. package/dist/services/session/local.d.ts +19 -0
  83. package/dist/services/session/local.d.ts.map +1 -0
  84. package/dist/session.d.ts +70 -0
  85. package/dist/session.d.ts.map +1 -0
  86. package/package.json +10 -6
  87. package/src/_config.ts +1 -1
  88. package/src/_context.ts +19 -16
  89. package/src/_server.ts +284 -42
  90. package/src/_services.ts +147 -34
  91. package/src/_util.ts +2 -3
  92. package/src/_waituntil.ts +5 -153
  93. package/src/agent.ts +667 -65
  94. package/src/app.ts +159 -13
  95. package/src/eval.ts +95 -0
  96. package/src/index.ts +6 -1
  97. package/src/io/email.ts +173 -0
  98. package/src/logger/console.ts +222 -15
  99. package/src/logger/index.ts +0 -1
  100. package/src/logger/user.ts +8 -4
  101. package/src/otel/config.ts +7 -44
  102. package/src/otel/console.ts +9 -4
  103. package/src/otel/exporters/README.md +217 -0
  104. package/src/otel/exporters/index.ts +3 -0
  105. package/src/otel/exporters/jsonl-log-exporter.ts +113 -0
  106. package/src/otel/exporters/jsonl-metric-exporter.ts +120 -0
  107. package/src/otel/exporters/jsonl-trace-exporter.ts +121 -0
  108. package/src/otel/http.ts +3 -1
  109. package/src/otel/logger.ts +106 -41
  110. package/src/otel/otel.ts +43 -22
  111. package/src/router.ts +44 -4
  112. package/src/services/evalrun/composite.ts +34 -0
  113. package/src/services/evalrun/http.ts +112 -0
  114. package/src/services/evalrun/index.ts +4 -0
  115. package/src/services/evalrun/json.ts +46 -0
  116. package/src/services/evalrun/local.ts +28 -0
  117. package/src/services/local/README.md +1576 -0
  118. package/src/services/local/_db.ts +182 -0
  119. package/src/services/local/_router.ts +86 -0
  120. package/src/services/local/_util.ts +49 -0
  121. package/src/services/local/index.ts +7 -0
  122. package/src/services/local/keyvalue.ts +118 -0
  123. package/src/services/local/objectstore.ts +152 -0
  124. package/src/services/local/stream.ts +296 -0
  125. package/src/services/local/vector.ts +264 -0
  126. package/src/services/session/composite.ts +33 -0
  127. package/src/services/session/http.ts +64 -0
  128. package/src/services/session/index.ts +4 -0
  129. package/src/services/session/json.ts +42 -0
  130. package/src/services/session/local.ts +28 -0
  131. package/src/session.ts +284 -0
  132. package/dist/_unauthenticated.d.ts +0 -26
  133. package/dist/_unauthenticated.d.ts.map +0 -1
  134. package/src/_unauthenticated.ts +0 -126
@@ -0,0 +1,296 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type {
3
+ StreamStorage,
4
+ Stream,
5
+ CreateStreamProps,
6
+ ListStreamsParams,
7
+ ListStreamsResponse,
8
+ StreamInfo,
9
+ } from '@agentuity/core';
10
+ import { now } from './_util';
11
+ import { join } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ import { randomUUID } from 'node:crypto';
14
+ import { mkdirSync, existsSync, unlinkSync } from 'node:fs';
15
+ import { openSync, writeSync, closeSync, readFileSync } from 'node:fs';
16
+
17
+ export class LocalStreamStorage implements StreamStorage {
18
+ #db: Database;
19
+ #projectPath: string;
20
+ #serverUrl: string;
21
+ #tempDir: string;
22
+
23
+ constructor(db: Database, projectPath: string, serverUrl: string) {
24
+ this.#db = db;
25
+ this.#projectPath = projectPath;
26
+ this.#serverUrl = serverUrl;
27
+
28
+ // Create temp directory for stream buffering
29
+ this.#tempDir = join(homedir(), '.config', 'agentuity', 'streams');
30
+ if (!existsSync(this.#tempDir)) {
31
+ mkdirSync(this.#tempDir, { recursive: true });
32
+ }
33
+ }
34
+
35
+ async create(name: string, props?: CreateStreamProps): Promise<Stream> {
36
+ if (!name || name.length < 1 || name.length > 254) {
37
+ throw new Error('Stream name must be between 1 and 254 characters');
38
+ }
39
+
40
+ const id = randomUUID();
41
+ const timestamp = now();
42
+ const metadata = props?.metadata ? JSON.stringify(props.metadata) : null;
43
+
44
+ // Insert stream record with NULL data
45
+ const stmt = this.#db.prepare(`
46
+ INSERT INTO stream_storage (
47
+ project_path, id, name, metadata, content_type, created_at
48
+ )
49
+ VALUES (?, ?, ?, ?, ?, ?)
50
+ `);
51
+
52
+ stmt.run(
53
+ this.#projectPath,
54
+ id,
55
+ name,
56
+ metadata,
57
+ props?.contentType || 'application/octet-stream',
58
+ timestamp
59
+ );
60
+
61
+ const url = `${this.#serverUrl}/_agentuity/local/stream/${id}`;
62
+
63
+ return new LocalStream(
64
+ id,
65
+ url,
66
+ this.#db,
67
+ this.#projectPath,
68
+ this.#tempDir,
69
+ props?.compress ?? false
70
+ );
71
+ }
72
+
73
+ async list(params?: ListStreamsParams): Promise<ListStreamsResponse> {
74
+ if (params?.limit && (params.limit <= 0 || params.limit > 1000)) {
75
+ throw new Error('limit must be between 1 and 1000');
76
+ }
77
+
78
+ let query = `
79
+ SELECT id, name, metadata, size_bytes
80
+ FROM stream_storage
81
+ WHERE project_path = ?
82
+ `;
83
+ const queryParams: (string | number)[] = [this.#projectPath];
84
+
85
+ // Add filters
86
+ if (params?.name) {
87
+ query += ` AND name = ?`;
88
+ queryParams.push(params.name);
89
+ }
90
+
91
+ if (params?.metadata) {
92
+ // Simple JSON matching - check if metadata contains all key-value pairs
93
+ for (const [key, value] of Object.entries(params.metadata)) {
94
+ query += ` AND metadata LIKE ?`;
95
+ queryParams.push(`%"${key}":"${value}"%`);
96
+ }
97
+ }
98
+
99
+ // Get total count
100
+ const countQuery = this.#db.query(
101
+ query.replace('SELECT id, name, metadata, size_bytes', 'SELECT COUNT(*) as count')
102
+ );
103
+ const { count } = countQuery.get(...queryParams) as { count: number };
104
+
105
+ // Add pagination
106
+ query += ` ORDER BY created_at DESC`;
107
+ if (params?.limit) {
108
+ query += ` LIMIT ${params.limit}`;
109
+ }
110
+ if (params?.offset) {
111
+ query += ` OFFSET ${params.offset}`;
112
+ }
113
+
114
+ const stmt = this.#db.query(query);
115
+ const rows = stmt.all(...queryParams) as Array<{
116
+ id: string;
117
+ name: string;
118
+ metadata: string | null;
119
+ size_bytes: number;
120
+ }>;
121
+
122
+ const streams: StreamInfo[] = rows.map((row) => ({
123
+ id: row.id,
124
+ name: row.name,
125
+ metadata: row.metadata ? JSON.parse(row.metadata) : {},
126
+ url: `${this.#serverUrl}/_agentuity/local/stream/${row.id}`,
127
+ sizeBytes: row.size_bytes,
128
+ }));
129
+
130
+ return {
131
+ success: true,
132
+ streams,
133
+ total: count,
134
+ };
135
+ }
136
+
137
+ async delete(id: string): Promise<void> {
138
+ if (!id?.trim()) {
139
+ throw new Error('Stream id is required');
140
+ }
141
+
142
+ const stmt = this.#db.prepare(`
143
+ DELETE FROM stream_storage
144
+ WHERE project_path = ? AND id = ?
145
+ `);
146
+
147
+ stmt.run(this.#projectPath, id);
148
+ }
149
+ }
150
+
151
+ class LocalStream extends WritableStream implements Stream {
152
+ public readonly id: string;
153
+ public readonly url: string;
154
+
155
+ #db: Database;
156
+ #projectPath: string;
157
+ #compressed: boolean;
158
+ #tempFilePath: string;
159
+ #fileHandle: number | null = null;
160
+ #bytesWritten = 0;
161
+ #closed = false;
162
+
163
+ constructor(
164
+ id: string,
165
+ url: string,
166
+ db: Database,
167
+ projectPath: string,
168
+ tempDir: string,
169
+ compressed: boolean
170
+ ) {
171
+ super({
172
+ write: async (chunk: Uint8Array) => {
173
+ await this.#writeToFile(chunk);
174
+ },
175
+ close: async () => {
176
+ await this.#persist();
177
+ },
178
+ });
179
+
180
+ this.id = id;
181
+ this.url = url;
182
+ this.#db = db;
183
+ this.#projectPath = projectPath;
184
+ this.#compressed = compressed;
185
+ this.#tempFilePath = join(tempDir, `${id}.tmp`);
186
+
187
+ // Open file for writing
188
+ this.#fileHandle = openSync(this.#tempFilePath, 'w');
189
+ }
190
+
191
+ get bytesWritten(): number {
192
+ return this.#bytesWritten;
193
+ }
194
+
195
+ get compressed(): boolean {
196
+ return this.#compressed;
197
+ }
198
+
199
+ async write(chunk: string | Uint8Array | ArrayBuffer | Buffer | object): Promise<void> {
200
+ if (this.#closed) {
201
+ throw new Error('Stream is closed');
202
+ }
203
+
204
+ let binary: Uint8Array;
205
+ if (chunk instanceof Uint8Array) {
206
+ binary = chunk;
207
+ } else if (typeof chunk === 'string') {
208
+ binary = new TextEncoder().encode(chunk);
209
+ } else if (chunk instanceof ArrayBuffer) {
210
+ binary = new Uint8Array(chunk);
211
+ } else if (typeof chunk === 'object') {
212
+ binary = new TextEncoder().encode(JSON.stringify(chunk));
213
+ } else {
214
+ binary = new TextEncoder().encode(String(chunk));
215
+ }
216
+
217
+ await this.#writeToFile(binary);
218
+ }
219
+
220
+ override async close(): Promise<void> {
221
+ if (this.#closed) {
222
+ return;
223
+ }
224
+
225
+ this.#closed = true;
226
+
227
+ // Close file handle if open
228
+ if (this.#fileHandle !== null) {
229
+ closeSync(this.#fileHandle);
230
+ this.#fileHandle = null;
231
+ }
232
+
233
+ await this.#persist();
234
+ }
235
+
236
+ getReader(): ReadableStream<Uint8Array> {
237
+ const db = this.#db;
238
+ const projectPath = this.#projectPath;
239
+ const id = this.id;
240
+
241
+ return new ReadableStream({
242
+ start(controller) {
243
+ const query = db.query(`
244
+ SELECT data FROM stream_storage
245
+ WHERE project_path = ? AND id = ?
246
+ `);
247
+
248
+ const row = query.get(projectPath, id) as { data: Buffer | null } | null;
249
+
250
+ if (!row || !row.data) {
251
+ controller.error(new Error('Stream not found or not finalized'));
252
+ return;
253
+ }
254
+
255
+ controller.enqueue(new Uint8Array(row.data));
256
+ controller.close();
257
+ },
258
+ });
259
+ }
260
+
261
+ async #writeToFile(chunk: Uint8Array): Promise<void> {
262
+ if (this.#fileHandle === null) {
263
+ throw new Error('File handle is closed');
264
+ }
265
+
266
+ const written = writeSync(this.#fileHandle, chunk);
267
+ this.#bytesWritten += written;
268
+ }
269
+
270
+ async #persist(): Promise<void> {
271
+ // Read buffered file
272
+ let data = readFileSync(this.#tempFilePath);
273
+
274
+ // Optional: Apply compression if enabled
275
+ if (this.#compressed) {
276
+ const { gzipSync } = await import('node:zlib');
277
+ data = gzipSync(data);
278
+ }
279
+
280
+ // Update DB with finalized data
281
+ const stmt = this.#db.prepare(`
282
+ UPDATE stream_storage
283
+ SET data = ?, size_bytes = ?
284
+ WHERE project_path = ? AND id = ?
285
+ `);
286
+
287
+ stmt.run(data, this.#bytesWritten, this.#projectPath, this.id);
288
+
289
+ // Clean up temp file
290
+ try {
291
+ unlinkSync(this.#tempFilePath);
292
+ } catch {
293
+ // Ignore cleanup errors
294
+ }
295
+ }
296
+ }
@@ -0,0 +1,264 @@
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
+ } from '@agentuity/core';
12
+ import { randomUUID } from 'node:crypto';
13
+ import { simpleEmbedding, cosineSimilarity, now } from './_util';
14
+
15
+ export class LocalVectorStorage implements VectorStorage {
16
+ #db: Database;
17
+ #projectPath: string;
18
+
19
+ constructor(db: Database, projectPath: string) {
20
+ this.#db = db;
21
+ this.#projectPath = projectPath;
22
+ }
23
+
24
+ async upsert(name: string, ...documents: VectorUpsertParams[]): Promise<VectorUpsertResult[]> {
25
+ if (!name?.trim()) {
26
+ throw new Error('Vector storage name is required');
27
+ }
28
+ if (documents.length === 0) {
29
+ throw new Error('At least one document is required');
30
+ }
31
+
32
+ const results: VectorUpsertResult[] = [];
33
+ const stmt = this.#db.prepare(`
34
+ INSERT INTO vector_storage (
35
+ project_path, name, id, key, embedding, document, metadata, created_at, updated_at
36
+ )
37
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
38
+ ON CONFLICT(project_path, name, key)
39
+ DO UPDATE SET
40
+ embedding = excluded.embedding,
41
+ document = excluded.document,
42
+ metadata = excluded.metadata,
43
+ updated_at = excluded.updated_at
44
+ `);
45
+
46
+ for (const doc of documents) {
47
+ if (!doc.key?.trim()) {
48
+ throw new Error('Each document must have a non-empty key');
49
+ }
50
+
51
+ // Generate or use provided embeddings
52
+ let embedding: number[];
53
+ if ('embeddings' in doc && doc.embeddings) {
54
+ if (!Array.isArray(doc.embeddings) || doc.embeddings.length === 0) {
55
+ throw new Error('Embeddings must be a non-empty array');
56
+ }
57
+ embedding = doc.embeddings;
58
+ } else if ('document' in doc && doc.document) {
59
+ if (!doc.document?.trim()) {
60
+ throw new Error('Document text must be non-empty');
61
+ }
62
+ embedding = simpleEmbedding(doc.document);
63
+ } else {
64
+ throw new Error('Each document must have either embeddings or document text');
65
+ }
66
+
67
+ const id = randomUUID();
68
+ const timestamp = now();
69
+ const embeddingJson = JSON.stringify(embedding);
70
+ const documentText = 'document' in doc ? doc.document : null;
71
+ const metadata = doc.metadata ? JSON.stringify(doc.metadata) : null;
72
+
73
+ stmt.run(
74
+ this.#projectPath,
75
+ name,
76
+ id,
77
+ doc.key,
78
+ embeddingJson,
79
+ documentText ?? null,
80
+ metadata ?? null,
81
+ timestamp,
82
+ timestamp
83
+ );
84
+
85
+ const row = this.#db
86
+ .prepare(
87
+ 'SELECT id FROM vector_storage WHERE project_path = ? AND name = ? AND key = ?'
88
+ )
89
+ .get(this.#projectPath, name, doc.key) as { id: string } | undefined;
90
+
91
+ const actualId = row?.id ?? id;
92
+ results.push({ key: doc.key, id: actualId });
93
+ }
94
+
95
+ return results;
96
+ }
97
+
98
+ async get<T extends Record<string, unknown> = Record<string, unknown>>(
99
+ name: string,
100
+ key: string
101
+ ): Promise<VectorResult<T>> {
102
+ if (!name?.trim() || !key?.trim()) {
103
+ throw new Error('Vector storage name and key are required');
104
+ }
105
+
106
+ const query = this.#db.query(`
107
+ SELECT id, key, embedding, document, metadata
108
+ FROM vector_storage
109
+ WHERE project_path = ? AND name = ? AND key = ?
110
+ `);
111
+
112
+ const row = query.get(this.#projectPath, name, key) as {
113
+ id: string;
114
+ key: string;
115
+ embedding: string;
116
+ document: string | null;
117
+ metadata: string | null;
118
+ } | null;
119
+
120
+ if (!row) {
121
+ return { exists: false } as VectorResultNotFound;
122
+ }
123
+
124
+ return {
125
+ exists: true,
126
+ data: {
127
+ id: row.id,
128
+ key: row.key,
129
+ embeddings: JSON.parse(row.embedding),
130
+ document: row.document || undefined,
131
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
132
+ similarity: 1.0, // Perfect match for direct get
133
+ } as VectorSearchResultWithDocument<T>,
134
+ };
135
+ }
136
+
137
+ async getMany<T extends Record<string, unknown> = Record<string, unknown>>(
138
+ name: string,
139
+ ...keys: string[]
140
+ ): Promise<Map<string, VectorSearchResultWithDocument<T>>> {
141
+ if (!name?.trim()) {
142
+ throw new Error('Vector storage name is required');
143
+ }
144
+ if (keys.length === 0) {
145
+ return new Map();
146
+ }
147
+
148
+ const results = await Promise.all(
149
+ keys.map(async (key) => {
150
+ const result = await this.get<T>(name, key);
151
+ return { key, result };
152
+ })
153
+ );
154
+
155
+ const map = new Map<string, VectorSearchResultWithDocument<T>>();
156
+ for (const { key, result } of results) {
157
+ if (result.exists) {
158
+ map.set(key, result.data);
159
+ }
160
+ }
161
+
162
+ return map;
163
+ }
164
+
165
+ async search<T extends Record<string, unknown> = Record<string, unknown>>(
166
+ name: string,
167
+ params: VectorSearchParams<T>
168
+ ): Promise<VectorSearchResult<T>[]> {
169
+ if (!name?.trim()) {
170
+ throw new Error('Vector storage name is required');
171
+ }
172
+ if (!params.query?.trim()) {
173
+ throw new Error('Query is required');
174
+ }
175
+
176
+ // Generate query embedding
177
+ const queryEmbedding = simpleEmbedding(params.query);
178
+
179
+ // Fetch all vectors for this name
180
+ const query = this.#db.query(`
181
+ SELECT id, key, embedding, metadata
182
+ FROM vector_storage
183
+ WHERE project_path = ? AND name = ?
184
+ `);
185
+
186
+ const rows = query.all(this.#projectPath, name) as Array<{
187
+ id: string;
188
+ key: string;
189
+ embedding: string;
190
+ metadata: string | null;
191
+ }>;
192
+
193
+ // Calculate similarities
194
+ const results: Array<VectorSearchResult<T> & { similarity: number }> = [];
195
+
196
+ for (const row of rows) {
197
+ const embedding = JSON.parse(row.embedding);
198
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
199
+
200
+ // Apply similarity threshold
201
+ if (params.similarity !== undefined && similarity < params.similarity) {
202
+ continue;
203
+ }
204
+
205
+ // Apply metadata filter
206
+ if (params.metadata) {
207
+ const rowMetadata = row.metadata ? JSON.parse(row.metadata) : {};
208
+ const matches = Object.entries(params.metadata).every(
209
+ ([key, value]) => rowMetadata[key] === value
210
+ );
211
+ if (!matches) {
212
+ continue;
213
+ }
214
+ }
215
+
216
+ results.push({
217
+ id: row.id,
218
+ key: row.key,
219
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
220
+ similarity,
221
+ } as VectorSearchResult<T> & { similarity: number });
222
+ }
223
+
224
+ // Sort by similarity descending
225
+ results.sort((a, b) => b.similarity - a.similarity);
226
+
227
+ // Apply limit
228
+ const limit = params.limit || 10;
229
+ return results.slice(0, limit);
230
+ }
231
+
232
+ async delete(name: string, ...keys: string[]): Promise<number> {
233
+ if (!name?.trim()) {
234
+ throw new Error('Vector storage name is required');
235
+ }
236
+ if (keys.length === 0) {
237
+ return 0;
238
+ }
239
+
240
+ const placeholders = keys.map(() => '?').join(', ');
241
+ const stmt = this.#db.prepare(`
242
+ DELETE FROM vector_storage
243
+ WHERE project_path = ? AND name = ? AND key IN (${placeholders})
244
+ `);
245
+
246
+ const result = stmt.run(this.#projectPath, name, ...keys);
247
+ return result.changes;
248
+ }
249
+
250
+ async exists(name: string): Promise<boolean> {
251
+ if (!name?.trim()) {
252
+ throw new Error('Vector storage name is required');
253
+ }
254
+
255
+ const query = this.#db.query(`
256
+ SELECT COUNT(*) as count
257
+ FROM vector_storage
258
+ WHERE project_path = ? AND name = ?
259
+ `);
260
+
261
+ const { count } = query.get(this.#projectPath, name) as { count: number };
262
+ return count > 0;
263
+ }
264
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ type SessionCompleteEvent,
3
+ type SessionEventProvider,
4
+ type SessionStartEvent,
5
+ } from '@agentuity/core';
6
+
7
+ /**
8
+ * A composite implementation of SessionEventProvider that forwards events to multiple providers
9
+ */
10
+ export class CompositeSessionEventProvider implements SessionEventProvider {
11
+ private providers: SessionEventProvider[];
12
+
13
+ constructor(...providers: SessionEventProvider[]) {
14
+ this.providers = providers;
15
+ }
16
+ /**
17
+ * called when the session starts
18
+ *
19
+ * @param event SessionStartEvent
20
+ */
21
+ async start(event: SessionStartEvent): Promise<void> {
22
+ await Promise.all(this.providers.map((p) => p.start(event)));
23
+ }
24
+
25
+ /**
26
+ * called when the session completes
27
+ *
28
+ * @param event SessionCompleteEvent
29
+ */
30
+ async complete(event: SessionCompleteEvent): Promise<void> {
31
+ await Promise.all(this.providers.map((p) => p.complete(event)));
32
+ }
33
+ }
@@ -0,0 +1,64 @@
1
+ import { APIClient, APIResponseSchemaNoData } from '@agentuity/server';
2
+ import {
3
+ type SessionEventProvider,
4
+ type SessionStartEvent,
5
+ SessionStartEventDelayedSchema,
6
+ SessionCompleteEventDelayedSchema,
7
+ type SessionCompleteEvent,
8
+ type Logger,
9
+ } from '@agentuity/core';
10
+
11
+ /**
12
+ * An implementation of the SessionEventProvider which uses HTTP for delivery
13
+ */
14
+ export class HTTPSessionEventProvider implements SessionEventProvider {
15
+ private apiClient: APIClient;
16
+ private logger: Logger;
17
+
18
+ constructor(client: APIClient, logger: Logger) {
19
+ this.apiClient = client;
20
+ this.logger = logger;
21
+ }
22
+
23
+ /**
24
+ * called when the session starts
25
+ *
26
+ * @param event SessionStartEvent
27
+ */
28
+ async start(event: SessionStartEvent): Promise<void> {
29
+ this.logger.debug('Sending session start event: %s', event.id);
30
+ const resp = await this.apiClient.request(
31
+ 'POST',
32
+ '/session/2025-03-17',
33
+ APIResponseSchemaNoData(),
34
+ { ...event, timestamp: Date.now() },
35
+ SessionStartEventDelayedSchema
36
+ );
37
+ if (resp.success) {
38
+ this.logger.debug('Session start event sent successfully: %s', event.id);
39
+ return;
40
+ }
41
+ throw new Error(resp.message);
42
+ }
43
+
44
+ /**
45
+ * called when the session completes
46
+ *
47
+ * @param event SessionCompleteEvent
48
+ */
49
+ async complete(event: SessionCompleteEvent): Promise<void> {
50
+ this.logger.debug('Sending session complete event: %s', event.id);
51
+ const resp = await this.apiClient.request(
52
+ 'PUT',
53
+ '/session/2025-03-17',
54
+ APIResponseSchemaNoData(),
55
+ { ...event, timestamp: Date.now() },
56
+ SessionCompleteEventDelayedSchema
57
+ );
58
+ if (resp.success) {
59
+ this.logger.debug('Session complete event sent successfully: %s', event.id);
60
+ return;
61
+ }
62
+ throw new Error(resp.message);
63
+ }
64
+ }
@@ -0,0 +1,4 @@
1
+ export * from './composite';
2
+ export * from './http';
3
+ export * from './json';
4
+ export * from './local';
@@ -0,0 +1,42 @@
1
+ import { join } from 'node:path';
2
+ import { randomUUID } from 'node:crypto';
3
+ import {
4
+ type SessionEventProvider,
5
+ type SessionStartEvent,
6
+ type SessionCompleteEvent,
7
+ } from '@agentuity/core';
8
+
9
+ /**
10
+ * An implementation of the SessionEventProvider which uses JSON logs for delivery
11
+ */
12
+ export class JSONSessionEventProvider implements SessionEventProvider {
13
+ private directory: string;
14
+
15
+ constructor(directory: string) {
16
+ this.directory = directory;
17
+ }
18
+ private makeFilename(type: 'start' | 'complete'): string {
19
+ return join(this.directory, `session-${type}.${Date.now()}${randomUUID()}.json`);
20
+ }
21
+ /**
22
+ * called when the session starts
23
+ *
24
+ * @param event SessionStartEvent
25
+ */
26
+ async start(event: SessionStartEvent): Promise<void> {
27
+ const filename = this.makeFilename('start');
28
+ const payload = JSON.stringify({ ...event, timestamp: new Date() }) + '\n';
29
+ await Bun.file(filename).write(payload);
30
+ }
31
+
32
+ /**
33
+ * called when the session completes
34
+ *
35
+ * @param event SessionCompleteEvent
36
+ */
37
+ async complete(event: SessionCompleteEvent): Promise<void> {
38
+ const filename = this.makeFilename('complete');
39
+ const payload = JSON.stringify({ ...event, timestamp: new Date() }) + '\n';
40
+ await Bun.file(filename).write(payload);
41
+ }
42
+ }