llmjs2 1.3.9 → 1.6.1

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 (49) hide show
  1. package/README.md +31 -476
  2. package/chain/AGENT_STEP_README.md +102 -0
  3. package/chain/README.md +257 -0
  4. package/chain/WORKFLOW_README.md +85 -0
  5. package/chain/agent-step-example.js +232 -0
  6. package/chain/docs/AGENT.md +126 -0
  7. package/chain/docs/GRAPH.md +490 -0
  8. package/chain/examples.js +314 -0
  9. package/chain/index.js +31 -0
  10. package/chain/lib/agent.js +338 -0
  11. package/chain/lib/flow/agent-step.js +119 -0
  12. package/chain/lib/flow/edge.js +24 -0
  13. package/chain/lib/flow/flow.js +76 -0
  14. package/chain/lib/flow/graph.js +331 -0
  15. package/chain/lib/flow/index.js +7 -0
  16. package/chain/lib/flow/step.js +63 -0
  17. package/chain/lib/memory/in-memory.js +117 -0
  18. package/chain/lib/memory/index.js +36 -0
  19. package/chain/lib/memory/lance-memory.js +225 -0
  20. package/chain/lib/memory/sqlite-memory.js +309 -0
  21. package/chain/simple-agent-step-example.js +168 -0
  22. package/chain/workflow-example-usage.js +70 -0
  23. package/chain/workflow-example.json +59 -0
  24. package/core/README.md +485 -0
  25. package/core/cli.js +275 -0
  26. package/core/docs/BASIC_USAGE.md +62 -0
  27. package/core/docs/CLI.md +104 -0
  28. package/{docs → core/docs}/GET_STARTED.md +129 -129
  29. package/{docs → core/docs}/GUARDRAILS_GUIDE.md +734 -734
  30. package/{docs → core/docs}/README.md +47 -47
  31. package/core/docs/ROUTER_GUIDE.md +199 -0
  32. package/{docs → core/docs}/SERVER_MODE.md +358 -350
  33. package/core/index.js +115 -0
  34. package/{providers → core/providers}/ollama.js +14 -6
  35. package/{providers → core/providers}/openai.js +14 -6
  36. package/{providers → core/providers}/openrouter.js +14 -6
  37. package/core/router.js +252 -0
  38. package/{server.js → core/server.js} +15 -5
  39. package/package.json +43 -27
  40. package/cli.js +0 -195
  41. package/docs/BASIC_USAGE.md +0 -296
  42. package/docs/CLI.md +0 -455
  43. package/docs/ROUTER_GUIDE.md +0 -402
  44. package/index.js +0 -267
  45. package/router.js +0 -273
  46. package/test-completion.js +0 -99
  47. package/test.js +0 -246
  48. /package/{config.yaml → core/config.yaml} +0 -0
  49. /package/{logger.js → core/logger.js} +0 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * InMemory class for managing conversation history and data storage
3
+ * Provides methods to save, retrieve, and search conversation data
4
+ */
5
+ class InMemory {
6
+ /**
7
+ * Create a new Memory instance
8
+ */
9
+ constructor() {
10
+ this.messages = [];
11
+ }
12
+
13
+ /**
14
+ * Get session history for a specific resource and thread
15
+ * @param {string} resourceId - The resource identifier
16
+ * @param {string} threadId - The thread identifier
17
+ * @param {number} limit - Maximum number of messages to retrieve (default: 10)
18
+ * @returns {Promise<Array>} Array of messages in the session
19
+ */
20
+ async getSessionHistory(resourceId, threadId, limit = 10) {
21
+ // Filter messages by resourceId and threadId
22
+ const filtered = this.messages.filter(msg =>
23
+ msg.resourceId === resourceId &&
24
+ msg.threadId === threadId
25
+ );
26
+
27
+ // Sort by timestamp (newest first) and apply limit
28
+ const sorted = filtered
29
+ .sort((a, b) => a.timestamp - b.timestamp)
30
+ .slice(0, limit);
31
+
32
+ return sorted;
33
+ }
34
+
35
+ /**
36
+ * Search messages by query
37
+ * @param {string} query - Search query
38
+ * @param {string} resourceId - The resource identifier to search within
39
+ * @param {number} limit - Maximum number of results (default: 5)
40
+ * @returns {Promise<Array>} Array of matching messages
41
+ */
42
+ async search(query, resourceId, limit = 5) {
43
+ const queryLower = query.toLowerCase();
44
+
45
+ // Filter messages by resourceId and search content
46
+ const filtered = this.messages.filter(msg =>
47
+ msg.resourceId === resourceId &&
48
+ (msg.content.toLowerCase().includes(queryLower) ||
49
+ msg.role.toLowerCase().includes(queryLower))
50
+ );
51
+
52
+ // Sort by relevance (exact matches first) and apply limit
53
+ const sorted = filtered
54
+ .sort((a, b) => {
55
+ const aExact = a.content.toLowerCase() === queryLower ? 1 : 0;
56
+ const bExact = b.content.toLowerCase() === queryLower ? 1 : 0;
57
+ return bExact - aExact;
58
+ })
59
+ .slice(0, limit);
60
+
61
+ return sorted;
62
+ }
63
+
64
+ /**
65
+ * Save a message to memory
66
+ * @param {string} content - The message content
67
+ * @param {string} resourceId - The resource identifier
68
+ * @param {string} threadId - The thread identifier
69
+ * @param {string} role - The role (user, assistant, system, tool)
70
+ * @param {string} type - The message type
71
+ * @param {string} generationId - Optional generation identifier
72
+ * @param {string} remarks - Optional remarks
73
+ * @returns {Promise<Object>} The saved message object
74
+ */
75
+ async save(content, resourceId, threadId, role, type, generationId = null, remarks = null) {
76
+ const message = {
77
+ id: this.generateId(),
78
+ content: content,
79
+ resourceId: resourceId,
80
+ threadId: threadId,
81
+ role: role,
82
+ type: type,
83
+ generationId: generationId,
84
+ remarks: remarks,
85
+ timestamp: Date.now()
86
+ };
87
+
88
+ this.messages.push(message);
89
+
90
+ return message;
91
+ }
92
+
93
+ /**
94
+ * Generate a unique ID for messages
95
+ * @returns {string} Unique ID
96
+ */
97
+ generateId() {
98
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
99
+ }
100
+
101
+ /**
102
+ * Get all messages (for debugging/testing)
103
+ * @returns {Array} All messages
104
+ */
105
+ getAllMessages() {
106
+ return this.messages;
107
+ }
108
+
109
+ /**
110
+ * Clear all messages
111
+ */
112
+ clear() {
113
+ this.messages = [];
114
+ }
115
+ }
116
+
117
+ module.exports = { InMemory };
@@ -0,0 +1,36 @@
1
+ const { InMemory } = require('./in-memory');
2
+ const { LanceMemory } = require('./lance-memory');
3
+ const { SQLiteMemory } = require('./sqlite-memory');
4
+
5
+ /**
6
+ * Memory factory for creating different types of memory instances
7
+ */
8
+ const memory = {
9
+ /**
10
+ * Create a memory instance
11
+ * @param {Object} options - Memory configuration
12
+ * @param {string} options.type - Type of memory ('in-memory', 'lancedb', 'sqlite')
13
+ * @param {Object} options.config - Configuration for the specific memory type
14
+ * @returns {InMemory|LanceMemory|SQLiteMemory} Memory instance
15
+ */
16
+ create(options = {}) {
17
+ const { type = 'in-memory', config = {} } = options;
18
+
19
+ switch (type.toLowerCase()) {
20
+ case 'in-memory':
21
+ return new InMemory(config);
22
+
23
+ case 'lancedb':
24
+ case 'lance':
25
+ return new LanceMemory(config);
26
+
27
+ case 'sqlite':
28
+ return new SQLiteMemory(config);
29
+
30
+ default:
31
+ throw new Error(`Unknown memory type: ${type}. Supported types: in-memory, lancedb, sqlite`);
32
+ }
33
+ }
34
+ };
35
+
36
+ module.exports = { memory, InMemory, LanceMemory, SQLiteMemory };
@@ -0,0 +1,225 @@
1
+ const { InMemory } = require('./in-memory');
2
+ const lancedb = require('@lancedb/lancedb');
3
+ const { pipeline } = require('@huggingface/transformers');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const crypto = require('crypto');
7
+
8
+ // LanceDB schema will be inferred from first record
9
+ // but we ensure consistent types by providing proper values
10
+
11
+ /**
12
+ * LanceMemory class for managing conversation history with persistent storage
13
+ * Uses LanceDB for vector search and metadata storage
14
+ */
15
+ class LanceMemory extends InMemory {
16
+ /**
17
+ * Create a new LanceMemory instance
18
+ * @param {Object} options - Memory configuration
19
+ * @param {string} options.db - Database name (default: 'lance')
20
+ * @param {string} options.dir - Data directory (default: './lance_data')
21
+ * @param {string} options.model - Embedding model (default: 'onnx-community/Qwen3-Embedding-0.6B-ONNX')
22
+ */
23
+ constructor({ db = 'lance', dir = './lance_data', model = 'onnx-community/Qwen3-Embedding-0.6B-ONNX' } = {}) {
24
+ super();
25
+ this.db = db;
26
+ this.dir = dir;
27
+ this.model = model;
28
+
29
+ // Create directory if it doesn't exist
30
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
31
+
32
+ this.lanceTable = null;
33
+ this.extractor = null;
34
+ this.initialized = false;
35
+ }
36
+
37
+ /**
38
+ * Initialize the embedding model and LanceDB connection
39
+ */
40
+ async init() {
41
+ if (this.initialized) return;
42
+
43
+ this.extractor = await pipeline(
44
+ 'feature-extraction',
45
+ this.model,
46
+ { dtype: 'q8', quantized: true }
47
+ );
48
+
49
+ // Warm up the embedding model
50
+ for (let i = 0; i < 3; i++) {
51
+ await this.#getEmbedding("warming up warming up " + i + crypto.randomBytes(50).toString('hex'));
52
+ }
53
+
54
+ console.log("LanceMemory embedding is up");
55
+
56
+ const db = await lancedb.connect(path.join(this.dir, `${this.db}.lance`));
57
+ try {
58
+ this.lanceTable = await db.openTable(this.db);
59
+ } catch (e) {
60
+ this.lanceTable = null;
61
+ }
62
+
63
+ this.initialized = true;
64
+ }
65
+
66
+ /**
67
+ * Get embedding for a text
68
+ * @param {string} text - Text to embed
69
+ * @returns {Promise<Array>} Embedding vector
70
+ */
71
+ async #getEmbedding(text) {
72
+ const output = await this.extractor(text, { pooling: 'mean', normalize: true });
73
+ return Array.from(output.data);
74
+ }
75
+
76
+ /**
77
+ * Save a message to memory
78
+ * @param {string} content - The message content
79
+ * @param {string} resourceId - The resource identifier
80
+ * @param {string} threadId - The thread identifier
81
+ * @param {string} role - The role (user, assistant, system, tool)
82
+ * @param {string} type - The message type
83
+ * @param {string} generationId - Optional generation identifier
84
+ * @param {string} remarks - Optional remarks
85
+ * @returns {Promise<Object>} The saved message object
86
+ */
87
+ async save(content, resourceId, threadId, role, type, generationId = null, remarks = null) {
88
+ if (!this.initialized) await this.init();
89
+
90
+ // Generate unique ID
91
+ const id = `${resourceId}_${threadId}_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
92
+ const createdAt = new Date().toISOString();
93
+
94
+ console.time('getEmbedding for save');
95
+ const vector = await this.#getEmbedding(content);
96
+ console.timeEnd('getEmbedding for save');
97
+
98
+ // LanceDB Store - ensure all fields are properly typed
99
+ const record = {
100
+ id,
101
+ resourceId,
102
+ threadId,
103
+ role,
104
+ type,
105
+ vector,
106
+ content,
107
+ generationId: generationId || '',
108
+ remarks: remarks || '',
109
+ created_at: createdAt
110
+ };
111
+
112
+ if (!this.lanceTable) {
113
+ const db = await lancedb.connect(path.join(this.dir, `${this.db}.lance`));
114
+ this.lanceTable = await db.createTable(this.db, [record]);
115
+ } else {
116
+ await this.lanceTable.add([record]);
117
+ }
118
+
119
+ // Also add to in-memory cache
120
+ const message = {
121
+ id,
122
+ content,
123
+ resourceId,
124
+ threadId,
125
+ role,
126
+ type,
127
+ generationId,
128
+ remarks,
129
+ timestamp: Date.now()
130
+ };
131
+
132
+ this.messages.push(message);
133
+
134
+ return message;
135
+ }
136
+
137
+ /**
138
+ * Get session history for a specific resource and thread
139
+ * @param {string} resourceId - The resource identifier
140
+ * @param {string} threadId - The thread identifier
141
+ * @param {number} limit - Maximum number of messages to retrieve (default: 10)
142
+ * @returns {Promise<Array>} Array of messages in the session
143
+ */
144
+ async getSessionHistory(resourceId, threadId, limit = 10) {
145
+ if (!this.initialized) await this.init();
146
+ if (!this.lanceTable) return [];
147
+
148
+ const results = await this.lanceTable
149
+ .query()
150
+ .where(`resourceId = "${resourceId}" AND threadId = "${threadId}"`)
151
+ .limit(limit)
152
+ .toArray();
153
+
154
+ return results.map(res => ({
155
+ id: res.id,
156
+ content: res.content,
157
+ resourceId: res.resourceId,
158
+ threadId: res.threadId,
159
+ role: res.role,
160
+ type: res.type,
161
+ generationId: res.generationId,
162
+ remarks: res.remarks,
163
+ timestamp: new Date(res.created_at).getTime()
164
+ }));
165
+ }
166
+
167
+ /**
168
+ * Search messages by query
169
+ * @param {string} query - Search query
170
+ * @param {string} resourceId - The resource identifier to search within
171
+ * @param {number} limit - Maximum number of results (default: 5)
172
+ * @returns {Promise<Array>} Array of matching messages
173
+ */
174
+ async search(query, resourceId, limit = 5) {
175
+ if (!this.initialized) await this.init();
176
+ if (!this.lanceTable) return [];
177
+
178
+ console.time('getEmbedding for search');
179
+ const queryVector = await this.#getEmbedding(query);
180
+ console.timeEnd('getEmbedding for search');
181
+
182
+ const results = await this.lanceTable
183
+ .search(queryVector)
184
+ .where(`resourceId = "${resourceId}"`)
185
+ .limit(limit)
186
+ .toArray();
187
+
188
+ return results.map(res => ({
189
+ id: res.id,
190
+ content: res.content,
191
+ resourceId: res.resourceId,
192
+ threadId: res.threadId,
193
+ role: res.role,
194
+ type: res.type,
195
+ generationId: res.generationId,
196
+ remarks: res.remarks,
197
+ timestamp: new Date(res.created_at).getTime(),
198
+ distance: res._distance
199
+ }));
200
+ }
201
+
202
+ /**
203
+ * Clear all messages from memory and database
204
+ */
205
+ async clear() {
206
+ this.messages = [];
207
+
208
+ // Drop LanceDB table
209
+ if (this.lanceTable) {
210
+ await this.lanceTable.delete('1=1');
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Close the database connections and cleanup
216
+ */
217
+ async close() {
218
+ if (this.extractor) {
219
+ await this.extractor.cleanup();
220
+ }
221
+ this.initialized = false;
222
+ }
223
+ }
224
+
225
+ module.exports = { LanceMemory };
@@ -0,0 +1,309 @@
1
+ const { InMemory } = require('./in-memory');
2
+ const { pipeline } = require('@huggingface/transformers');
3
+
4
+ /**
5
+ * SQLiteMemory class for managing conversation history with SQLite storage
6
+ * Uses better-sqlite3, sqlite-vec, and HuggingFace transformers for vector search
7
+ */
8
+ class SQLiteMemory extends InMemory {
9
+ /**
10
+ * Create a new SQLiteMemory instance
11
+ * @param {Object} options - Memory configuration
12
+ * @param {string} options.db - Database file path (default: './sqlite_memory.db')
13
+ * @param {string} options.model - Embedding model (default: 'onnx-community/Qwen3-Embedding-0.6B-ONNX')
14
+ */
15
+ constructor({ db = './sqlite_memory.db', model = 'onnx-community/Qwen3-Embedding-0.6B-ONNX' } = {}) {
16
+ super();
17
+ this.dbPath = db;
18
+ this.model = model;
19
+ this.db = null;
20
+ this.extractor = null;
21
+ this.initialized = false;
22
+ }
23
+
24
+ /**
25
+ * Initialize the SQLite database, vector extensions, and embedding model
26
+ */
27
+ async init() {
28
+ if (this.initialized) return;
29
+
30
+ // Initialize embedding model
31
+ this.extractor = await pipeline(
32
+ 'feature-extraction',
33
+ this.model,
34
+ { dtype: 'q8', quantized: true }
35
+ );
36
+
37
+ // Warm up the embedding model
38
+ for (let i = 0; i < 3; i++) {
39
+ await this._getEmbedding("warming up warming up " + i + Math.random().toString(36));
40
+ }
41
+
42
+ console.log("SQLiteMemory embedding is up");
43
+
44
+ const Database = require('better-sqlite3');
45
+ this.db = new Database(this.dbPath);
46
+
47
+ // Enable vector extension if available
48
+ try {
49
+ this.db.loadExtension(require('sqlite-vec'));
50
+ // Create vector table for embeddings
51
+ this.db.exec(`
52
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_messages USING vec0(
53
+ id TEXT PRIMARY KEY,
54
+ vector float[768]
55
+ );
56
+ `);
57
+ } catch (error) {
58
+ console.warn('sqlite-vec extension not available, falling back to basic search');
59
+ }
60
+
61
+ // Create main messages table
62
+ this.db.exec(`
63
+ CREATE TABLE IF NOT EXISTS messages (
64
+ id TEXT PRIMARY KEY,
65
+ resourceId TEXT,
66
+ threadId TEXT,
67
+ role TEXT,
68
+ type TEXT,
69
+ content TEXT,
70
+ generationId TEXT,
71
+ remarks TEXT,
72
+ vector BLOB,
73
+ created_at TEXT
74
+ );
75
+
76
+ CREATE INDEX IF NOT EXISTS idx_resource_thread ON messages(resourceId, threadId);
77
+ CREATE INDEX IF NOT EXISTS idx_created_at ON messages(created_at);
78
+ `);
79
+
80
+ // Prepare statements
81
+ this.insertStmt = this.db.prepare(`
82
+ INSERT OR REPLACE INTO messages (id, resourceId, threadId, role, type, content, generationId, remarks, vector, created_at)
83
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
84
+ `);
85
+
86
+ this.selectStmt = this.db.prepare(`
87
+ SELECT * FROM messages
88
+ WHERE resourceId = ? AND threadId = ?
89
+ ORDER BY created_at DESC
90
+ LIMIT ?
91
+ `);
92
+
93
+ this.searchStmt = this.db.prepare(`
94
+ SELECT * FROM messages
95
+ WHERE resourceId = ?
96
+ ORDER BY created_at DESC
97
+ LIMIT ?
98
+ `);
99
+
100
+ this.deleteStmt = this.db.prepare('DELETE FROM messages');
101
+
102
+ // Vector search statements (if sqlite-vec is available)
103
+ if (this.db.loadExtension) {
104
+ try {
105
+ this.vectorInsertStmt = this.db.prepare(`
106
+ INSERT OR REPLACE INTO vec_messages (id, vector) VALUES (?, ?)
107
+ `);
108
+
109
+ this.vectorSearchStmt = this.db.prepare(`
110
+ SELECT id, distance
111
+ FROM vec_messages
112
+ WHERE vector MATCH ? AND k = ?
113
+ ORDER BY distance
114
+ `);
115
+ } catch (error) {
116
+ console.warn('Vector search not available:', error.message);
117
+ }
118
+ }
119
+
120
+ this.initialized = true;
121
+ }
122
+
123
+ /**
124
+ * Get embedding for a text
125
+ * @param {string} text - Text to embed
126
+ * @returns {Promise<Array>} Embedding vector
127
+ */
128
+ async _getEmbedding(text) {
129
+ const output = await this.extractor(text, { pooling: 'mean', normalize: true });
130
+ return Array.from(output.data);
131
+ }
132
+
133
+ /**
134
+ * Save a message to memory
135
+ * @param {string} content - The message content
136
+ * @param {string} resourceId - The resource identifier
137
+ * @param {string} threadId - The thread identifier
138
+ * @param {string} role - The role (user, assistant, system, tool)
139
+ * @param {string} type - The message type
140
+ * @param {string} generationId - Optional generation identifier
141
+ * @param {string} remarks - Optional remarks
142
+ * @returns {Promise<Object>} The saved message object
143
+ */
144
+ async save(content, resourceId, threadId, role, type, generationId = null, remarks = null) {
145
+ if (!this.initialized) await this.init();
146
+
147
+ const id = `${resourceId}_${threadId}_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
148
+ const createdAt = new Date().toISOString();
149
+
150
+ console.time('getEmbedding for save');
151
+ const vector = await this._getEmbedding(content);
152
+ console.timeEnd('getEmbedding for save');
153
+
154
+ // Store vector as JSON for basic storage, and also in vector table if available
155
+ const vectorJson = JSON.stringify(vector);
156
+
157
+ this.insertStmt.run(
158
+ id, resourceId, threadId, role, type, content,
159
+ generationId || '', remarks || '', vectorJson, createdAt
160
+ );
161
+
162
+ // Store in vector table if available
163
+ if (this.vectorInsertStmt) {
164
+ try {
165
+ this.vectorInsertStmt.run(id, vector);
166
+ } catch (error) {
167
+ console.warn('Failed to store vector:', error.message);
168
+ }
169
+ }
170
+
171
+ const message = {
172
+ id,
173
+ content,
174
+ resourceId,
175
+ threadId,
176
+ role,
177
+ type,
178
+ generationId,
179
+ remarks,
180
+ timestamp: Date.now()
181
+ };
182
+
183
+ // Also add to in-memory cache for faster access
184
+ this.messages.push(message);
185
+
186
+ return message;
187
+ }
188
+
189
+ /**
190
+ * Get session history for a specific resource and thread
191
+ * @param {string} resourceId - The resource identifier
192
+ * @param {string} threadId - The thread identifier
193
+ * @param {number} limit - Maximum number of messages to retrieve (default: 10)
194
+ * @returns {Promise<Array>} Array of messages in the session
195
+ */
196
+ async getSessionHistory(resourceId, threadId, limit = 10) {
197
+ if (!this.initialized) await this.init();
198
+
199
+ const rows = this.selectStmt.all(resourceId, threadId, limit);
200
+ return rows.map(row => ({
201
+ id: row.id,
202
+ content: row.content,
203
+ resourceId: row.resourceId,
204
+ threadId: row.threadId,
205
+ role: row.role,
206
+ type: row.type,
207
+ generationId: row.generationId,
208
+ remarks: row.remarks,
209
+ timestamp: new Date(row.created_at).getTime()
210
+ }));
211
+ }
212
+
213
+ /**
214
+ * Search messages by query using vector search when available
215
+ * @param {string} query - Search query
216
+ * @param {string} resourceId - The resource identifier to search within
217
+ * @param {number} limit - Maximum number of results (default: 5)
218
+ * @returns {Promise<Array>} Array of matching messages
219
+ */
220
+ async search(query, resourceId, limit = 5) {
221
+ if (!this.initialized) await this.init();
222
+
223
+ // Try vector search first if available
224
+ if (this.vectorSearchStmt) {
225
+ try {
226
+ console.time('getEmbedding for search');
227
+ const queryVector = await this._getEmbedding(query);
228
+ console.timeEnd('getEmbedding for search');
229
+
230
+ const vectorResults = this.vectorSearchStmt.all(JSON.stringify(queryVector), limit);
231
+
232
+ if (vectorResults && vectorResults.length > 0) {
233
+ // Get full message details for the vector search results
234
+ const ids = vectorResults.map(r => r.id);
235
+ const messages = [];
236
+
237
+ for (const id of ids) {
238
+ const row = this.db.prepare('SELECT * FROM messages WHERE id = ?').get(id);
239
+ if (row) {
240
+ messages.push({
241
+ id: row.id,
242
+ content: row.content,
243
+ resourceId: row.resourceId,
244
+ threadId: row.threadId,
245
+ role: row.role,
246
+ type: row.type,
247
+ generationId: row.generationId,
248
+ remarks: row.remarks,
249
+ timestamp: new Date(row.created_at).getTime(),
250
+ distance: vectorResults.find(r => r.id === id)?.distance
251
+ });
252
+ }
253
+ }
254
+
255
+ return messages;
256
+ }
257
+ } catch (error) {
258
+ console.warn('Vector search failed, falling back to text search:', error.message);
259
+ }
260
+ }
261
+
262
+ // Fallback to basic text search
263
+ const queryLower = query.toLowerCase();
264
+ const rows = this.searchStmt.all(resourceId, limit * 2); // Get more to filter
265
+
266
+ const filtered = rows.filter(row =>
267
+ row.content.toLowerCase().includes(queryLower) ||
268
+ row.role.toLowerCase().includes(queryLower)
269
+ );
270
+
271
+ return filtered.slice(0, limit).map(row => ({
272
+ id: row.id,
273
+ content: row.content,
274
+ resourceId: row.resourceId,
275
+ threadId: row.threadId,
276
+ role: row.role,
277
+ type: row.type,
278
+ generationId: row.generationId,
279
+ remarks: row.remarks,
280
+ timestamp: new Date(row.created_at).getTime()
281
+ }));
282
+ }
283
+
284
+ /**
285
+ * Clear all messages from memory and database
286
+ */
287
+ async clear() {
288
+ this.messages = [];
289
+
290
+ if (this.db) {
291
+ this.deleteStmt.run();
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Close the database connection and cleanup embedding model
297
+ */
298
+ async close() {
299
+ if (this.extractor) {
300
+ await this.extractor.cleanup();
301
+ }
302
+ if (this.db) {
303
+ this.db.close();
304
+ }
305
+ this.initialized = false;
306
+ }
307
+ }
308
+
309
+ module.exports = { SQLiteMemory };