@yesvara/svara 0.1.0 → 0.1.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.
- package/CONTRIBUTING.md +233 -0
- package/README.md +172 -22
- package/dist/{chunk-FEA5KIJN.mjs → chunk-WA3BIA3S.mjs} +356 -27
- package/dist/index.d.mts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +454 -269
- package/dist/index.mjs +81 -239
- package/dist/{retriever-4QY667XF.mjs → retriever-OTPBECGO.mjs} +1 -1
- package/package.json +27 -15
- package/src/core/agent.ts +89 -8
- package/src/core/types.ts +14 -1
- package/src/database/schema.ts +31 -6
- package/src/rag/chunker.ts +1 -0
- package/src/rag/retriever.ts +146 -33
- package/svara@1.0.0 +0 -0
- package/test-rag.ts +20 -0
- package/tsx +0 -0
package/dist/index.js
CHANGED
|
@@ -30,13 +30,274 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// src/database/schema.ts
|
|
34
|
+
var SCHEMA_VERSION, CREATE_TABLES_SQL, INSERT_META_SQL;
|
|
35
|
+
var init_schema = __esm({
|
|
36
|
+
"src/database/schema.ts"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
SCHEMA_VERSION = 1;
|
|
39
|
+
CREATE_TABLES_SQL = `
|
|
40
|
+
-- Schema version tracking
|
|
41
|
+
CREATE TABLE IF NOT EXISTS svara_meta (
|
|
42
|
+
key TEXT PRIMARY KEY,
|
|
43
|
+
value TEXT NOT NULL
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Conversation history persistence
|
|
47
|
+
CREATE TABLE IF NOT EXISTS svara_messages (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
session_id TEXT NOT NULL,
|
|
50
|
+
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system', 'tool')),
|
|
51
|
+
content TEXT NOT NULL,
|
|
52
|
+
tool_call_id TEXT,
|
|
53
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
57
|
+
ON svara_messages (session_id, created_at);
|
|
58
|
+
|
|
59
|
+
-- User registry
|
|
60
|
+
CREATE TABLE IF NOT EXISTS svara_users (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
email TEXT,
|
|
63
|
+
display_name TEXT,
|
|
64
|
+
first_seen INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
65
|
+
last_seen INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
66
|
+
metadata TEXT DEFAULT '{}'
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_users_email
|
|
70
|
+
ON svara_users (email);
|
|
71
|
+
|
|
72
|
+
-- Session metadata
|
|
73
|
+
CREATE TABLE IF NOT EXISTS svara_sessions (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
user_id TEXT NOT NULL,
|
|
76
|
+
channel TEXT NOT NULL,
|
|
77
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
78
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
79
|
+
metadata TEXT DEFAULT '{}',
|
|
80
|
+
FOREIGN KEY (user_id) REFERENCES svara_users(id)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user
|
|
84
|
+
ON svara_sessions (user_id);
|
|
85
|
+
|
|
86
|
+
-- Vector store chunks for RAG (per agent)
|
|
87
|
+
CREATE TABLE IF NOT EXISTS svara_chunks (
|
|
88
|
+
id TEXT PRIMARY KEY,
|
|
89
|
+
agent_name TEXT NOT NULL, -- Separate RAG per agent
|
|
90
|
+
document_id TEXT NOT NULL,
|
|
91
|
+
content TEXT NOT NULL,
|
|
92
|
+
content_hash TEXT NOT NULL, -- MD5 hash of content for deduplication
|
|
93
|
+
chunk_index INTEGER NOT NULL,
|
|
94
|
+
embedding TEXT, -- stored as JSON string of float array
|
|
95
|
+
source TEXT NOT NULL,
|
|
96
|
+
metadata TEXT DEFAULT '{}',
|
|
97
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_agent
|
|
101
|
+
ON svara_chunks (agent_name);
|
|
102
|
+
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_agent_document
|
|
104
|
+
ON svara_chunks (agent_name, document_id);
|
|
105
|
+
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_chunks_content_hash
|
|
107
|
+
ON svara_chunks (content_hash);
|
|
108
|
+
|
|
109
|
+
-- Document registry
|
|
110
|
+
CREATE TABLE IF NOT EXISTS svara_documents (
|
|
111
|
+
id TEXT PRIMARY KEY,
|
|
112
|
+
source TEXT NOT NULL UNIQUE,
|
|
113
|
+
type TEXT NOT NULL,
|
|
114
|
+
size INTEGER,
|
|
115
|
+
hash TEXT,
|
|
116
|
+
indexed_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
117
|
+
metadata TEXT DEFAULT '{}'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
-- Key-value store for arbitrary agent state
|
|
121
|
+
CREATE TABLE IF NOT EXISTS svara_kv (
|
|
122
|
+
key TEXT PRIMARY KEY,
|
|
123
|
+
value TEXT NOT NULL,
|
|
124
|
+
expires_at INTEGER, -- unix timestamp, NULL = no expiry
|
|
125
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
126
|
+
);
|
|
127
|
+
`;
|
|
128
|
+
INSERT_META_SQL = `
|
|
129
|
+
INSERT OR REPLACE INTO svara_meta (key, value)
|
|
130
|
+
VALUES ('schema_version', ?), ('created_at', ?);
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// src/database/sqlite.ts
|
|
136
|
+
var import_path, import_fs, KVStore, SvaraDB;
|
|
137
|
+
var init_sqlite = __esm({
|
|
138
|
+
"src/database/sqlite.ts"() {
|
|
139
|
+
"use strict";
|
|
140
|
+
import_path = __toESM(require("path"));
|
|
141
|
+
import_fs = __toESM(require("fs"));
|
|
142
|
+
init_schema();
|
|
143
|
+
KVStore = class {
|
|
144
|
+
constructor(db) {
|
|
145
|
+
this.db = db;
|
|
146
|
+
}
|
|
147
|
+
db;
|
|
148
|
+
/** Set a key-value pair, with optional TTL in seconds. */
|
|
149
|
+
set(key, value, ttlSeconds) {
|
|
150
|
+
const expiresAt = ttlSeconds ? Math.floor(Date.now() / 1e3) + ttlSeconds : null;
|
|
151
|
+
this.db.prepare(`
|
|
152
|
+
INSERT OR REPLACE INTO svara_kv (key, value, expires_at, updated_at)
|
|
153
|
+
VALUES (?, ?, ?, unixepoch())
|
|
154
|
+
`).run(key, JSON.stringify(value), expiresAt);
|
|
155
|
+
}
|
|
156
|
+
/** Get a value by key. Returns undefined if not found or expired. */
|
|
157
|
+
get(key) {
|
|
158
|
+
const row = this.db.prepare(`
|
|
159
|
+
SELECT value, expires_at FROM svara_kv
|
|
160
|
+
WHERE key = ? AND (expires_at IS NULL OR expires_at > unixepoch())
|
|
161
|
+
`).get(key);
|
|
162
|
+
if (!row) return void 0;
|
|
163
|
+
return JSON.parse(row.value);
|
|
164
|
+
}
|
|
165
|
+
/** Delete a key. */
|
|
166
|
+
delete(key) {
|
|
167
|
+
this.db.prepare("DELETE FROM svara_kv WHERE key = ?").run(key);
|
|
168
|
+
}
|
|
169
|
+
/** Check if a key exists and is not expired. */
|
|
170
|
+
has(key) {
|
|
171
|
+
return this.get(key) !== void 0;
|
|
172
|
+
}
|
|
173
|
+
/** Get all keys matching a prefix. */
|
|
174
|
+
keys(prefix = "") {
|
|
175
|
+
const rows = this.db.prepare(`
|
|
176
|
+
SELECT key FROM svara_kv
|
|
177
|
+
WHERE key LIKE ? AND (expires_at IS NULL OR expires_at > unixepoch())
|
|
178
|
+
`).all(`${prefix}%`);
|
|
179
|
+
return rows.map((r) => r.key);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
SvaraDB = class {
|
|
183
|
+
db;
|
|
184
|
+
kv;
|
|
185
|
+
constructor(dbPath = ":memory:") {
|
|
186
|
+
if (dbPath !== ":memory:") {
|
|
187
|
+
import_fs.default.mkdirSync(import_path.default.dirname(import_path.default.resolve(dbPath)), { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
this.db = this.openDatabase(dbPath);
|
|
190
|
+
this.configure();
|
|
191
|
+
this.migrate();
|
|
192
|
+
this.kv = new KVStore(this.db);
|
|
193
|
+
}
|
|
194
|
+
// ─── Query Helpers ────────────────────────────────────────────────────────
|
|
195
|
+
/**
|
|
196
|
+
* Run a SELECT and return all matching rows.
|
|
197
|
+
*/
|
|
198
|
+
query(sql, params = []) {
|
|
199
|
+
return this.db.prepare(sql).all(...params);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Run a SELECT and return the first matching row.
|
|
203
|
+
*/
|
|
204
|
+
queryOne(sql, params = []) {
|
|
205
|
+
return this.db.prepare(sql).get(...params);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Run an INSERT/UPDATE/DELETE. Returns affected row count.
|
|
209
|
+
*/
|
|
210
|
+
run(sql, params = []) {
|
|
211
|
+
return this.db.prepare(sql).run(...params).changes;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Execute raw SQL (for DDL, migrations, etc.).
|
|
215
|
+
*/
|
|
216
|
+
exec(sql) {
|
|
217
|
+
this.db.exec(sql);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Run multiple operations in a single transaction.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* db.transaction(() => {
|
|
224
|
+
* db.run('INSERT INTO orders ...', [...]);
|
|
225
|
+
* db.run('UPDATE inventory ...', [...]);
|
|
226
|
+
* });
|
|
227
|
+
*/
|
|
228
|
+
transaction(fn) {
|
|
229
|
+
return this.db.transaction(fn)();
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Close the database connection.
|
|
233
|
+
*/
|
|
234
|
+
close() {
|
|
235
|
+
this.db.close();
|
|
236
|
+
}
|
|
237
|
+
// ─── Internal Message Storage ─────────────────────────────────────────────
|
|
238
|
+
saveMessage(params) {
|
|
239
|
+
this.db.prepare(`
|
|
240
|
+
INSERT OR REPLACE INTO svara_messages (id, session_id, role, content, tool_call_id)
|
|
241
|
+
VALUES (?, ?, ?, ?, ?)
|
|
242
|
+
`).run(
|
|
243
|
+
params.id,
|
|
244
|
+
params.sessionId,
|
|
245
|
+
params.role,
|
|
246
|
+
params.content,
|
|
247
|
+
params.toolCallId ?? null
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
getMessages(sessionId, limit = 50) {
|
|
251
|
+
return this.db.prepare(`
|
|
252
|
+
SELECT id, role, content, tool_call_id, created_at
|
|
253
|
+
FROM svara_messages
|
|
254
|
+
WHERE session_id = ?
|
|
255
|
+
ORDER BY created_at ASC
|
|
256
|
+
LIMIT ?
|
|
257
|
+
`).all(sessionId, limit);
|
|
258
|
+
}
|
|
259
|
+
clearSession(sessionId) {
|
|
260
|
+
this.db.prepare("DELETE FROM svara_messages WHERE session_id = ?").run(sessionId);
|
|
261
|
+
}
|
|
262
|
+
// ─── Private Setup ────────────────────────────────────────────────────────
|
|
263
|
+
openDatabase(dbPath) {
|
|
264
|
+
try {
|
|
265
|
+
const Database = require("better-sqlite3");
|
|
266
|
+
return new Database(dbPath);
|
|
267
|
+
} catch {
|
|
268
|
+
throw new Error(
|
|
269
|
+
'[SvaraJS] Database requires the "better-sqlite3" package.\nRun: npm install better-sqlite3'
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
configure() {
|
|
274
|
+
this.db.pragma("journal_mode = WAL");
|
|
275
|
+
this.db.pragma("synchronous = NORMAL");
|
|
276
|
+
this.db.pragma("foreign_keys = ON");
|
|
277
|
+
}
|
|
278
|
+
migrate() {
|
|
279
|
+
this.db.exec(CREATE_TABLES_SQL);
|
|
280
|
+
const meta = this.db.prepare(
|
|
281
|
+
"SELECT value FROM svara_meta WHERE key = 'schema_version'"
|
|
282
|
+
).get();
|
|
283
|
+
if (!meta) {
|
|
284
|
+
this.db.prepare(INSERT_META_SQL).run(
|
|
285
|
+
String(SCHEMA_VERSION),
|
|
286
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
33
294
|
// src/rag/loader.ts
|
|
34
|
-
var import_promises,
|
|
295
|
+
var import_promises, import_path2, import_crypto, TextFileLoader, JsonFileLoader, HtmlFileLoader, PdfFileLoader, DocxFileLoader, DocumentLoader;
|
|
35
296
|
var init_loader = __esm({
|
|
36
297
|
"src/rag/loader.ts"() {
|
|
37
298
|
"use strict";
|
|
38
299
|
import_promises = __toESM(require("fs/promises"));
|
|
39
|
-
|
|
300
|
+
import_path2 = __toESM(require("path"));
|
|
40
301
|
import_crypto = __toESM(require("crypto"));
|
|
41
302
|
TextFileLoader = class {
|
|
42
303
|
extensions = [".txt", ".md", ".mdx", ".rst", ".csv", ".log"];
|
|
@@ -48,7 +309,7 @@ var init_loader = __esm({
|
|
|
48
309
|
extensions = [".json", ".jsonl"];
|
|
49
310
|
async load(filePath) {
|
|
50
311
|
const raw = await import_promises.default.readFile(filePath, "utf-8");
|
|
51
|
-
if (
|
|
312
|
+
if (import_path2.default.extname(filePath) === ".jsonl") {
|
|
52
313
|
return raw.split("\n").filter(Boolean).map((line) => {
|
|
53
314
|
const obj = JSON.parse(line);
|
|
54
315
|
return Object.values(obj).join(" ");
|
|
@@ -116,7 +377,7 @@ var init_loader = __esm({
|
|
|
116
377
|
* Load a single file into a Document.
|
|
117
378
|
*/
|
|
118
379
|
async load(filePath) {
|
|
119
|
-
const ext =
|
|
380
|
+
const ext = import_path2.default.extname(filePath).toLowerCase();
|
|
120
381
|
const loader = this.extensionMap.get(ext);
|
|
121
382
|
if (!loader) {
|
|
122
383
|
throw new Error(
|
|
@@ -131,7 +392,7 @@ var init_loader = __esm({
|
|
|
131
392
|
type: this.detectType(ext),
|
|
132
393
|
source: filePath,
|
|
133
394
|
metadata: {
|
|
134
|
-
filename:
|
|
395
|
+
filename: import_path2.default.basename(filePath),
|
|
135
396
|
extension: ext,
|
|
136
397
|
size: stats.size,
|
|
137
398
|
lastModified: stats.mtime.toISOString()
|
|
@@ -155,7 +416,7 @@ var init_loader = __esm({
|
|
|
155
416
|
}
|
|
156
417
|
/** Check if this loader supports a given file extension. */
|
|
157
418
|
supports(filePath) {
|
|
158
|
-
const ext =
|
|
419
|
+
const ext = import_path2.default.extname(filePath).toLowerCase();
|
|
159
420
|
return this.extensionMap.has(ext);
|
|
160
421
|
}
|
|
161
422
|
detectType(ext) {
|
|
@@ -218,6 +479,7 @@ var init_chunker = __esm({
|
|
|
218
479
|
id: this.chunkId(document.id, index),
|
|
219
480
|
documentId: document.id,
|
|
220
481
|
content: content.trim(),
|
|
482
|
+
source: document.source,
|
|
221
483
|
index,
|
|
222
484
|
metadata: {
|
|
223
485
|
...document.metadata,
|
|
@@ -313,12 +575,14 @@ function cosineSimilarity(a, b) {
|
|
|
313
575
|
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
314
576
|
return denominator === 0 ? 0 : dot / denominator;
|
|
315
577
|
}
|
|
316
|
-
var OpenAIEmbeddings, OllamaEmbeddings,
|
|
578
|
+
var import_crypto3, OpenAIEmbeddings, OllamaEmbeddings, VectorStore, PersistentVectorStore, VectorRetriever;
|
|
317
579
|
var init_retriever = __esm({
|
|
318
580
|
"src/rag/retriever.ts"() {
|
|
319
581
|
"use strict";
|
|
320
582
|
init_loader();
|
|
321
583
|
init_chunker();
|
|
584
|
+
init_sqlite();
|
|
585
|
+
import_crypto3 = __toESM(require("crypto"));
|
|
322
586
|
OpenAIEmbeddings = class {
|
|
323
587
|
client;
|
|
324
588
|
model;
|
|
@@ -373,25 +637,95 @@ var init_retriever = __esm({
|
|
|
373
637
|
return data.embedding;
|
|
374
638
|
}
|
|
375
639
|
};
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const existing = this.entries.findIndex((e) => e.chunk.id === chunk.id);
|
|
380
|
-
if (existing >= 0) {
|
|
381
|
-
this.entries[existing] = { chunk, embedding };
|
|
382
|
-
} else {
|
|
383
|
-
this.entries.push({ chunk, embedding });
|
|
384
|
-
}
|
|
640
|
+
VectorStore = class {
|
|
641
|
+
contentHash(content) {
|
|
642
|
+
return import_crypto3.default.createHash("md5").update(content).digest("hex");
|
|
385
643
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
|
|
644
|
+
};
|
|
645
|
+
PersistentVectorStore = class extends VectorStore {
|
|
646
|
+
constructor(db, agentName) {
|
|
647
|
+
super();
|
|
648
|
+
this.db = db;
|
|
649
|
+
this.agentName = agentName;
|
|
650
|
+
}
|
|
651
|
+
db;
|
|
652
|
+
agentName;
|
|
653
|
+
async add(chunk, embedding) {
|
|
654
|
+
const contentHash = this.contentHash(chunk.content);
|
|
655
|
+
const existing = this.db.query(
|
|
656
|
+
"SELECT id FROM svara_chunks WHERE agent_name = ? AND content_hash = ?",
|
|
657
|
+
[this.agentName, contentHash]
|
|
658
|
+
);
|
|
659
|
+
if (existing.length > 0) {
|
|
660
|
+
console.log(`[SvaraJS:RAG] Duplicate content detected for ${this.agentName}, skipping chunk ${chunk.id}`);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const embeddingJson = JSON.stringify(embedding);
|
|
664
|
+
this.db.run(
|
|
665
|
+
`INSERT OR REPLACE INTO svara_chunks
|
|
666
|
+
(id, agent_name, document_id, content, content_hash, chunk_index, embedding, source, metadata)
|
|
667
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
668
|
+
[
|
|
669
|
+
chunk.id,
|
|
670
|
+
this.agentName,
|
|
671
|
+
chunk.documentId,
|
|
672
|
+
chunk.content,
|
|
673
|
+
contentHash,
|
|
674
|
+
chunk.index,
|
|
675
|
+
embeddingJson,
|
|
676
|
+
chunk.source,
|
|
677
|
+
JSON.stringify(chunk.metadata)
|
|
678
|
+
]
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
async search(queryEmbedding, topK, threshold = 0) {
|
|
682
|
+
const rows = this.db.query(
|
|
683
|
+
"SELECT id, document_id, content, chunk_index, embedding, source, metadata FROM svara_chunks WHERE agent_name = ? ORDER BY id DESC",
|
|
684
|
+
[this.agentName]
|
|
685
|
+
);
|
|
686
|
+
const scored = rows.map((row) => {
|
|
687
|
+
const embedding = JSON.parse(row.embedding);
|
|
688
|
+
return {
|
|
689
|
+
chunk: {
|
|
690
|
+
id: row.id,
|
|
691
|
+
documentId: row.document_id,
|
|
692
|
+
content: row.content,
|
|
693
|
+
index: row.chunk_index,
|
|
694
|
+
source: row.source,
|
|
695
|
+
metadata: JSON.parse(row.metadata)
|
|
696
|
+
},
|
|
697
|
+
score: cosineSimilarity(queryEmbedding, embedding)
|
|
698
|
+
};
|
|
699
|
+
}).filter((s) => s.score >= threshold).sort((a, b) => b.score - a.score).slice(0, topK);
|
|
700
|
+
return scored.map((s) => s.chunk);
|
|
701
|
+
}
|
|
702
|
+
async searchWithScores(queryEmbedding, topK, threshold = 0) {
|
|
703
|
+
const rows = this.db.query(
|
|
704
|
+
"SELECT id, document_id, content, chunk_index, embedding, source, metadata FROM svara_chunks WHERE agent_name = ? ORDER BY id DESC",
|
|
705
|
+
[this.agentName]
|
|
706
|
+
);
|
|
707
|
+
const scored = rows.map((row) => {
|
|
708
|
+
const embedding = JSON.parse(row.embedding);
|
|
709
|
+
return {
|
|
710
|
+
chunk: {
|
|
711
|
+
id: row.id,
|
|
712
|
+
documentId: row.document_id,
|
|
713
|
+
content: row.content,
|
|
714
|
+
index: row.chunk_index,
|
|
715
|
+
source: row.source,
|
|
716
|
+
metadata: JSON.parse(row.metadata)
|
|
717
|
+
},
|
|
718
|
+
score: cosineSimilarity(queryEmbedding, embedding)
|
|
719
|
+
};
|
|
720
|
+
}).filter((s) => s.score >= threshold).sort((a, b) => b.score - a.score).slice(0, topK);
|
|
721
|
+
return scored;
|
|
722
|
+
}
|
|
723
|
+
async size() {
|
|
724
|
+
const result = this.db.query(
|
|
725
|
+
"SELECT COUNT(*) as count FROM svara_chunks WHERE agent_name = ?",
|
|
726
|
+
[this.agentName]
|
|
727
|
+
);
|
|
728
|
+
return result[0]?.count ?? 0;
|
|
395
729
|
}
|
|
396
730
|
};
|
|
397
731
|
VectorRetriever = class {
|
|
@@ -400,13 +734,17 @@ var init_retriever = __esm({
|
|
|
400
734
|
loader;
|
|
401
735
|
chunker;
|
|
402
736
|
config;
|
|
403
|
-
|
|
404
|
-
|
|
737
|
+
db;
|
|
738
|
+
agentName;
|
|
739
|
+
constructor(agentName, db) {
|
|
740
|
+
this.agentName = agentName;
|
|
405
741
|
this.loader = new DocumentLoader();
|
|
406
742
|
this.chunker = new Chunker();
|
|
743
|
+
this.db = db || new SvaraDB("./data/svara.db");
|
|
407
744
|
}
|
|
408
745
|
async init(config) {
|
|
409
746
|
this.config = config;
|
|
747
|
+
this.store = new PersistentVectorStore(this.db, this.agentName);
|
|
410
748
|
if (config.chunking) {
|
|
411
749
|
this.chunker = new Chunker({
|
|
412
750
|
strategy: config.chunking.strategy ?? "sentence",
|
|
@@ -435,15 +773,17 @@ var init_retriever = __esm({
|
|
|
435
773
|
console.log(`[SvaraJS:RAG] Embedding ${chunks.length} chunk(s)...`);
|
|
436
774
|
const embeddings = await this.embedder.embed(chunks.map((c) => c.content));
|
|
437
775
|
for (let i = 0; i < chunks.length; i++) {
|
|
438
|
-
this.store.add(chunks[i], embeddings[i]);
|
|
776
|
+
await this.store.add(chunks[i], embeddings[i]);
|
|
439
777
|
}
|
|
440
|
-
|
|
778
|
+
const size = await this.store.size();
|
|
779
|
+
console.log(`[SvaraJS:RAG] Vector store now has ${size} chunk(s).`);
|
|
441
780
|
}
|
|
442
781
|
async retrieve(query, topK = 5) {
|
|
443
|
-
|
|
782
|
+
const size = await this.store.size();
|
|
783
|
+
if (size === 0) return "";
|
|
444
784
|
const queryEmbedding = await this.embedder.embedOne(query);
|
|
445
785
|
const threshold = this.config.retrieval?.threshold ?? 0.3;
|
|
446
|
-
const chunks = this.store.search(queryEmbedding, topK, threshold);
|
|
786
|
+
const chunks = await this.store.search(queryEmbedding, topK, threshold);
|
|
447
787
|
if (!chunks.length) return "";
|
|
448
788
|
return chunks.map((chunk, i) => `[Context ${i + 1}]
|
|
449
789
|
Source: ${String(chunk.metadata.filename ?? chunk.documentId)}
|
|
@@ -452,11 +792,11 @@ ${chunk.content}`).join("\n\n---\n\n");
|
|
|
452
792
|
async retrieveChunks(query, topK = 5) {
|
|
453
793
|
const queryEmbedding = await this.embedder.embedOne(query);
|
|
454
794
|
const threshold = this.config.retrieval?.threshold ?? 0.3;
|
|
455
|
-
const
|
|
795
|
+
const chunksWithScores = await this.store.searchWithScores(queryEmbedding, topK, threshold);
|
|
456
796
|
return {
|
|
457
|
-
chunks,
|
|
797
|
+
chunks: chunksWithScores,
|
|
458
798
|
query,
|
|
459
|
-
totalFound:
|
|
799
|
+
totalFound: chunksWithScores.length
|
|
460
800
|
};
|
|
461
801
|
}
|
|
462
802
|
};
|
|
@@ -1294,6 +1634,9 @@ var ToolRegistry = class {
|
|
|
1294
1634
|
}
|
|
1295
1635
|
};
|
|
1296
1636
|
|
|
1637
|
+
// src/core/agent.ts
|
|
1638
|
+
init_sqlite();
|
|
1639
|
+
|
|
1297
1640
|
// src/tools/executor.ts
|
|
1298
1641
|
var ToolExecutor = class {
|
|
1299
1642
|
constructor(registry) {
|
|
@@ -1354,13 +1697,17 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1354
1697
|
verbose;
|
|
1355
1698
|
channels = /* @__PURE__ */ new Map();
|
|
1356
1699
|
knowledgeBase = null;
|
|
1700
|
+
retriever = null;
|
|
1701
|
+
// Store VectorRetriever for retrieveChunks access
|
|
1357
1702
|
knowledgePaths = [];
|
|
1358
1703
|
isStarted = false;
|
|
1704
|
+
db;
|
|
1359
1705
|
constructor(config) {
|
|
1360
1706
|
super();
|
|
1361
1707
|
this.name = config.name;
|
|
1362
1708
|
this.maxIterations = config.maxIterations ?? 10;
|
|
1363
1709
|
this.verbose = config.verbose ?? false;
|
|
1710
|
+
this.db = new SvaraDB("./data/svara.db");
|
|
1364
1711
|
this.systemPrompt = config.systemPrompt ?? `You are ${config.name}, a helpful and friendly AI assistant. Be concise and accurate.`;
|
|
1365
1712
|
this.llmConfig = resolveConfig(config.model, {
|
|
1366
1713
|
temperature: config.temperature,
|
|
@@ -1479,7 +1826,8 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1479
1826
|
response: result.response,
|
|
1480
1827
|
sessionId: result.sessionId,
|
|
1481
1828
|
usage: result.usage,
|
|
1482
|
-
toolsUsed: result.toolsUsed
|
|
1829
|
+
toolsUsed: result.toolsUsed,
|
|
1830
|
+
retrievedDocuments: result.retrievedDocuments || []
|
|
1483
1831
|
});
|
|
1484
1832
|
} catch (err) {
|
|
1485
1833
|
const error = err;
|
|
@@ -1550,6 +1898,46 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1550
1898
|
await this.knowledgeBase.load(arr);
|
|
1551
1899
|
}
|
|
1552
1900
|
}
|
|
1901
|
+
// ─── Internal: User & Session Tracking ───────────────────────────────────────
|
|
1902
|
+
async trackUserAndSession(userId, sessionId, channel = "api") {
|
|
1903
|
+
try {
|
|
1904
|
+
const existingUser = this.db.query(
|
|
1905
|
+
"SELECT id FROM svara_users WHERE id = ?",
|
|
1906
|
+
[userId]
|
|
1907
|
+
);
|
|
1908
|
+
if (existingUser.length === 0) {
|
|
1909
|
+
this.db.run(
|
|
1910
|
+
`INSERT INTO svara_users (id, display_name, first_seen, last_seen)
|
|
1911
|
+
VALUES (?, ?, unixepoch(), unixepoch())`,
|
|
1912
|
+
[userId, userId]
|
|
1913
|
+
);
|
|
1914
|
+
} else {
|
|
1915
|
+
this.db.run(
|
|
1916
|
+
"UPDATE svara_users SET last_seen = unixepoch() WHERE id = ?",
|
|
1917
|
+
[userId]
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
const existingSession = this.db.query(
|
|
1921
|
+
"SELECT id FROM svara_sessions WHERE id = ?",
|
|
1922
|
+
[sessionId]
|
|
1923
|
+
);
|
|
1924
|
+
if (existingSession.length === 0) {
|
|
1925
|
+
this.db.run(
|
|
1926
|
+
`INSERT INTO svara_sessions (id, user_id, channel, created_at, updated_at)
|
|
1927
|
+
VALUES (?, ?, ?, unixepoch(), unixepoch())`,
|
|
1928
|
+
[sessionId, userId, channel]
|
|
1929
|
+
);
|
|
1930
|
+
} else {
|
|
1931
|
+
this.db.run(
|
|
1932
|
+
"UPDATE svara_sessions SET updated_at = unixepoch() WHERE id = ?",
|
|
1933
|
+
[sessionId]
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
this.log("debug", `Tracked user ${userId} with session ${sessionId}`);
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
this.log("error", `Failed to track user: ${error.message}`);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1553
1941
|
// ─── Internal: Agentic Loop ───────────────────────────────────────────────
|
|
1554
1942
|
/**
|
|
1555
1943
|
* Receives a raw incoming message from a channel and processes it.
|
|
@@ -1562,13 +1950,33 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1562
1950
|
});
|
|
1563
1951
|
}
|
|
1564
1952
|
async run(message, options) {
|
|
1953
|
+
console.log(`
|
|
1954
|
+
[RUN START] kb=${!!this.knowledgeBase} ret=${!!this.retriever}`);
|
|
1565
1955
|
const startTime = Date.now();
|
|
1566
1956
|
const sessionId = options.sessionId ?? crypto.randomUUID();
|
|
1567
|
-
|
|
1957
|
+
const userId = options.userId ?? "unknown";
|
|
1958
|
+
await this.trackUserAndSession(userId, sessionId);
|
|
1959
|
+
this.emit("message:received", { message, sessionId, userId });
|
|
1568
1960
|
const history = await this.memory.getHistory(sessionId);
|
|
1569
1961
|
let ragContext = "";
|
|
1570
|
-
|
|
1962
|
+
let retrievedDocuments = [];
|
|
1963
|
+
if (this.knowledgeBase && this.retriever) {
|
|
1571
1964
|
ragContext = await this.knowledgeBase.retrieve(message);
|
|
1965
|
+
try {
|
|
1966
|
+
console.log(`[DEBUG] Calling retrieveChunks for query: "${message}"`);
|
|
1967
|
+
const context = await this.retriever.retrieveChunks(message, 3);
|
|
1968
|
+
console.log(`[DEBUG] Retrieved ${context.chunks.length} chunks`);
|
|
1969
|
+
retrievedDocuments = context.chunks.map((item) => ({
|
|
1970
|
+
source: item.chunk?.source || "unknown",
|
|
1971
|
+
score: Math.round(item.score * 100) / 100,
|
|
1972
|
+
excerpt: item.chunk?.content?.substring(0, 150) || ""
|
|
1973
|
+
}));
|
|
1974
|
+
console.log(`[DEBUG] Mapped ${retrievedDocuments.length} documents`);
|
|
1975
|
+
} catch (e) {
|
|
1976
|
+
console.error(`[ERROR] RAG retrieval failed:`, e);
|
|
1977
|
+
}
|
|
1978
|
+
} else {
|
|
1979
|
+
console.log(`[DEBUG] No knowledgeBase (${!!this.knowledgeBase}) or retriever (${!!this.retriever})`);
|
|
1572
1980
|
}
|
|
1573
1981
|
const messages = this.context.buildMessages(
|
|
1574
1982
|
this.systemPrompt,
|
|
@@ -1578,7 +1986,7 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1578
1986
|
);
|
|
1579
1987
|
const internalCtx = {
|
|
1580
1988
|
sessionId,
|
|
1581
|
-
userId
|
|
1989
|
+
userId,
|
|
1582
1990
|
agentName: this.name,
|
|
1583
1991
|
history,
|
|
1584
1992
|
metadata: options.metadata ?? {}
|
|
@@ -1635,7 +2043,8 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1635
2043
|
toolsUsed: [...new Set(toolsUsed)],
|
|
1636
2044
|
iterations,
|
|
1637
2045
|
usage: totalUsage,
|
|
1638
|
-
duration: Date.now() - startTime
|
|
2046
|
+
duration: Date.now() - startTime,
|
|
2047
|
+
retrievedDocuments: retrievedDocuments.length > 0 ? retrievedDocuments : void 0
|
|
1639
2048
|
};
|
|
1640
2049
|
this.emit("message:sent", { response: finalResponse, sessionId });
|
|
1641
2050
|
return result;
|
|
@@ -1645,8 +2054,8 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1645
2054
|
try {
|
|
1646
2055
|
const { glob } = await import("glob");
|
|
1647
2056
|
const { VectorRetriever: VectorRetriever2 } = await Promise.resolve().then(() => (init_retriever(), retriever_exports));
|
|
1648
|
-
|
|
1649
|
-
await retriever.init({ embeddings: { provider: "openai" } });
|
|
2057
|
+
this.retriever = new VectorRetriever2(this.name, this.db);
|
|
2058
|
+
await this.retriever.init({ embeddings: { provider: "openai" } });
|
|
1650
2059
|
const files = [];
|
|
1651
2060
|
for (const pattern of paths) {
|
|
1652
2061
|
const matches = await glob(pattern);
|
|
@@ -1656,16 +2065,16 @@ var SvaraAgent = class extends import_events.default {
|
|
|
1656
2065
|
console.warn(`[@yesvara/svara] No files found matching: ${paths.join(", ")}`);
|
|
1657
2066
|
return;
|
|
1658
2067
|
}
|
|
1659
|
-
await retriever.addDocuments(files);
|
|
2068
|
+
await this.retriever.addDocuments(files);
|
|
1660
2069
|
this.knowledgeBase = {
|
|
1661
2070
|
load: async (p) => {
|
|
1662
2071
|
const newFiles = [];
|
|
1663
2072
|
for (const pattern of Array.isArray(p) ? p : [p]) {
|
|
1664
2073
|
newFiles.push(...await glob(pattern));
|
|
1665
2074
|
}
|
|
1666
|
-
await retriever.addDocuments(newFiles);
|
|
2075
|
+
await this.retriever.addDocuments(newFiles);
|
|
1667
2076
|
},
|
|
1668
|
-
retrieve: (query, topK) => retriever.retrieve(query, topK)
|
|
2077
|
+
retrieve: (query, topK) => this.retriever.retrieve(query, topK)
|
|
1669
2078
|
};
|
|
1670
2079
|
this.log("info", `Knowledge base loaded: ${files.length} file(s).`);
|
|
1671
2080
|
} catch (err) {
|
|
@@ -1727,232 +2136,8 @@ function createTool(definition) {
|
|
|
1727
2136
|
};
|
|
1728
2137
|
}
|
|
1729
2138
|
|
|
1730
|
-
// src/database/sqlite.ts
|
|
1731
|
-
var import_path2 = __toESM(require("path"));
|
|
1732
|
-
var import_fs = __toESM(require("fs"));
|
|
1733
|
-
|
|
1734
|
-
// src/database/schema.ts
|
|
1735
|
-
var SCHEMA_VERSION = 1;
|
|
1736
|
-
var CREATE_TABLES_SQL = `
|
|
1737
|
-
-- Schema version tracking
|
|
1738
|
-
CREATE TABLE IF NOT EXISTS svara_meta (
|
|
1739
|
-
key TEXT PRIMARY KEY,
|
|
1740
|
-
value TEXT NOT NULL
|
|
1741
|
-
);
|
|
1742
|
-
|
|
1743
|
-
-- Conversation history persistence
|
|
1744
|
-
CREATE TABLE IF NOT EXISTS svara_messages (
|
|
1745
|
-
id TEXT PRIMARY KEY,
|
|
1746
|
-
session_id TEXT NOT NULL,
|
|
1747
|
-
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system', 'tool')),
|
|
1748
|
-
content TEXT NOT NULL,
|
|
1749
|
-
tool_call_id TEXT,
|
|
1750
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1751
|
-
);
|
|
1752
|
-
|
|
1753
|
-
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
1754
|
-
ON svara_messages (session_id, created_at);
|
|
1755
|
-
|
|
1756
|
-
-- Session metadata
|
|
1757
|
-
CREATE TABLE IF NOT EXISTS svara_sessions (
|
|
1758
|
-
id TEXT PRIMARY KEY,
|
|
1759
|
-
user_id TEXT,
|
|
1760
|
-
channel TEXT NOT NULL,
|
|
1761
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1762
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1763
|
-
metadata TEXT DEFAULT '{}'
|
|
1764
|
-
);
|
|
1765
|
-
|
|
1766
|
-
-- Vector store chunks for RAG
|
|
1767
|
-
CREATE TABLE IF NOT EXISTS svara_chunks (
|
|
1768
|
-
id TEXT PRIMARY KEY,
|
|
1769
|
-
document_id TEXT NOT NULL,
|
|
1770
|
-
content TEXT NOT NULL,
|
|
1771
|
-
chunk_index INTEGER NOT NULL,
|
|
1772
|
-
embedding BLOB, -- stored as binary float32 array
|
|
1773
|
-
source TEXT NOT NULL,
|
|
1774
|
-
metadata TEXT DEFAULT '{}',
|
|
1775
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1776
|
-
);
|
|
1777
|
-
|
|
1778
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_document
|
|
1779
|
-
ON svara_chunks (document_id);
|
|
1780
|
-
|
|
1781
|
-
-- Document registry
|
|
1782
|
-
CREATE TABLE IF NOT EXISTS svara_documents (
|
|
1783
|
-
id TEXT PRIMARY KEY,
|
|
1784
|
-
source TEXT NOT NULL UNIQUE,
|
|
1785
|
-
type TEXT NOT NULL,
|
|
1786
|
-
size INTEGER,
|
|
1787
|
-
hash TEXT,
|
|
1788
|
-
indexed_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
1789
|
-
metadata TEXT DEFAULT '{}'
|
|
1790
|
-
);
|
|
1791
|
-
|
|
1792
|
-
-- Key-value store for arbitrary agent state
|
|
1793
|
-
CREATE TABLE IF NOT EXISTS svara_kv (
|
|
1794
|
-
key TEXT PRIMARY KEY,
|
|
1795
|
-
value TEXT NOT NULL,
|
|
1796
|
-
expires_at INTEGER, -- unix timestamp, NULL = no expiry
|
|
1797
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
1798
|
-
);
|
|
1799
|
-
`;
|
|
1800
|
-
var INSERT_META_SQL = `
|
|
1801
|
-
INSERT OR REPLACE INTO svara_meta (key, value)
|
|
1802
|
-
VALUES ('schema_version', ?), ('created_at', ?);
|
|
1803
|
-
`;
|
|
1804
|
-
|
|
1805
|
-
// src/database/sqlite.ts
|
|
1806
|
-
var KVStore = class {
|
|
1807
|
-
constructor(db) {
|
|
1808
|
-
this.db = db;
|
|
1809
|
-
}
|
|
1810
|
-
db;
|
|
1811
|
-
/** Set a key-value pair, with optional TTL in seconds. */
|
|
1812
|
-
set(key, value, ttlSeconds) {
|
|
1813
|
-
const expiresAt = ttlSeconds ? Math.floor(Date.now() / 1e3) + ttlSeconds : null;
|
|
1814
|
-
this.db.prepare(`
|
|
1815
|
-
INSERT OR REPLACE INTO svara_kv (key, value, expires_at, updated_at)
|
|
1816
|
-
VALUES (?, ?, ?, unixepoch())
|
|
1817
|
-
`).run(key, JSON.stringify(value), expiresAt);
|
|
1818
|
-
}
|
|
1819
|
-
/** Get a value by key. Returns undefined if not found or expired. */
|
|
1820
|
-
get(key) {
|
|
1821
|
-
const row = this.db.prepare(`
|
|
1822
|
-
SELECT value, expires_at FROM svara_kv
|
|
1823
|
-
WHERE key = ? AND (expires_at IS NULL OR expires_at > unixepoch())
|
|
1824
|
-
`).get(key);
|
|
1825
|
-
if (!row) return void 0;
|
|
1826
|
-
return JSON.parse(row.value);
|
|
1827
|
-
}
|
|
1828
|
-
/** Delete a key. */
|
|
1829
|
-
delete(key) {
|
|
1830
|
-
this.db.prepare("DELETE FROM svara_kv WHERE key = ?").run(key);
|
|
1831
|
-
}
|
|
1832
|
-
/** Check if a key exists and is not expired. */
|
|
1833
|
-
has(key) {
|
|
1834
|
-
return this.get(key) !== void 0;
|
|
1835
|
-
}
|
|
1836
|
-
/** Get all keys matching a prefix. */
|
|
1837
|
-
keys(prefix = "") {
|
|
1838
|
-
const rows = this.db.prepare(`
|
|
1839
|
-
SELECT key FROM svara_kv
|
|
1840
|
-
WHERE key LIKE ? AND (expires_at IS NULL OR expires_at > unixepoch())
|
|
1841
|
-
`).all(`${prefix}%`);
|
|
1842
|
-
return rows.map((r) => r.key);
|
|
1843
|
-
}
|
|
1844
|
-
};
|
|
1845
|
-
var SvaraDB = class {
|
|
1846
|
-
db;
|
|
1847
|
-
kv;
|
|
1848
|
-
constructor(dbPath = ":memory:") {
|
|
1849
|
-
if (dbPath !== ":memory:") {
|
|
1850
|
-
import_fs.default.mkdirSync(import_path2.default.dirname(import_path2.default.resolve(dbPath)), { recursive: true });
|
|
1851
|
-
}
|
|
1852
|
-
this.db = this.openDatabase(dbPath);
|
|
1853
|
-
this.configure();
|
|
1854
|
-
this.migrate();
|
|
1855
|
-
this.kv = new KVStore(this.db);
|
|
1856
|
-
}
|
|
1857
|
-
// ─── Query Helpers ────────────────────────────────────────────────────────
|
|
1858
|
-
/**
|
|
1859
|
-
* Run a SELECT and return all matching rows.
|
|
1860
|
-
*/
|
|
1861
|
-
query(sql, params = []) {
|
|
1862
|
-
return this.db.prepare(sql).all(...params);
|
|
1863
|
-
}
|
|
1864
|
-
/**
|
|
1865
|
-
* Run a SELECT and return the first matching row.
|
|
1866
|
-
*/
|
|
1867
|
-
queryOne(sql, params = []) {
|
|
1868
|
-
return this.db.prepare(sql).get(...params);
|
|
1869
|
-
}
|
|
1870
|
-
/**
|
|
1871
|
-
* Run an INSERT/UPDATE/DELETE. Returns affected row count.
|
|
1872
|
-
*/
|
|
1873
|
-
run(sql, params = []) {
|
|
1874
|
-
return this.db.prepare(sql).run(...params).changes;
|
|
1875
|
-
}
|
|
1876
|
-
/**
|
|
1877
|
-
* Execute raw SQL (for DDL, migrations, etc.).
|
|
1878
|
-
*/
|
|
1879
|
-
exec(sql) {
|
|
1880
|
-
this.db.exec(sql);
|
|
1881
|
-
}
|
|
1882
|
-
/**
|
|
1883
|
-
* Run multiple operations in a single transaction.
|
|
1884
|
-
*
|
|
1885
|
-
* @example
|
|
1886
|
-
* db.transaction(() => {
|
|
1887
|
-
* db.run('INSERT INTO orders ...', [...]);
|
|
1888
|
-
* db.run('UPDATE inventory ...', [...]);
|
|
1889
|
-
* });
|
|
1890
|
-
*/
|
|
1891
|
-
transaction(fn) {
|
|
1892
|
-
return this.db.transaction(fn)();
|
|
1893
|
-
}
|
|
1894
|
-
/**
|
|
1895
|
-
* Close the database connection.
|
|
1896
|
-
*/
|
|
1897
|
-
close() {
|
|
1898
|
-
this.db.close();
|
|
1899
|
-
}
|
|
1900
|
-
// ─── Internal Message Storage ─────────────────────────────────────────────
|
|
1901
|
-
saveMessage(params) {
|
|
1902
|
-
this.db.prepare(`
|
|
1903
|
-
INSERT OR REPLACE INTO svara_messages (id, session_id, role, content, tool_call_id)
|
|
1904
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1905
|
-
`).run(
|
|
1906
|
-
params.id,
|
|
1907
|
-
params.sessionId,
|
|
1908
|
-
params.role,
|
|
1909
|
-
params.content,
|
|
1910
|
-
params.toolCallId ?? null
|
|
1911
|
-
);
|
|
1912
|
-
}
|
|
1913
|
-
getMessages(sessionId, limit = 50) {
|
|
1914
|
-
return this.db.prepare(`
|
|
1915
|
-
SELECT id, role, content, tool_call_id, created_at
|
|
1916
|
-
FROM svara_messages
|
|
1917
|
-
WHERE session_id = ?
|
|
1918
|
-
ORDER BY created_at ASC
|
|
1919
|
-
LIMIT ?
|
|
1920
|
-
`).all(sessionId, limit);
|
|
1921
|
-
}
|
|
1922
|
-
clearSession(sessionId) {
|
|
1923
|
-
this.db.prepare("DELETE FROM svara_messages WHERE session_id = ?").run(sessionId);
|
|
1924
|
-
}
|
|
1925
|
-
// ─── Private Setup ────────────────────────────────────────────────────────
|
|
1926
|
-
openDatabase(dbPath) {
|
|
1927
|
-
try {
|
|
1928
|
-
const Database = require("better-sqlite3");
|
|
1929
|
-
return new Database(dbPath);
|
|
1930
|
-
} catch {
|
|
1931
|
-
throw new Error(
|
|
1932
|
-
'[SvaraJS] Database requires the "better-sqlite3" package.\nRun: npm install better-sqlite3'
|
|
1933
|
-
);
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
configure() {
|
|
1937
|
-
this.db.pragma("journal_mode = WAL");
|
|
1938
|
-
this.db.pragma("synchronous = NORMAL");
|
|
1939
|
-
this.db.pragma("foreign_keys = ON");
|
|
1940
|
-
}
|
|
1941
|
-
migrate() {
|
|
1942
|
-
this.db.exec(CREATE_TABLES_SQL);
|
|
1943
|
-
const meta = this.db.prepare(
|
|
1944
|
-
"SELECT value FROM svara_meta WHERE key = 'schema_version'"
|
|
1945
|
-
).get();
|
|
1946
|
-
if (!meta) {
|
|
1947
|
-
this.db.prepare(INSERT_META_SQL).run(
|
|
1948
|
-
String(SCHEMA_VERSION),
|
|
1949
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
1950
|
-
);
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
};
|
|
1954
|
-
|
|
1955
2139
|
// src/index.ts
|
|
2140
|
+
init_sqlite();
|
|
1956
2141
|
init_web();
|
|
1957
2142
|
init_telegram();
|
|
1958
2143
|
init_whatsapp();
|