@auxiora/sessions 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/dist/db.d.ts +22 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +135 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +38 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +269 -0
- package/dist/manager.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +26 -0
- package/src/db.ts +180 -0
- package/src/index.ts +11 -0
- package/src/manager.ts +343 -0
- package/src/types.ts +56 -0
- package/tests/db.test.ts +139 -0
- package/tests/migration.test.ts +114 -0
- package/tests/sessions.test.ts +187 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tsbuildinfo +1 -0
package/dist/manager.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as crypto from 'node:crypto';
|
|
4
|
+
import { getSessionsDir } from '@auxiora/core';
|
|
5
|
+
import { audit } from '@auxiora/audit';
|
|
6
|
+
import { SessionDatabase } from './db.js';
|
|
7
|
+
function generateMessageId() {
|
|
8
|
+
const timestamp = Date.now().toString(36);
|
|
9
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
10
|
+
return `${timestamp}-${random}`;
|
|
11
|
+
}
|
|
12
|
+
export class SessionManager {
|
|
13
|
+
sessions = new Map();
|
|
14
|
+
config;
|
|
15
|
+
db;
|
|
16
|
+
sessionsDir;
|
|
17
|
+
cleanupInterval = null;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.sessionsDir = config.sessionsDir ?? getSessionsDir();
|
|
21
|
+
const dbPath = config.dbPath ?? path.join(this.sessionsDir, 'sessions.db');
|
|
22
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
23
|
+
this.db = new SessionDatabase(dbPath);
|
|
24
|
+
// Cleanup expired sessions every 5 minutes (non-webchat channels only)
|
|
25
|
+
this.cleanupInterval = setInterval(() => this.cleanupExpired(), 5 * 60 * 1000);
|
|
26
|
+
}
|
|
27
|
+
async initialize() {
|
|
28
|
+
// Migrate legacy JSON session files to SQLite
|
|
29
|
+
let files;
|
|
30
|
+
try {
|
|
31
|
+
files = fs.readdirSync(this.sessionsDir).filter(f => f.endsWith('.json'));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return; // Directory doesn't exist or not readable
|
|
35
|
+
}
|
|
36
|
+
if (files.length === 0)
|
|
37
|
+
return;
|
|
38
|
+
const migratedDir = path.join(this.sessionsDir, 'migrated');
|
|
39
|
+
fs.mkdirSync(migratedDir, { recursive: true });
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const filePath = path.join(this.sessionsDir, file);
|
|
42
|
+
try {
|
|
43
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
44
|
+
const data = JSON.parse(raw);
|
|
45
|
+
const title = data.metadata.channelType === 'webchat' ? 'New Chat' : `${data.metadata.channelType} session`;
|
|
46
|
+
this.db.insertChatWithId(data.id, title, data.metadata.channelType, data.metadata.createdAt, data.metadata.lastActiveAt, data.metadata.senderId);
|
|
47
|
+
for (const msg of data.messages) {
|
|
48
|
+
this.db.addMessage(data.id, msg.id, msg.role, msg.content, msg.timestamp, msg.tokens?.input, msg.tokens?.output);
|
|
49
|
+
}
|
|
50
|
+
// Move original to migrated/
|
|
51
|
+
fs.renameSync(filePath, path.join(migratedDir, file));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Skip corrupt files — leave them in place
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async create(metadata) {
|
|
59
|
+
const title = metadata.channelType === 'webchat' ? 'New Chat' : `${metadata.channelType} session`;
|
|
60
|
+
const chat = this.db.createChat(title, metadata.channelType, metadata.senderId);
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const session = {
|
|
63
|
+
id: chat.id,
|
|
64
|
+
messages: [],
|
|
65
|
+
metadata: {
|
|
66
|
+
channelType: metadata.channelType,
|
|
67
|
+
senderId: metadata.senderId,
|
|
68
|
+
clientId: metadata.clientId,
|
|
69
|
+
createdAt: now,
|
|
70
|
+
lastActiveAt: now,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
this.sessions.set(chat.id, session);
|
|
74
|
+
await audit('session.created', { sessionId: chat.id, channelType: metadata.channelType });
|
|
75
|
+
return session;
|
|
76
|
+
}
|
|
77
|
+
async get(id) {
|
|
78
|
+
// Check in-memory cache
|
|
79
|
+
if (this.sessions.has(id)) {
|
|
80
|
+
return this.sessions.get(id);
|
|
81
|
+
}
|
|
82
|
+
// Check DB
|
|
83
|
+
const chat = this.db.getChat(id);
|
|
84
|
+
if (!chat)
|
|
85
|
+
return null;
|
|
86
|
+
const messages = this.db.getMessages(id);
|
|
87
|
+
const session = {
|
|
88
|
+
id: chat.id,
|
|
89
|
+
messages,
|
|
90
|
+
metadata: {
|
|
91
|
+
channelType: chat.channel,
|
|
92
|
+
senderId: chat.metadata?.senderId,
|
|
93
|
+
clientId: chat.metadata?.clientId,
|
|
94
|
+
createdAt: chat.createdAt,
|
|
95
|
+
lastActiveAt: chat.updatedAt,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
this.sessions.set(id, session);
|
|
99
|
+
return session;
|
|
100
|
+
}
|
|
101
|
+
async getOrCreate(key, metadata) {
|
|
102
|
+
// Check in-memory cache first
|
|
103
|
+
for (const session of this.sessions.values()) {
|
|
104
|
+
if (session.metadata.senderId === metadata.senderId &&
|
|
105
|
+
session.metadata.channelType === metadata.channelType) {
|
|
106
|
+
session.metadata.lastActiveAt = Date.now();
|
|
107
|
+
return session;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Check DB
|
|
111
|
+
if (metadata.senderId) {
|
|
112
|
+
const chat = this.db.getOrCreateSessionChat(metadata.senderId, metadata.channelType);
|
|
113
|
+
const existing = this.sessions.get(chat.id);
|
|
114
|
+
if (existing) {
|
|
115
|
+
existing.metadata.lastActiveAt = Date.now();
|
|
116
|
+
return existing;
|
|
117
|
+
}
|
|
118
|
+
const messages = this.db.getMessages(chat.id);
|
|
119
|
+
const session = {
|
|
120
|
+
id: chat.id,
|
|
121
|
+
messages,
|
|
122
|
+
metadata: {
|
|
123
|
+
channelType: metadata.channelType,
|
|
124
|
+
senderId: metadata.senderId,
|
|
125
|
+
clientId: metadata.clientId,
|
|
126
|
+
createdAt: chat.createdAt,
|
|
127
|
+
lastActiveAt: Date.now(),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
this.sessions.set(chat.id, session);
|
|
131
|
+
return session;
|
|
132
|
+
}
|
|
133
|
+
// No senderId — create new
|
|
134
|
+
return this.create(metadata);
|
|
135
|
+
}
|
|
136
|
+
async addMessage(sessionId, role, content, tokens) {
|
|
137
|
+
const session = await this.get(sessionId);
|
|
138
|
+
if (!session) {
|
|
139
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
140
|
+
}
|
|
141
|
+
const message = {
|
|
142
|
+
id: generateMessageId(),
|
|
143
|
+
role,
|
|
144
|
+
content,
|
|
145
|
+
timestamp: Date.now(),
|
|
146
|
+
tokens,
|
|
147
|
+
};
|
|
148
|
+
session.messages.push(message);
|
|
149
|
+
session.metadata.lastActiveAt = Date.now();
|
|
150
|
+
// Persist to DB
|
|
151
|
+
this.db.addMessage(sessionId, message.id, role, content, message.timestamp, tokens?.input, tokens?.output);
|
|
152
|
+
return message;
|
|
153
|
+
}
|
|
154
|
+
getMessages(sessionId) {
|
|
155
|
+
const session = this.sessions.get(sessionId);
|
|
156
|
+
if (session)
|
|
157
|
+
return session.messages;
|
|
158
|
+
// Fall back to DB
|
|
159
|
+
return this.db.getMessages(sessionId);
|
|
160
|
+
}
|
|
161
|
+
getContextMessages(sessionId, maxTokens) {
|
|
162
|
+
const limit = maxTokens || this.config.maxContextTokens;
|
|
163
|
+
// Try in-memory first
|
|
164
|
+
const session = this.sessions.get(sessionId);
|
|
165
|
+
if (session) {
|
|
166
|
+
const messages = [];
|
|
167
|
+
let tokenCount = 0;
|
|
168
|
+
for (let i = session.messages.length - 1; i >= 0; i--) {
|
|
169
|
+
const msg = session.messages[i];
|
|
170
|
+
const msgTokens = Math.ceil(msg.content.length / 4);
|
|
171
|
+
if (tokenCount + msgTokens > limit)
|
|
172
|
+
break;
|
|
173
|
+
messages.unshift(msg);
|
|
174
|
+
tokenCount += msgTokens;
|
|
175
|
+
}
|
|
176
|
+
return messages;
|
|
177
|
+
}
|
|
178
|
+
// Fall back to DB
|
|
179
|
+
return this.db.getContextMessages(sessionId, limit);
|
|
180
|
+
}
|
|
181
|
+
async setSystemPrompt(sessionId, prompt) {
|
|
182
|
+
const session = await this.get(sessionId);
|
|
183
|
+
if (!session) {
|
|
184
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
185
|
+
}
|
|
186
|
+
session.systemPrompt = prompt;
|
|
187
|
+
}
|
|
188
|
+
async save(_sessionId) {
|
|
189
|
+
// No-op — writes are immediate with SQLite
|
|
190
|
+
}
|
|
191
|
+
async delete(sessionId) {
|
|
192
|
+
const existed = this.sessions.delete(sessionId);
|
|
193
|
+
this.db.deleteChat(sessionId);
|
|
194
|
+
if (existed) {
|
|
195
|
+
await audit('session.destroyed', { sessionId });
|
|
196
|
+
}
|
|
197
|
+
return existed;
|
|
198
|
+
}
|
|
199
|
+
async clear(sessionId) {
|
|
200
|
+
const session = await this.get(sessionId);
|
|
201
|
+
if (!session)
|
|
202
|
+
return;
|
|
203
|
+
session.messages = [];
|
|
204
|
+
session.metadata.lastActiveAt = Date.now();
|
|
205
|
+
this.db.clearMessages(sessionId);
|
|
206
|
+
}
|
|
207
|
+
async compact(sessionId, summary) {
|
|
208
|
+
const session = await this.get(sessionId);
|
|
209
|
+
if (!session || !this.config.compactionEnabled)
|
|
210
|
+
return;
|
|
211
|
+
const originalCount = session.messages.length;
|
|
212
|
+
this.db.clearMessages(sessionId);
|
|
213
|
+
const summaryMessage = {
|
|
214
|
+
id: generateMessageId(),
|
|
215
|
+
role: 'system',
|
|
216
|
+
content: `[Previous conversation summary]\n${summary}`,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
};
|
|
219
|
+
session.messages = [summaryMessage];
|
|
220
|
+
session.metadata.lastActiveAt = Date.now();
|
|
221
|
+
this.db.addMessage(sessionId, summaryMessage.id, 'system', summaryMessage.content, summaryMessage.timestamp);
|
|
222
|
+
await audit('session.compacted', { sessionId, originalCount });
|
|
223
|
+
}
|
|
224
|
+
cleanupExpired() {
|
|
225
|
+
const now = Date.now();
|
|
226
|
+
const ttlMs = this.config.ttlMinutes * 60 * 1000;
|
|
227
|
+
for (const [id, session] of this.sessions) {
|
|
228
|
+
// Only expire non-webchat sessions from memory
|
|
229
|
+
if (session.metadata.channelType === 'webchat')
|
|
230
|
+
continue;
|
|
231
|
+
if (now - session.metadata.lastActiveAt > ttlMs) {
|
|
232
|
+
this.sessions.delete(id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
getActiveSessions() {
|
|
237
|
+
return Array.from(this.sessions.values());
|
|
238
|
+
}
|
|
239
|
+
// ── Chat management ──
|
|
240
|
+
createChat(title) {
|
|
241
|
+
return this.db.createChat(title ?? 'New Chat', 'webchat');
|
|
242
|
+
}
|
|
243
|
+
listChats(options) {
|
|
244
|
+
return this.db.listChats(options);
|
|
245
|
+
}
|
|
246
|
+
renameChat(chatId, title) {
|
|
247
|
+
this.db.renameChat(chatId, title);
|
|
248
|
+
}
|
|
249
|
+
archiveChat(chatId) {
|
|
250
|
+
this.db.archiveChat(chatId);
|
|
251
|
+
this.sessions.delete(chatId);
|
|
252
|
+
}
|
|
253
|
+
deleteChat(chatId) {
|
|
254
|
+
this.db.deleteChat(chatId);
|
|
255
|
+
this.sessions.delete(chatId);
|
|
256
|
+
}
|
|
257
|
+
getChatMessages(chatId) {
|
|
258
|
+
return this.db.getMessages(chatId);
|
|
259
|
+
}
|
|
260
|
+
destroy() {
|
|
261
|
+
if (this.cleanupInterval) {
|
|
262
|
+
clearInterval(this.cleanupInterval);
|
|
263
|
+
this.cleanupInterval = null;
|
|
264
|
+
}
|
|
265
|
+
this.sessions.clear();
|
|
266
|
+
this.db.close();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAG1C,SAAS,iBAAiB;IACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrD,OAAO,GAAG,SAAS,IAAI,MAAM,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,OAAO,cAAc;IACjB,QAAQ,GAAyB,IAAI,GAAG,EAAE,CAAC;IAC3C,MAAM,CAAgB;IACtB,EAAE,CAAkB;IACpB,WAAW,CAAS;IACpB,eAAe,GAA0C,IAAI,CAAC;IAEtE,YAAY,MAAqB;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,cAAc,EAAE,CAAC;QAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;QAC3E,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,EAAE,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;QAEtC,uEAAuE;QACvE,IAAI,CAAC,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,UAAU;QACd,8CAA8C;QAC9C,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YACH,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,0CAA0C;QACpD,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE/B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QAC5D,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE/C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAI1B,CAAC;gBAEF,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,UAAU,CAAC;gBAC5G,IAAI,CAAC,EAAE,CAAC,gBAAgB,CACtB,IAAI,CAAC,EAAE,EACP,KAAK,EACL,IAAI,CAAC,QAAQ,CAAC,WAAW,EACzB,IAAI,CAAC,QAAQ,CAAC,SAAS,EACvB,IAAI,CAAC,QAAQ,CAAC,YAAY,EAC1B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CACvB,CAAC;gBAEF,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAChB,IAAI,CAAC,EAAE,EACP,GAAG,CAAC,EAAE,EACN,GAAG,CAAC,IAAI,EACR,GAAG,CAAC,OAAO,EACX,GAAG,CAAC,SAAS,EACb,GAAG,CAAC,MAAM,EAAE,KAAK,EACjB,GAAG,CAAC,MAAM,EAAE,MAAM,CACnB,CAAC;gBACJ,CAAC;gBAED,6BAA6B;gBAC7B,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC;YACxD,CAAC;YAAC,MAAM,CAAC;gBACP,2CAA2C;YAC7C,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,QAA4D;QACvE,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,WAAW,UAAU,CAAC;QAClG,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAEhF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAY;YACvB,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,QAAQ,EAAE,EAAE;YACZ,QAAQ,EAAE;gBACR,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,SAAS,EAAE,GAAG;gBACd,YAAY,EAAE,GAAG;aAClB;SACF,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACpC,MAAM,KAAK,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1F,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,wBAAwB;QACxB,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC;QAChC,CAAC;QAED,WAAW;QACX,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACzC,MAAM,OAAO,GAAY;YACvB,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,QAAQ;YACR,QAAQ,EAAE;gBACR,WAAW,EAAE,IAAI,CAAC,OAAO;gBACzB,QAAQ,EAAG,IAAI,CAAC,QAAQ,EAAE,QAA+B;gBACzD,QAAQ,EAAG,IAAI,CAAC,QAAQ,EAAE,QAA+B;gBACzD,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,YAAY,EAAE,IAAI,CAAC,SAAS;aAC7B;SACF,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,WAAW,CACf,GAAW,EACX,QAA4D;QAE5D,8BAA8B;QAC9B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IACE,OAAO,CAAC,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ;gBAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW,EACrD,CAAC;gBACD,OAAO,CAAC,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3C,OAAO,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;QAED,WAAW;QACX,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;YACrF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5C,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC5C,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,OAAO,GAAY;gBACvB,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,QAAQ;gBACR,QAAQ,EAAE;oBACR,WAAW,EAAE,QAAQ,CAAC,WAAW;oBACjC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;oBAC3B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;oBAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;iBACzB;aACF,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YACpC,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,2BAA2B;QAC3B,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,UAAU,CACd,SAAiB,EACjB,IAAiB,EACjB,OAAe,EACf,MAA4C;QAE5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,MAAM,OAAO,GAAY;YACvB,EAAE,EAAE,iBAAiB,EAAE;YACvB,IAAI;YACJ,OAAO;YACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,MAAM;SACP,CAAC;QAEF,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE3C,gBAAgB;QAChB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAE3G,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,WAAW,CAAC,SAAiB;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC,QAAQ,CAAC;QAErC,kBAAkB;QAClB,OAAO,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAED,kBAAkB,CAAC,SAAiB,EAAE,SAAkB;QACtD,MAAM,KAAK,GAAG,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC;QAExD,sBAAsB;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAc,EAAE,CAAC;YAC/B,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAChC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBACpD,IAAI,UAAU,GAAG,SAAS,GAAG,KAAK;oBAAE,MAAM;gBAC1C,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACtB,UAAU,IAAI,SAAS,CAAC;YAC1B,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,kBAAkB;QAClB,OAAO,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,SAAiB,EAAE,MAAc;QACrD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,CAAC,YAAY,GAAG,MAAM,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAkB;QAC3B,2CAA2C;IAC7C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAE9B,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB;QAC3B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,OAAO,CAAC,QAAQ,GAAG,EAAE,CAAC;QACtB,OAAO,CAAC,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,OAAe;QAC9C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB;YAAE,OAAO;QAEvD,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC9C,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAEjC,MAAM,cAAc,GAAY;YAC9B,EAAE,EAAE,iBAAiB,EAAE;YACvB,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,oCAAoC,OAAO,EAAE;YACtD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;QAEF,OAAO,CAAC,QAAQ,GAAG,CAAC,cAAc,CAAC,CAAC;QACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;QAE7G,MAAM,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;IACjE,CAAC;IAEO,cAAc;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;QAEjD,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1C,+CAA+C;YAC/C,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS;gBAAE,SAAS;YACzD,IAAI,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,GAAG,KAAK,EAAE,CAAC;gBAChD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,iBAAiB;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,wBAAwB;IAExB,UAAU,CAAC,KAAc;QACvB,OAAO,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,IAAI,UAAU,EAAE,SAAS,CAAC,CAAC;IAC5D,CAAC;IAED,SAAS,CAAC,OAA0B;QAClC,OAAO,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,UAAU,CAAC,MAAc,EAAE,KAAa;QACtC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,WAAW,CAAC,MAAc;QACxB,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,UAAU,CAAC,MAAc;QACvB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,eAAe,CAAC,MAAc;QAC5B,OAAO,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACpC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type MessageRole = 'user' | 'assistant' | 'system';
|
|
2
|
+
export interface Message {
|
|
3
|
+
id: string;
|
|
4
|
+
role: MessageRole;
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
tokens?: {
|
|
8
|
+
input?: number;
|
|
9
|
+
output?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface SessionMetadata {
|
|
13
|
+
channelType: string;
|
|
14
|
+
senderId?: string;
|
|
15
|
+
clientId?: string;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
lastActiveAt: number;
|
|
18
|
+
activeMode?: string;
|
|
19
|
+
modeAutoDetected?: boolean;
|
|
20
|
+
escalationLevel?: string;
|
|
21
|
+
suspendedMode?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface Session {
|
|
24
|
+
id: string;
|
|
25
|
+
messages: Message[];
|
|
26
|
+
metadata: SessionMetadata;
|
|
27
|
+
systemPrompt?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SessionConfig {
|
|
30
|
+
maxContextTokens: number;
|
|
31
|
+
ttlMinutes: number;
|
|
32
|
+
autoSave: boolean;
|
|
33
|
+
compactionEnabled: boolean;
|
|
34
|
+
dbPath?: string;
|
|
35
|
+
sessionsDir?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface Chat {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
channel: string;
|
|
41
|
+
createdAt: number;
|
|
42
|
+
updatedAt: number;
|
|
43
|
+
archived: boolean;
|
|
44
|
+
metadata?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
export interface ListChatsOptions {
|
|
47
|
+
archived?: boolean;
|
|
48
|
+
limit?: number;
|
|
49
|
+
offset?: number;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE1D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auxiora/sessions",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Session management for Auxiora",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@auxiora/audit": "1.0.0",
|
|
16
|
+
"@auxiora/core": "1.0.0"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.0.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"clean": "rm -rf dist",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/db.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import * as crypto from 'node:crypto';
|
|
3
|
+
import type { Message, Chat, ListChatsOptions } from './types.js';
|
|
4
|
+
|
|
5
|
+
function generateId(): string {
|
|
6
|
+
return crypto.randomUUID();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class SessionDatabase {
|
|
10
|
+
private db: DatabaseSync;
|
|
11
|
+
|
|
12
|
+
constructor(dbPath: string) {
|
|
13
|
+
this.db = new DatabaseSync(dbPath);
|
|
14
|
+
this.db.exec('PRAGMA journal_mode=WAL');
|
|
15
|
+
this.db.exec('PRAGMA foreign_keys=ON');
|
|
16
|
+
this.migrate();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private migrate(): void {
|
|
20
|
+
this.db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS chats (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
title TEXT NOT NULL,
|
|
24
|
+
channel TEXT NOT NULL DEFAULT 'webchat',
|
|
25
|
+
created_at INTEGER NOT NULL,
|
|
26
|
+
updated_at INTEGER NOT NULL,
|
|
27
|
+
archived INTEGER NOT NULL DEFAULT 0,
|
|
28
|
+
metadata TEXT,
|
|
29
|
+
sender_id TEXT
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
33
|
+
id TEXT PRIMARY KEY,
|
|
34
|
+
chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
35
|
+
role TEXT NOT NULL,
|
|
36
|
+
content TEXT NOT NULL,
|
|
37
|
+
timestamp INTEGER NOT NULL,
|
|
38
|
+
tokens_in INTEGER,
|
|
39
|
+
tokens_out INTEGER
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_messages_chat_ts ON messages(chat_id, timestamp);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_chats_updated ON chats(updated_at DESC);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_chats_sender ON chats(sender_id, channel);
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Chat operations ──
|
|
49
|
+
|
|
50
|
+
createChat(title: string, channel: string, senderId?: string): Chat {
|
|
51
|
+
const id = generateId();
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
this.db.prepare(
|
|
54
|
+
'INSERT INTO chats (id, title, channel, created_at, updated_at, sender_id) VALUES (?, ?, ?, ?, ?, ?)',
|
|
55
|
+
).run(id, title, channel, now, now, senderId ?? null);
|
|
56
|
+
return { id, title, channel, createdAt: now, updatedAt: now, archived: false };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getChat(id: string): Chat | undefined {
|
|
60
|
+
const row = this.db.prepare('SELECT * FROM chats WHERE id = ?').get(id) as Record<string, unknown> | undefined;
|
|
61
|
+
return row ? this.rowToChat(row) : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
listChats(options?: ListChatsOptions): Chat[] {
|
|
65
|
+
const limit = options?.limit ?? 50;
|
|
66
|
+
const offset = options?.offset ?? 0;
|
|
67
|
+
if (options?.archived) {
|
|
68
|
+
return (this.db.prepare('SELECT * FROM chats ORDER BY updated_at DESC LIMIT ? OFFSET ?').all(limit, offset) as Record<string, unknown>[]).map(r => this.rowToChat(r));
|
|
69
|
+
}
|
|
70
|
+
return (this.db.prepare('SELECT * FROM chats WHERE archived = 0 ORDER BY updated_at DESC LIMIT ? OFFSET ?').all(limit, offset) as Record<string, unknown>[]).map(r => this.rowToChat(r));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
renameChat(id: string, title: string): void {
|
|
74
|
+
this.db.prepare('UPDATE chats SET title = ?, updated_at = ? WHERE id = ?').run(title, Date.now(), id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
archiveChat(id: string): void {
|
|
78
|
+
this.db.prepare('UPDATE chats SET archived = 1, updated_at = ? WHERE id = ?').run(Date.now(), id);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
deleteChat(id: string): void {
|
|
82
|
+
this.db.prepare('DELETE FROM chats WHERE id = ?').run(id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Message operations ──
|
|
86
|
+
|
|
87
|
+
addMessage(
|
|
88
|
+
chatId: string,
|
|
89
|
+
msgId: string,
|
|
90
|
+
role: string,
|
|
91
|
+
content: string,
|
|
92
|
+
timestamp: number,
|
|
93
|
+
tokensIn?: number,
|
|
94
|
+
tokensOut?: number,
|
|
95
|
+
): void {
|
|
96
|
+
this.db.prepare(
|
|
97
|
+
'INSERT INTO messages (id, chat_id, role, content, timestamp, tokens_in, tokens_out) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
98
|
+
).run(msgId, chatId, role, content, timestamp, tokensIn ?? null, tokensOut ?? null);
|
|
99
|
+
this.db.prepare('UPDATE chats SET updated_at = ? WHERE id = ?').run(timestamp, chatId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getMessages(chatId: string): Message[] {
|
|
103
|
+
return (this.db.prepare('SELECT * FROM messages WHERE chat_id = ? ORDER BY timestamp ASC').all(chatId) as Record<string, unknown>[]).map(r => this.rowToMessage(r));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getContextMessages(chatId: string, maxTokens: number): Message[] {
|
|
107
|
+
const rows = this.db.prepare('SELECT * FROM messages WHERE chat_id = ? ORDER BY timestamp DESC').all(chatId) as Record<string, unknown>[];
|
|
108
|
+
const messages: Message[] = [];
|
|
109
|
+
let tokenCount = 0;
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
const msg = this.rowToMessage(row);
|
|
112
|
+
const msgTokens = Math.ceil(msg.content.length / 4);
|
|
113
|
+
if (tokenCount + msgTokens > maxTokens) break;
|
|
114
|
+
messages.unshift(msg);
|
|
115
|
+
tokenCount += msgTokens;
|
|
116
|
+
}
|
|
117
|
+
return messages;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
clearMessages(chatId: string): void {
|
|
121
|
+
this.db.prepare('DELETE FROM messages WHERE chat_id = ?').run(chatId);
|
|
122
|
+
this.db.prepare('UPDATE chats SET updated_at = ? WHERE id = ?').run(Date.now(), chatId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Session compatibility ──
|
|
126
|
+
|
|
127
|
+
getOrCreateSessionChat(senderId: string, channel: string): Chat {
|
|
128
|
+
const row = this.db.prepare(
|
|
129
|
+
'SELECT * FROM chats WHERE sender_id = ? AND channel = ? ORDER BY updated_at DESC LIMIT 1',
|
|
130
|
+
).get(senderId, channel) as Record<string, unknown> | undefined;
|
|
131
|
+
if (row) {
|
|
132
|
+
this.db.prepare('UPDATE chats SET updated_at = ? WHERE id = ?').run(Date.now(), row.id as string);
|
|
133
|
+
return this.rowToChat(row);
|
|
134
|
+
}
|
|
135
|
+
return this.createChat(`${channel} session`, channel, senderId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
insertChatWithId(
|
|
139
|
+
id: string,
|
|
140
|
+
title: string,
|
|
141
|
+
channel: string,
|
|
142
|
+
createdAt: number,
|
|
143
|
+
updatedAt: number,
|
|
144
|
+
senderId?: string,
|
|
145
|
+
): void {
|
|
146
|
+
this.db.prepare(
|
|
147
|
+
'INSERT INTO chats (id, title, channel, created_at, updated_at, sender_id) VALUES (?, ?, ?, ?, ?, ?)',
|
|
148
|
+
).run(id, title, channel, createdAt, updatedAt, senderId ?? null);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Helpers ──
|
|
152
|
+
|
|
153
|
+
private rowToChat(row: Record<string, unknown>): Chat {
|
|
154
|
+
return {
|
|
155
|
+
id: row.id as string,
|
|
156
|
+
title: row.title as string,
|
|
157
|
+
channel: row.channel as string,
|
|
158
|
+
createdAt: row.created_at as number,
|
|
159
|
+
updatedAt: row.updated_at as number,
|
|
160
|
+
archived: row.archived === 1,
|
|
161
|
+
metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private rowToMessage(row: Record<string, unknown>): Message {
|
|
166
|
+
return {
|
|
167
|
+
id: row.id as string,
|
|
168
|
+
role: row.role as 'user' | 'assistant' | 'system',
|
|
169
|
+
content: row.content as string,
|
|
170
|
+
timestamp: row.timestamp as number,
|
|
171
|
+
tokens: (row.tokens_in != null || row.tokens_out != null)
|
|
172
|
+
? { input: (row.tokens_in as number) ?? undefined, output: (row.tokens_out as number) ?? undefined }
|
|
173
|
+
: undefined,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
close(): void {
|
|
178
|
+
this.db.close();
|
|
179
|
+
}
|
|
180
|
+
}
|
package/src/index.ts
ADDED