@falai/agent 0.3.11 → 0.3.20
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/README.md +164 -0
- package/dist/adapters/MongoAdapter.d.ts +97 -0
- package/dist/adapters/MongoAdapter.d.ts.map +1 -0
- package/dist/adapters/MongoAdapter.js +163 -0
- package/dist/adapters/MongoAdapter.js.map +1 -0
- package/dist/adapters/PostgreSQLAdapter.d.ts +71 -0
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -0
- package/dist/adapters/PostgreSQLAdapter.js +256 -0
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -0
- package/dist/adapters/PrismaAdapter.d.ts +115 -0
- package/dist/adapters/PrismaAdapter.d.ts.map +1 -0
- package/dist/adapters/PrismaAdapter.js +331 -0
- package/dist/adapters/PrismaAdapter.js.map +1 -0
- package/dist/adapters/RedisAdapter.d.ts +71 -0
- package/dist/adapters/RedisAdapter.d.ts.map +1 -0
- package/dist/adapters/RedisAdapter.js +226 -0
- package/dist/adapters/RedisAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +8 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/cjs/adapters/MongoAdapter.d.ts +97 -0
- package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -0
- package/dist/cjs/adapters/MongoAdapter.js +167 -0
- package/dist/cjs/adapters/MongoAdapter.js.map +1 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +71 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.js +260 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -0
- package/dist/cjs/adapters/PrismaAdapter.d.ts +115 -0
- package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -0
- package/dist/cjs/adapters/PrismaAdapter.js +335 -0
- package/dist/cjs/adapters/PrismaAdapter.js.map +1 -0
- package/dist/cjs/adapters/RedisAdapter.d.ts +71 -0
- package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -0
- package/dist/cjs/adapters/RedisAdapter.js +230 -0
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -0
- package/dist/cjs/adapters/index.d.ts +12 -0
- package/dist/cjs/adapters/index.d.ts.map +1 -0
- package/dist/cjs/adapters/index.js +15 -0
- package/dist/cjs/adapters/index.js.map +1 -0
- package/dist/cjs/core/Agent.d.ts +10 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +23 -0
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/PersistenceManager.d.ts +77 -0
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -0
- package/dist/cjs/core/PersistenceManager.js +153 -0
- package/dist/cjs/core/PersistenceManager.js.map +1 -0
- package/dist/cjs/index.d.ts +10 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +12 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +3 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/persistence.d.ts +194 -0
- package/dist/cjs/types/persistence.d.ts.map +1 -0
- package/dist/cjs/types/persistence.js +7 -0
- package/dist/cjs/types/persistence.js.map +1 -0
- package/dist/core/Agent.d.ts +10 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +23 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/PersistenceManager.d.ts +77 -0
- package/dist/core/PersistenceManager.d.ts.map +1 -0
- package/dist/core/PersistenceManager.js +149 -0
- package/dist/core/PersistenceManager.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +3 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/persistence.d.ts +194 -0
- package/dist/types/persistence.d.ts.map +1 -0
- package/dist/types/persistence.js +6 -0
- package/dist/types/persistence.js.map +1 -0
- package/docs/ADAPTERS.md +127 -0
- package/docs/API_REFERENCE.md +337 -0
- package/docs/PERSISTENCE.md +513 -0
- package/examples/prisma-persistence.ts +313 -0
- package/examples/prisma-schema.example.prisma +74 -0
- package/examples/redis-persistence.ts +89 -0
- package/package.json +29 -1
- package/src/adapters/MongoAdapter.ts +295 -0
- package/src/adapters/PostgreSQLAdapter.ts +417 -0
- package/src/adapters/PrismaAdapter.ts +510 -0
- package/src/adapters/RedisAdapter.ts +365 -0
- package/src/adapters/index.ts +28 -0
- package/src/core/Agent.ts +31 -0
- package/src/core/PersistenceManager.ts +222 -0
- package/src/index.ts +36 -0
- package/src/types/agent.ts +3 -0
- package/src/types/index.ts +14 -0
- package/src/types/persistence.ts +234 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis adapter for persistence
|
|
3
|
+
* Uses Redis for fast session/message storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
SessionRepository,
|
|
8
|
+
MessageRepository,
|
|
9
|
+
SessionData,
|
|
10
|
+
MessageData,
|
|
11
|
+
SessionStatus,
|
|
12
|
+
PersistenceAdapter,
|
|
13
|
+
} from "../types/persistence";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Redis client interface - matches ioredis/redis clients
|
|
17
|
+
*/
|
|
18
|
+
export interface RedisClient {
|
|
19
|
+
get(key: string): Promise<string | null>;
|
|
20
|
+
set(key: string, value: string): Promise<string | null>;
|
|
21
|
+
setex(key: string, seconds: number, value: string): Promise<string>;
|
|
22
|
+
del(...keys: string[]): Promise<number>;
|
|
23
|
+
keys(pattern: string): Promise<string[]>;
|
|
24
|
+
hgetall(key: string): Promise<Record<string, string>>;
|
|
25
|
+
hset(key: string, field: string, value: string): Promise<number>;
|
|
26
|
+
expire(key: string, seconds: number): Promise<number>;
|
|
27
|
+
quit(): Promise<string>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Options for Redis adapter
|
|
32
|
+
*/
|
|
33
|
+
export interface RedisAdapterOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Redis client instance (ioredis or node-redis)
|
|
36
|
+
*/
|
|
37
|
+
redis: RedisClient;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Key prefix for all keys (default: "agent:")
|
|
41
|
+
*/
|
|
42
|
+
keyPrefix?: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* TTL in seconds for sessions (default: 7 days)
|
|
46
|
+
*/
|
|
47
|
+
sessionTTL?: number;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* TTL in seconds for messages (default: 30 days)
|
|
51
|
+
*/
|
|
52
|
+
messageTTL?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Redis Adapter - Provider-style API for Redis persistence
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import Redis from 'ioredis';
|
|
61
|
+
* import { Agent, RedisAdapter } from '@falai/agent';
|
|
62
|
+
*
|
|
63
|
+
* const redis = new Redis();
|
|
64
|
+
*
|
|
65
|
+
* const agent = new Agent({
|
|
66
|
+
* name: "My Agent",
|
|
67
|
+
* ai: provider,
|
|
68
|
+
* persistence: {
|
|
69
|
+
* adapter: new RedisAdapter({ redis }),
|
|
70
|
+
* userId: "user_123",
|
|
71
|
+
* },
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export class RedisAdapter implements PersistenceAdapter {
|
|
76
|
+
public readonly sessionRepository: SessionRepository;
|
|
77
|
+
public readonly messageRepository: MessageRepository;
|
|
78
|
+
private redis: RedisClient;
|
|
79
|
+
private keyPrefix: string;
|
|
80
|
+
private sessionTTL: number;
|
|
81
|
+
private messageTTL: number;
|
|
82
|
+
|
|
83
|
+
constructor(options: RedisAdapterOptions) {
|
|
84
|
+
this.redis = options.redis;
|
|
85
|
+
this.keyPrefix = options.keyPrefix || "agent:";
|
|
86
|
+
this.sessionTTL = options.sessionTTL || 7 * 24 * 60 * 60; // 7 days
|
|
87
|
+
this.messageTTL = options.messageTTL || 30 * 24 * 60 * 60; // 30 days
|
|
88
|
+
|
|
89
|
+
this.sessionRepository = new RedisSessionRepository(
|
|
90
|
+
this.redis,
|
|
91
|
+
this.keyPrefix,
|
|
92
|
+
this.sessionTTL
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
this.messageRepository = new RedisMessageRepository(
|
|
96
|
+
this.redis,
|
|
97
|
+
this.keyPrefix,
|
|
98
|
+
this.messageTTL
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async disconnect(): Promise<void> {
|
|
103
|
+
await this.redis.quit();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Redis Session Repository
|
|
109
|
+
*/
|
|
110
|
+
class RedisSessionRepository implements SessionRepository {
|
|
111
|
+
constructor(
|
|
112
|
+
private redis: RedisClient,
|
|
113
|
+
private keyPrefix: string,
|
|
114
|
+
private ttl: number
|
|
115
|
+
) {}
|
|
116
|
+
|
|
117
|
+
private getKey(id: string): string {
|
|
118
|
+
return `${this.keyPrefix}session:${id}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private getUserKey(userId: string): string {
|
|
122
|
+
return `${this.keyPrefix}user:${userId}:sessions`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async create(
|
|
126
|
+
data: Omit<SessionData, "id" | "createdAt" | "updatedAt">
|
|
127
|
+
): Promise<SessionData> {
|
|
128
|
+
const id = `session_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
129
|
+
const now = new Date();
|
|
130
|
+
const session: SessionData = {
|
|
131
|
+
...data,
|
|
132
|
+
id,
|
|
133
|
+
createdAt: now,
|
|
134
|
+
updatedAt: now,
|
|
135
|
+
status: data.status || "active",
|
|
136
|
+
messageCount: data.messageCount || 0,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
await this.redis.setex(this.getKey(id), this.ttl, JSON.stringify(session));
|
|
140
|
+
|
|
141
|
+
// Add to user's session list
|
|
142
|
+
if (data.userId) {
|
|
143
|
+
await this.redis.hset(
|
|
144
|
+
this.getUserKey(data.userId),
|
|
145
|
+
id,
|
|
146
|
+
now.toISOString()
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return session;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async findById(id: string): Promise<SessionData | null> {
|
|
154
|
+
const data = await this.redis.get(this.getKey(id));
|
|
155
|
+
if (!data) return null;
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(data) as SessionData;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(`Error parsing session data for id ${id}:`, error);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async findActiveByUserId(userId: string): Promise<SessionData | null> {
|
|
165
|
+
const sessionIds = await this.redis.hgetall(this.getUserKey(userId));
|
|
166
|
+
|
|
167
|
+
for (const sessionId of Object.keys(sessionIds)) {
|
|
168
|
+
const session = await this.findById(sessionId);
|
|
169
|
+
if (session && session.status === "active") {
|
|
170
|
+
return session;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async findByUserId(userId: string, limit = 100): Promise<SessionData[]> {
|
|
178
|
+
const sessionIds = await this.redis.hgetall(this.getUserKey(userId));
|
|
179
|
+
const sessions: SessionData[] = [];
|
|
180
|
+
|
|
181
|
+
for (const sessionId of Object.keys(sessionIds).slice(0, limit)) {
|
|
182
|
+
const session = await this.findById(sessionId);
|
|
183
|
+
if (session) {
|
|
184
|
+
sessions.push(session);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return sessions.sort(
|
|
189
|
+
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async update(
|
|
194
|
+
id: string,
|
|
195
|
+
data: Partial<Omit<SessionData, "id" | "createdAt">>
|
|
196
|
+
): Promise<SessionData | null> {
|
|
197
|
+
const existing = await this.findById(id);
|
|
198
|
+
if (!existing) return null;
|
|
199
|
+
|
|
200
|
+
const updated: SessionData = {
|
|
201
|
+
...existing,
|
|
202
|
+
...data,
|
|
203
|
+
updatedAt: new Date(),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
await this.redis.setex(this.getKey(id), this.ttl, JSON.stringify(updated));
|
|
207
|
+
|
|
208
|
+
return updated;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async updateStatus(
|
|
212
|
+
id: string,
|
|
213
|
+
status: SessionStatus,
|
|
214
|
+
completedAt?: Date
|
|
215
|
+
): Promise<SessionData | null> {
|
|
216
|
+
return this.update(id, { status, completedAt });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async updateCollectedData(
|
|
220
|
+
id: string,
|
|
221
|
+
collectedData: Record<string, unknown>
|
|
222
|
+
): Promise<SessionData | null> {
|
|
223
|
+
return this.update(id, { collectedData });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async updateRouteState(
|
|
227
|
+
id: string,
|
|
228
|
+
route?: string,
|
|
229
|
+
state?: string
|
|
230
|
+
): Promise<SessionData | null> {
|
|
231
|
+
return this.update(id, { currentRoute: route, currentState: state });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async incrementMessageCount(id: string): Promise<SessionData | null> {
|
|
235
|
+
const session = await this.findById(id);
|
|
236
|
+
if (!session) return null;
|
|
237
|
+
|
|
238
|
+
return this.update(id, {
|
|
239
|
+
messageCount: (session.messageCount || 0) + 1,
|
|
240
|
+
lastMessageAt: new Date(),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async delete(id: string): Promise<boolean> {
|
|
245
|
+
const result = await this.redis.del(this.getKey(id));
|
|
246
|
+
return result > 0;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Redis Message Repository
|
|
252
|
+
*/
|
|
253
|
+
class RedisMessageRepository implements MessageRepository {
|
|
254
|
+
constructor(
|
|
255
|
+
private redis: RedisClient,
|
|
256
|
+
private keyPrefix: string,
|
|
257
|
+
private ttl: number
|
|
258
|
+
) {}
|
|
259
|
+
|
|
260
|
+
private getKey(id: string): string {
|
|
261
|
+
return `${this.keyPrefix}message:${id}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private getSessionKey(sessionId: string): string {
|
|
265
|
+
return `${this.keyPrefix}session:${sessionId}:messages`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async create(
|
|
269
|
+
data: Omit<MessageData, "id" | "createdAt">
|
|
270
|
+
): Promise<MessageData> {
|
|
271
|
+
const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
272
|
+
const message: MessageData = {
|
|
273
|
+
...data,
|
|
274
|
+
id,
|
|
275
|
+
createdAt: new Date(),
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await this.redis.setex(this.getKey(id), this.ttl, JSON.stringify(message));
|
|
279
|
+
await this.redis.hset(
|
|
280
|
+
this.getSessionKey(data.sessionId),
|
|
281
|
+
id,
|
|
282
|
+
message.createdAt.toISOString()
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return message;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async findById(id: string): Promise<MessageData | null> {
|
|
289
|
+
const data = await this.redis.get(this.getKey(id));
|
|
290
|
+
if (!data) return null;
|
|
291
|
+
try {
|
|
292
|
+
return JSON.parse(data) as MessageData;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error(`Error parsing message data for id ${id}:`, error);
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async findBySessionId(
|
|
300
|
+
sessionId: string,
|
|
301
|
+
limit = 1000
|
|
302
|
+
): Promise<MessageData[]> {
|
|
303
|
+
const messageIds = await this.redis.hgetall(this.getSessionKey(sessionId));
|
|
304
|
+
const messages: MessageData[] = [];
|
|
305
|
+
|
|
306
|
+
for (const messageId of Object.keys(messageIds).slice(0, limit)) {
|
|
307
|
+
const message = await this.findById(messageId);
|
|
308
|
+
if (message) {
|
|
309
|
+
messages.push(message);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return messages.sort(
|
|
314
|
+
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async findByUserId(userId: string, limit = 100): Promise<MessageData[]> {
|
|
319
|
+
// Redis doesn't have efficient user-level querying
|
|
320
|
+
// This would require additional indexing
|
|
321
|
+
const pattern = `${this.keyPrefix}message:*`;
|
|
322
|
+
const keys = await this.redis.keys(pattern);
|
|
323
|
+
const messages: MessageData[] = [];
|
|
324
|
+
|
|
325
|
+
for (const key of keys.slice(0, limit)) {
|
|
326
|
+
const data = await this.redis.get(key);
|
|
327
|
+
if (data) {
|
|
328
|
+
const message: MessageData = JSON.parse(data) as MessageData;
|
|
329
|
+
if (message.userId === userId) {
|
|
330
|
+
messages.push(message);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return messages.sort(
|
|
336
|
+
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async delete(id: string): Promise<boolean> {
|
|
341
|
+
const result = await this.redis.del(this.getKey(id));
|
|
342
|
+
return result > 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async deleteBySessionId(sessionId: string): Promise<number> {
|
|
346
|
+
const messageIds = await this.redis.hgetall(this.getSessionKey(sessionId));
|
|
347
|
+
const keys = Object.keys(messageIds).map((id) => this.getKey(id));
|
|
348
|
+
|
|
349
|
+
if (keys.length === 0) return 0;
|
|
350
|
+
|
|
351
|
+
const result = await this.redis.del(...keys);
|
|
352
|
+
await this.redis.del(this.getSessionKey(sessionId));
|
|
353
|
+
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async deleteByUserId(userId: string): Promise<number> {
|
|
358
|
+
const messages = await this.findByUserId(userId);
|
|
359
|
+
const keys = messages.map((m) => this.getKey(m.id));
|
|
360
|
+
|
|
361
|
+
if (keys.length === 0) return 0;
|
|
362
|
+
|
|
363
|
+
return await this.redis.del(...keys);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database adapters for persistence
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { PrismaAdapter } from "./PrismaAdapter";
|
|
6
|
+
export type {
|
|
7
|
+
PrismaClient,
|
|
8
|
+
FieldMappings,
|
|
9
|
+
PrismaAdapterOptions,
|
|
10
|
+
} from "./PrismaAdapter";
|
|
11
|
+
|
|
12
|
+
export { RedisAdapter } from "./RedisAdapter";
|
|
13
|
+
export type { RedisClient, RedisAdapterOptions } from "./RedisAdapter";
|
|
14
|
+
|
|
15
|
+
export { MongoAdapter } from "./MongoAdapter";
|
|
16
|
+
export type {
|
|
17
|
+
MongoClient,
|
|
18
|
+
MongoDatabase,
|
|
19
|
+
MongoCollection,
|
|
20
|
+
MongoAdapterOptions,
|
|
21
|
+
} from "./MongoAdapter";
|
|
22
|
+
|
|
23
|
+
export { PostgreSQLAdapter } from "./PostgreSQLAdapter";
|
|
24
|
+
export type {
|
|
25
|
+
PgClient,
|
|
26
|
+
PgQueryResult,
|
|
27
|
+
PostgreSQLAdapterOptions,
|
|
28
|
+
} from "./PostgreSQLAdapter";
|
package/src/core/Agent.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { Route } from "./Route";
|
|
|
16
16
|
import { DomainRegistry } from "./DomainRegistry";
|
|
17
17
|
import { PromptBuilder } from "./PromptBuilder";
|
|
18
18
|
import { Observation } from "./Observation";
|
|
19
|
+
import { PersistenceManager } from "./PersistenceManager";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Main Agent class with generic context support
|
|
@@ -28,6 +29,7 @@ export class Agent<TContext = unknown> {
|
|
|
28
29
|
private observations: Observation[] = [];
|
|
29
30
|
private domainRegistry = new DomainRegistry();
|
|
30
31
|
private context: TContext | undefined;
|
|
32
|
+
private persistenceManager: PersistenceManager | undefined;
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Dynamic domain property - populated via addDomain
|
|
@@ -50,6 +52,21 @@ export class Agent<TContext = unknown> {
|
|
|
50
52
|
// Initialize context if provided
|
|
51
53
|
this.context = options.context;
|
|
52
54
|
|
|
55
|
+
// Initialize persistence if configured
|
|
56
|
+
if (options.persistence) {
|
|
57
|
+
this.persistenceManager = new PersistenceManager(options.persistence);
|
|
58
|
+
|
|
59
|
+
// Initialize the adapter if it has an initialize method
|
|
60
|
+
if (options.persistence.adapter.initialize) {
|
|
61
|
+
options.persistence.adapter.initialize().catch((error) => {
|
|
62
|
+
console.error(
|
|
63
|
+
"[Agent] Persistence adapter initialization failed:",
|
|
64
|
+
error
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
// Initialize from options
|
|
54
71
|
if (options.terms) {
|
|
55
72
|
this.terms = [...options.terms];
|
|
@@ -598,6 +615,20 @@ export class Agent<TContext = unknown> {
|
|
|
598
615
|
return this.domainRegistry;
|
|
599
616
|
}
|
|
600
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Get the persistence manager (if configured)
|
|
620
|
+
*/
|
|
621
|
+
getPersistenceManager(): PersistenceManager | undefined {
|
|
622
|
+
return this.persistenceManager;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Check if persistence is enabled
|
|
627
|
+
*/
|
|
628
|
+
hasPersistence(): boolean {
|
|
629
|
+
return this.persistenceManager !== undefined;
|
|
630
|
+
}
|
|
631
|
+
|
|
601
632
|
/**
|
|
602
633
|
* Get allowed domains for a specific route
|
|
603
634
|
* @param routeId - Route ID to check
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence Manager
|
|
3
|
+
* Handles optional persistence of sessions and messages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
PersistenceConfig,
|
|
8
|
+
SessionData,
|
|
9
|
+
MessageData,
|
|
10
|
+
CreateSessionOptions,
|
|
11
|
+
SaveMessageOptions,
|
|
12
|
+
SessionStatus,
|
|
13
|
+
SessionRepository,
|
|
14
|
+
MessageRepository,
|
|
15
|
+
} from "../types/persistence";
|
|
16
|
+
import type { Event } from "../types/history";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Manager for handling persistence operations
|
|
20
|
+
* Provides a clean interface for optional database persistence
|
|
21
|
+
*/
|
|
22
|
+
export class PersistenceManager {
|
|
23
|
+
private config: PersistenceConfig;
|
|
24
|
+
private sessionRepository: SessionRepository;
|
|
25
|
+
private messageRepository: MessageRepository;
|
|
26
|
+
|
|
27
|
+
constructor(config: PersistenceConfig) {
|
|
28
|
+
this.config = {
|
|
29
|
+
autoSave: true,
|
|
30
|
+
...config,
|
|
31
|
+
};
|
|
32
|
+
this.sessionRepository = config.adapter.sessionRepository;
|
|
33
|
+
this.messageRepository = config.adapter.messageRepository;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a new session
|
|
38
|
+
*/
|
|
39
|
+
async createSession(options: CreateSessionOptions): Promise<SessionData> {
|
|
40
|
+
const userId = options.userId || this.config.userId;
|
|
41
|
+
|
|
42
|
+
return await this.sessionRepository.create({
|
|
43
|
+
userId,
|
|
44
|
+
agentName: options.agentName,
|
|
45
|
+
status: "active",
|
|
46
|
+
collectedData: options.initialData || {},
|
|
47
|
+
messageCount: 0,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get session by ID
|
|
53
|
+
*/
|
|
54
|
+
async getSession(sessionId: string): Promise<SessionData | null> {
|
|
55
|
+
return await this.sessionRepository.findById(sessionId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Find active session for a user
|
|
60
|
+
*/
|
|
61
|
+
async findActiveSession(userId?: string): Promise<SessionData | null> {
|
|
62
|
+
const effectiveUserId = userId || this.config.userId;
|
|
63
|
+
if (!effectiveUserId) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"userId must be provided or configured in PersistenceConfig"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return await this.sessionRepository.findActiveByUserId(effectiveUserId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get all sessions for a user
|
|
73
|
+
*/
|
|
74
|
+
async getUserSessions(
|
|
75
|
+
userId?: string,
|
|
76
|
+
limit?: number
|
|
77
|
+
): Promise<SessionData[]> {
|
|
78
|
+
const effectiveUserId = userId || this.config.userId;
|
|
79
|
+
if (!effectiveUserId) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"userId must be provided or configured in PersistenceConfig"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return await this.sessionRepository.findByUserId(effectiveUserId, limit);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Update session status
|
|
89
|
+
*/
|
|
90
|
+
async updateSessionStatus(
|
|
91
|
+
sessionId: string,
|
|
92
|
+
status: SessionStatus,
|
|
93
|
+
completedAt?: Date
|
|
94
|
+
): Promise<SessionData | null> {
|
|
95
|
+
return await this.sessionRepository.updateStatus(
|
|
96
|
+
sessionId,
|
|
97
|
+
status,
|
|
98
|
+
completedAt
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Update collected data in session
|
|
104
|
+
*/
|
|
105
|
+
async updateCollectedData(
|
|
106
|
+
sessionId: string,
|
|
107
|
+
collectedData: Record<string, unknown>
|
|
108
|
+
): Promise<SessionData | null> {
|
|
109
|
+
return await this.sessionRepository.updateCollectedData(
|
|
110
|
+
sessionId,
|
|
111
|
+
collectedData
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Update current route and state
|
|
117
|
+
*/
|
|
118
|
+
async updateRouteState(
|
|
119
|
+
sessionId: string,
|
|
120
|
+
route?: string,
|
|
121
|
+
state?: string
|
|
122
|
+
): Promise<SessionData | null> {
|
|
123
|
+
return await this.sessionRepository.updateRouteState(
|
|
124
|
+
sessionId,
|
|
125
|
+
route,
|
|
126
|
+
state
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Save a message
|
|
132
|
+
*/
|
|
133
|
+
async saveMessage(options: SaveMessageOptions): Promise<MessageData> {
|
|
134
|
+
const userId = options.userId || this.config.userId;
|
|
135
|
+
|
|
136
|
+
const message = await this.messageRepository.create({
|
|
137
|
+
sessionId: options.sessionId,
|
|
138
|
+
userId,
|
|
139
|
+
role: options.role,
|
|
140
|
+
content: options.content,
|
|
141
|
+
route: options.route,
|
|
142
|
+
state: options.state,
|
|
143
|
+
toolCalls: options.toolCalls,
|
|
144
|
+
event: options.event,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Increment message count in session if autoSave is enabled
|
|
148
|
+
if (this.config.autoSave) {
|
|
149
|
+
await this.sessionRepository.incrementMessageCount(options.sessionId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return message;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get all messages for a session
|
|
157
|
+
*/
|
|
158
|
+
async getSessionMessages(
|
|
159
|
+
sessionId: string,
|
|
160
|
+
limit?: number
|
|
161
|
+
): Promise<MessageData[]> {
|
|
162
|
+
return await this.messageRepository.findBySessionId(sessionId, limit);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get messages for a user
|
|
167
|
+
*/
|
|
168
|
+
async getUserMessages(
|
|
169
|
+
userId?: string,
|
|
170
|
+
limit?: number
|
|
171
|
+
): Promise<MessageData[]> {
|
|
172
|
+
const effectiveUserId = userId || this.config.userId;
|
|
173
|
+
if (!effectiveUserId) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"userId must be provided or configured in PersistenceConfig"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return await this.messageRepository.findByUserId(effectiveUserId, limit);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Delete a session and all its messages
|
|
183
|
+
*/
|
|
184
|
+
async deleteSession(sessionId: string): Promise<boolean> {
|
|
185
|
+
// Delete all messages first
|
|
186
|
+
await this.messageRepository.deleteBySessionId(sessionId);
|
|
187
|
+
|
|
188
|
+
// Then delete the session
|
|
189
|
+
return await this.sessionRepository.delete(sessionId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Complete a session
|
|
194
|
+
*/
|
|
195
|
+
async completeSession(sessionId: string): Promise<SessionData | null> {
|
|
196
|
+
return await this.updateSessionStatus(sessionId, "completed", new Date());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Abandon a session
|
|
201
|
+
*/
|
|
202
|
+
async abandonSession(sessionId: string): Promise<SessionData | null> {
|
|
203
|
+
return await this.updateSessionStatus(sessionId, "abandoned");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Helper: Convert message data to Event format
|
|
208
|
+
*/
|
|
209
|
+
messageToEvent(message: MessageData): Event | undefined {
|
|
210
|
+
return message.event;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Helper: Load history from session messages
|
|
215
|
+
*/
|
|
216
|
+
async loadSessionHistory(sessionId: string): Promise<Event[]> {
|
|
217
|
+
const messages = await this.getSessionMessages(sessionId);
|
|
218
|
+
return messages
|
|
219
|
+
.map((m) => this.messageToEvent(m))
|
|
220
|
+
.filter((e): e is Event => e !== undefined);
|
|
221
|
+
}
|
|
222
|
+
}
|