@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.
Files changed (101) hide show
  1. package/README.md +164 -0
  2. package/dist/adapters/MongoAdapter.d.ts +97 -0
  3. package/dist/adapters/MongoAdapter.d.ts.map +1 -0
  4. package/dist/adapters/MongoAdapter.js +163 -0
  5. package/dist/adapters/MongoAdapter.js.map +1 -0
  6. package/dist/adapters/PostgreSQLAdapter.d.ts +71 -0
  7. package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  8. package/dist/adapters/PostgreSQLAdapter.js +256 -0
  9. package/dist/adapters/PostgreSQLAdapter.js.map +1 -0
  10. package/dist/adapters/PrismaAdapter.d.ts +115 -0
  11. package/dist/adapters/PrismaAdapter.d.ts.map +1 -0
  12. package/dist/adapters/PrismaAdapter.js +331 -0
  13. package/dist/adapters/PrismaAdapter.js.map +1 -0
  14. package/dist/adapters/RedisAdapter.d.ts +71 -0
  15. package/dist/adapters/RedisAdapter.d.ts.map +1 -0
  16. package/dist/adapters/RedisAdapter.js +226 -0
  17. package/dist/adapters/RedisAdapter.js.map +1 -0
  18. package/dist/adapters/index.d.ts +12 -0
  19. package/dist/adapters/index.d.ts.map +1 -0
  20. package/dist/adapters/index.js +8 -0
  21. package/dist/adapters/index.js.map +1 -0
  22. package/dist/cjs/adapters/MongoAdapter.d.ts +97 -0
  23. package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -0
  24. package/dist/cjs/adapters/MongoAdapter.js +167 -0
  25. package/dist/cjs/adapters/MongoAdapter.js.map +1 -0
  26. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +71 -0
  27. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  28. package/dist/cjs/adapters/PostgreSQLAdapter.js +260 -0
  29. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -0
  30. package/dist/cjs/adapters/PrismaAdapter.d.ts +115 -0
  31. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -0
  32. package/dist/cjs/adapters/PrismaAdapter.js +335 -0
  33. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -0
  34. package/dist/cjs/adapters/RedisAdapter.d.ts +71 -0
  35. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -0
  36. package/dist/cjs/adapters/RedisAdapter.js +230 -0
  37. package/dist/cjs/adapters/RedisAdapter.js.map +1 -0
  38. package/dist/cjs/adapters/index.d.ts +12 -0
  39. package/dist/cjs/adapters/index.d.ts.map +1 -0
  40. package/dist/cjs/adapters/index.js +15 -0
  41. package/dist/cjs/adapters/index.js.map +1 -0
  42. package/dist/cjs/core/Agent.d.ts +10 -0
  43. package/dist/cjs/core/Agent.d.ts.map +1 -1
  44. package/dist/cjs/core/Agent.js +23 -0
  45. package/dist/cjs/core/Agent.js.map +1 -1
  46. package/dist/cjs/core/PersistenceManager.d.ts +77 -0
  47. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -0
  48. package/dist/cjs/core/PersistenceManager.js +153 -0
  49. package/dist/cjs/core/PersistenceManager.js.map +1 -0
  50. package/dist/cjs/index.d.ts +10 -0
  51. package/dist/cjs/index.d.ts.map +1 -1
  52. package/dist/cjs/index.js +12 -1
  53. package/dist/cjs/index.js.map +1 -1
  54. package/dist/cjs/types/agent.d.ts +3 -0
  55. package/dist/cjs/types/agent.d.ts.map +1 -1
  56. package/dist/cjs/types/agent.js.map +1 -1
  57. package/dist/cjs/types/index.d.ts +1 -0
  58. package/dist/cjs/types/index.d.ts.map +1 -1
  59. package/dist/cjs/types/persistence.d.ts +194 -0
  60. package/dist/cjs/types/persistence.d.ts.map +1 -0
  61. package/dist/cjs/types/persistence.js +7 -0
  62. package/dist/cjs/types/persistence.js.map +1 -0
  63. package/dist/core/Agent.d.ts +10 -0
  64. package/dist/core/Agent.d.ts.map +1 -1
  65. package/dist/core/Agent.js +23 -0
  66. package/dist/core/Agent.js.map +1 -1
  67. package/dist/core/PersistenceManager.d.ts +77 -0
  68. package/dist/core/PersistenceManager.d.ts.map +1 -0
  69. package/dist/core/PersistenceManager.js +149 -0
  70. package/dist/core/PersistenceManager.js.map +1 -0
  71. package/dist/index.d.ts +10 -0
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +6 -0
  74. package/dist/index.js.map +1 -1
  75. package/dist/types/agent.d.ts +3 -0
  76. package/dist/types/agent.d.ts.map +1 -1
  77. package/dist/types/agent.js.map +1 -1
  78. package/dist/types/index.d.ts +1 -0
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/persistence.d.ts +194 -0
  81. package/dist/types/persistence.d.ts.map +1 -0
  82. package/dist/types/persistence.js +6 -0
  83. package/dist/types/persistence.js.map +1 -0
  84. package/docs/ADAPTERS.md +127 -0
  85. package/docs/API_REFERENCE.md +337 -0
  86. package/docs/PERSISTENCE.md +513 -0
  87. package/examples/prisma-persistence.ts +313 -0
  88. package/examples/prisma-schema.example.prisma +74 -0
  89. package/examples/redis-persistence.ts +89 -0
  90. package/package.json +29 -1
  91. package/src/adapters/MongoAdapter.ts +295 -0
  92. package/src/adapters/PostgreSQLAdapter.ts +417 -0
  93. package/src/adapters/PrismaAdapter.ts +510 -0
  94. package/src/adapters/RedisAdapter.ts +365 -0
  95. package/src/adapters/index.ts +28 -0
  96. package/src/core/Agent.ts +31 -0
  97. package/src/core/PersistenceManager.ts +222 -0
  98. package/src/index.ts +36 -0
  99. package/src/types/agent.ts +3 -0
  100. package/src/types/index.ts +14 -0
  101. package/src/types/persistence.ts +234 -0
@@ -0,0 +1,295 @@
1
+ /**
2
+ * MongoDB adapter for persistence
3
+ * Document-based storage with flexible schema
4
+ */
5
+
6
+ import type {
7
+ SessionRepository,
8
+ MessageRepository,
9
+ SessionData,
10
+ MessageData,
11
+ SessionStatus,
12
+ PersistenceAdapter,
13
+ } from "../types/persistence";
14
+
15
+ /**
16
+ * MongoDB collection interface - matches mongodb driver
17
+ */
18
+ export interface MongoCollection<T = Record<string, unknown>> {
19
+ insertOne(doc: T): Promise<{ insertedId: unknown }>;
20
+ findOne(filter: Record<string, unknown>): Promise<T | null>;
21
+ find(filter: Record<string, unknown>): {
22
+ sort(sort: Record<string, number>): {
23
+ limit(limit: number): {
24
+ toArray(): Promise<T[]>;
25
+ };
26
+ };
27
+ toArray(): Promise<T[]>;
28
+ };
29
+ updateOne(
30
+ filter: Record<string, unknown>,
31
+ update: Record<string, unknown>
32
+ ): Promise<{ matchedCount: number }>;
33
+ deleteOne(filter: Record<string, unknown>): Promise<{ deletedCount: number }>;
34
+ deleteMany(
35
+ filter: Record<string, unknown>
36
+ ): Promise<{ deletedCount: number }>;
37
+ }
38
+
39
+ /**
40
+ * MongoDB database interface
41
+ */
42
+ export interface MongoDatabase {
43
+ collection<T = Record<string, unknown>>(name: string): MongoCollection<T>;
44
+ }
45
+
46
+ /**
47
+ * MongoDB client interface
48
+ */
49
+ export interface MongoClient {
50
+ db(name?: string): MongoDatabase;
51
+ close(): Promise<void>;
52
+ }
53
+
54
+ /**
55
+ * Options for MongoDB adapter
56
+ */
57
+ export interface MongoAdapterOptions {
58
+ /**
59
+ * MongoDB client instance
60
+ */
61
+ client: MongoClient;
62
+
63
+ /**
64
+ * Database name
65
+ */
66
+ databaseName: string;
67
+
68
+ /**
69
+ * Collection names (default: "agent_sessions" and "agent_messages")
70
+ */
71
+ collections?: {
72
+ sessions?: string;
73
+ messages?: string;
74
+ };
75
+ }
76
+
77
+ /**
78
+ * MongoDB Adapter - Provider-style API for MongoDB persistence
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * import { MongoClient } from 'mongodb';
83
+ * import { Agent, MongoAdapter } from '@falai/agent';
84
+ *
85
+ * const client = new MongoClient('mongodb://localhost:27017');
86
+ * await client.connect();
87
+ *
88
+ * const agent = new Agent({
89
+ * name: "My Agent",
90
+ * ai: provider,
91
+ * persistence: {
92
+ * adapter: new MongoAdapter({
93
+ * client,
94
+ * databaseName: 'myapp',
95
+ * }),
96
+ * userId: "user_123",
97
+ * },
98
+ * });
99
+ * ```
100
+ */
101
+ export class MongoAdapter implements PersistenceAdapter {
102
+ public readonly sessionRepository: SessionRepository;
103
+ public readonly messageRepository: MessageRepository;
104
+ private client: MongoClient;
105
+ private db: MongoDatabase;
106
+
107
+ constructor(options: MongoAdapterOptions) {
108
+ this.client = options.client;
109
+ this.db = options.client.db(options.databaseName);
110
+
111
+ const sessionCollection = options.collections?.sessions || "agent_sessions";
112
+ const messageCollection = options.collections?.messages || "agent_messages";
113
+
114
+ this.sessionRepository = new MongoSessionRepository(
115
+ this.db.collection(sessionCollection)
116
+ );
117
+
118
+ this.messageRepository = new MongoMessageRepository(
119
+ this.db.collection(messageCollection)
120
+ );
121
+ }
122
+
123
+ async disconnect(): Promise<void> {
124
+ await this.client.close();
125
+ }
126
+ }
127
+
128
+ /**
129
+ * MongoDB Session Repository
130
+ */
131
+ class MongoSessionRepository implements SessionRepository {
132
+ constructor(private collection: MongoCollection<SessionData>) {}
133
+
134
+ async create(
135
+ data: Omit<SessionData, "id" | "createdAt" | "updatedAt">
136
+ ): Promise<SessionData> {
137
+ const now = new Date();
138
+ const session: SessionData = {
139
+ ...data,
140
+ id: `session_${Date.now()}_${Math.random().toString(36).slice(2)}`,
141
+ status: data.status || "active",
142
+ messageCount: data.messageCount || 0,
143
+ createdAt: now,
144
+ updatedAt: now,
145
+ };
146
+
147
+ await this.collection.insertOne(session);
148
+ return session;
149
+ }
150
+
151
+ async findById(id: string): Promise<SessionData | null> {
152
+ return await this.collection.findOne({ id });
153
+ }
154
+
155
+ async findActiveByUserId(userId: string): Promise<SessionData | null> {
156
+ return await this.collection.findOne({ userId, status: "active" });
157
+ }
158
+
159
+ async findByUserId(userId: string, limit = 100): Promise<SessionData[]> {
160
+ return await this.collection
161
+ .find({ userId })
162
+ .sort({ createdAt: -1 })
163
+ .limit(limit)
164
+ .toArray();
165
+ }
166
+
167
+ async update(
168
+ id: string,
169
+ data: Partial<Omit<SessionData, "id" | "createdAt">>
170
+ ): Promise<SessionData | null> {
171
+ const result = await this.collection.updateOne(
172
+ { id },
173
+ { $set: { ...data, updatedAt: new Date() } }
174
+ );
175
+
176
+ if (result.matchedCount === 0) return null;
177
+ return await this.findById(id);
178
+ }
179
+
180
+ async updateStatus(
181
+ id: string,
182
+ status: SessionStatus,
183
+ completedAt?: Date
184
+ ): Promise<SessionData | null> {
185
+ const updateData: Record<string, unknown> = {
186
+ status,
187
+ updatedAt: new Date(),
188
+ };
189
+ if (completedAt) {
190
+ updateData.completedAt = completedAt;
191
+ }
192
+
193
+ const result = await this.collection.updateOne(
194
+ { id },
195
+ { $set: updateData }
196
+ );
197
+
198
+ if (result.matchedCount === 0) return null;
199
+ return await this.findById(id);
200
+ }
201
+
202
+ async updateCollectedData(
203
+ id: string,
204
+ collectedData: Record<string, unknown>
205
+ ): Promise<SessionData | null> {
206
+ return await this.update(id, { collectedData });
207
+ }
208
+
209
+ async updateRouteState(
210
+ id: string,
211
+ route?: string,
212
+ state?: string
213
+ ): Promise<SessionData | null> {
214
+ return await this.update(id, {
215
+ currentRoute: route,
216
+ currentState: state,
217
+ });
218
+ }
219
+
220
+ async incrementMessageCount(id: string): Promise<SessionData | null> {
221
+ const result = await this.collection.updateOne(
222
+ { id },
223
+ {
224
+ $inc: { messageCount: 1 },
225
+ $set: { lastMessageAt: new Date(), updatedAt: new Date() },
226
+ }
227
+ );
228
+
229
+ if (result.matchedCount === 0) return null;
230
+ return await this.findById(id);
231
+ }
232
+
233
+ async delete(id: string): Promise<boolean> {
234
+ const result = await this.collection.deleteOne({ id });
235
+ return result.deletedCount > 0;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * MongoDB Message Repository
241
+ */
242
+ class MongoMessageRepository implements MessageRepository {
243
+ constructor(private collection: MongoCollection<MessageData>) {}
244
+
245
+ async create(
246
+ data: Omit<MessageData, "id" | "createdAt">
247
+ ): Promise<MessageData> {
248
+ const message: MessageData = {
249
+ ...data,
250
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`,
251
+ createdAt: new Date(),
252
+ };
253
+
254
+ await this.collection.insertOne(message);
255
+ return message;
256
+ }
257
+
258
+ async findById(id: string): Promise<MessageData | null> {
259
+ return await this.collection.findOne({ id });
260
+ }
261
+
262
+ async findBySessionId(
263
+ sessionId: string,
264
+ limit = 1000
265
+ ): Promise<MessageData[]> {
266
+ return await this.collection
267
+ .find({ sessionId })
268
+ .sort({ createdAt: 1 })
269
+ .limit(limit)
270
+ .toArray();
271
+ }
272
+
273
+ async findByUserId(userId: string, limit = 100): Promise<MessageData[]> {
274
+ return await this.collection
275
+ .find({ userId })
276
+ .sort({ createdAt: -1 })
277
+ .limit(limit)
278
+ .toArray();
279
+ }
280
+
281
+ async delete(id: string): Promise<boolean> {
282
+ const result = await this.collection.deleteOne({ id });
283
+ return result.deletedCount > 0;
284
+ }
285
+
286
+ async deleteBySessionId(sessionId: string): Promise<number> {
287
+ const result = await this.collection.deleteMany({ sessionId });
288
+ return result.deletedCount;
289
+ }
290
+
291
+ async deleteByUserId(userId: string): Promise<number> {
292
+ const result = await this.collection.deleteMany({ userId });
293
+ return result.deletedCount;
294
+ }
295
+ }
@@ -0,0 +1,417 @@
1
+ /**
2
+ * PostgreSQL adapter for persistence
3
+ * Raw SQL adapter for PostgreSQL with custom schemas
4
+ */
5
+
6
+ import type {
7
+ SessionRepository,
8
+ MessageRepository,
9
+ SessionData,
10
+ MessageData,
11
+ SessionStatus,
12
+ PersistenceAdapter,
13
+ } from "../types/persistence";
14
+
15
+ /**
16
+ * PostgreSQL query result interface
17
+ */
18
+ export interface PgQueryResult<T = Record<string, unknown>> {
19
+ rows: T[];
20
+ rowCount: number;
21
+ }
22
+
23
+ /**
24
+ * PostgreSQL client interface - matches pg (node-postgres)
25
+ */
26
+ export interface PgClient {
27
+ query<T = Record<string, unknown>>(
28
+ sql: string,
29
+ values?: unknown[]
30
+ ): Promise<PgQueryResult<T>>;
31
+ end(): Promise<void>;
32
+ }
33
+
34
+ /**
35
+ * Options for PostgreSQL adapter
36
+ */
37
+ export interface PostgreSQLAdapterOptions {
38
+ /**
39
+ * PostgreSQL client instance (from 'pg' package)
40
+ */
41
+ client: PgClient;
42
+
43
+ /**
44
+ * Table names (default: "agent_sessions" and "agent_messages")
45
+ */
46
+ tables?: {
47
+ sessions?: string;
48
+ messages?: string;
49
+ };
50
+ }
51
+
52
+ /**
53
+ * PostgreSQL Adapter - Provider-style API for PostgreSQL persistence
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { Client } from 'pg';
58
+ * import { Agent, PostgreSQLAdapter } from '@falai/agent';
59
+ *
60
+ * const client = new Client({
61
+ * host: 'localhost',
62
+ * port: 5432,
63
+ * database: 'myapp',
64
+ * user: 'postgres',
65
+ * password: 'password',
66
+ * });
67
+ * await client.connect();
68
+ *
69
+ * const agent = new Agent({
70
+ * name: "My Agent",
71
+ * ai: provider,
72
+ * persistence: {
73
+ * adapter: new PostgreSQLAdapter({ client }),
74
+ * userId: "user_123",
75
+ * },
76
+ * });
77
+ * ```
78
+ */
79
+ export class PostgreSQLAdapter implements PersistenceAdapter {
80
+ public readonly sessionRepository: SessionRepository;
81
+ public readonly messageRepository: MessageRepository;
82
+ private client: PgClient;
83
+
84
+ constructor(options: PostgreSQLAdapterOptions) {
85
+ this.client = options.client;
86
+
87
+ const sessionTable = options.tables?.sessions || "agent_sessions";
88
+ const messageTable = options.tables?.messages || "agent_messages";
89
+
90
+ this.sessionRepository = new PostgreSQLSessionRepository(
91
+ this.client,
92
+ sessionTable
93
+ );
94
+
95
+ this.messageRepository = new PostgreSQLMessageRepository(
96
+ this.client,
97
+ messageTable
98
+ );
99
+ }
100
+
101
+ async initialize(): Promise<void> {
102
+ // Create tables if they don't exist
103
+ const sessionTable = "agent_sessions";
104
+ const messageTable = "agent_messages";
105
+
106
+ await this.client.query(`
107
+ CREATE TABLE IF NOT EXISTS ${sessionTable} (
108
+ id VARCHAR(255) PRIMARY KEY,
109
+ user_id VARCHAR(255),
110
+ agent_name VARCHAR(255),
111
+ status VARCHAR(50) DEFAULT 'active',
112
+ current_route VARCHAR(255),
113
+ current_state VARCHAR(255),
114
+ collected_data JSONB,
115
+ message_count INTEGER DEFAULT 0,
116
+ last_message_at TIMESTAMP,
117
+ completed_at TIMESTAMP,
118
+ created_at TIMESTAMP DEFAULT NOW(),
119
+ updated_at TIMESTAMP DEFAULT NOW()
120
+ )
121
+ `);
122
+
123
+ await this.client.query(`
124
+ CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON ${sessionTable}(user_id);
125
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON ${sessionTable}(status);
126
+ `);
127
+
128
+ await this.client.query(`
129
+ CREATE TABLE IF NOT EXISTS ${messageTable} (
130
+ id VARCHAR(255) PRIMARY KEY,
131
+ session_id VARCHAR(255) NOT NULL,
132
+ user_id VARCHAR(255),
133
+ role VARCHAR(50) NOT NULL,
134
+ content TEXT NOT NULL,
135
+ route VARCHAR(255),
136
+ state VARCHAR(255),
137
+ tool_calls JSONB,
138
+ event JSONB,
139
+ created_at TIMESTAMP DEFAULT NOW(),
140
+ FOREIGN KEY (session_id) REFERENCES ${sessionTable}(id) ON DELETE CASCADE
141
+ )
142
+ `);
143
+
144
+ await this.client.query(`
145
+ CREATE INDEX IF NOT EXISTS idx_messages_session_id ON ${messageTable}(session_id);
146
+ CREATE INDEX IF NOT EXISTS idx_messages_user_id ON ${messageTable}(user_id);
147
+ `);
148
+ }
149
+
150
+ async disconnect(): Promise<void> {
151
+ await this.client.end();
152
+ }
153
+ }
154
+
155
+ /**
156
+ * PostgreSQL Session Repository
157
+ */
158
+ class PostgreSQLSessionRepository implements SessionRepository {
159
+ constructor(private client: PgClient, private tableName: string) {}
160
+
161
+ async create(
162
+ data: Omit<SessionData, "id" | "createdAt" | "updatedAt">
163
+ ): Promise<SessionData> {
164
+ const id = `session_${Date.now()}_${Math.random().toString(36).slice(2)}`;
165
+ const now = new Date();
166
+
167
+ const result = await this.client.query<SessionData>(
168
+ `INSERT INTO ${this.tableName}
169
+ (id, user_id, agent_name, status, collected_data, message_count, created_at, updated_at)
170
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
171
+ RETURNING *`,
172
+ [
173
+ id,
174
+ data.userId || null,
175
+ data.agentName || null,
176
+ data.status || "active",
177
+ JSON.stringify(data.collectedData || {}),
178
+ data.messageCount || 0,
179
+ now,
180
+ now,
181
+ ]
182
+ );
183
+
184
+ return result.rows[0];
185
+ }
186
+
187
+ async findById(id: string): Promise<SessionData | null> {
188
+ const result = await this.client.query<SessionData>(
189
+ `SELECT * FROM ${this.tableName} WHERE id = $1`,
190
+ [id]
191
+ );
192
+
193
+ return result.rows[0] || null;
194
+ }
195
+
196
+ async findActiveByUserId(userId: string): Promise<SessionData | null> {
197
+ const result = await this.client.query<SessionData>(
198
+ `SELECT * FROM ${this.tableName}
199
+ WHERE user_id = $1 AND status = 'active'
200
+ ORDER BY created_at DESC
201
+ LIMIT 1`,
202
+ [userId]
203
+ );
204
+
205
+ return result.rows[0] || null;
206
+ }
207
+
208
+ async findByUserId(userId: string, limit = 100): Promise<SessionData[]> {
209
+ const result = await this.client.query<SessionData>(
210
+ `SELECT * FROM ${this.tableName}
211
+ WHERE user_id = $1
212
+ ORDER BY created_at DESC
213
+ LIMIT $2`,
214
+ [userId, limit]
215
+ );
216
+
217
+ return result.rows;
218
+ }
219
+
220
+ async update(
221
+ id: string,
222
+ data: Partial<Omit<SessionData, "id" | "createdAt">>
223
+ ): Promise<SessionData | null> {
224
+ const fields: string[] = [];
225
+ const values: unknown[] = [];
226
+ let paramIndex = 1;
227
+
228
+ if (data.status !== undefined) {
229
+ fields.push(`status = $${paramIndex++}`);
230
+ values.push(data.status);
231
+ }
232
+ if (data.collectedData !== undefined) {
233
+ fields.push(`collected_data = $${paramIndex++}`);
234
+ values.push(JSON.stringify(data.collectedData));
235
+ }
236
+ if (data.currentRoute !== undefined) {
237
+ fields.push(`current_route = $${paramIndex++}`);
238
+ values.push(data.currentRoute);
239
+ }
240
+ if (data.currentState !== undefined) {
241
+ fields.push(`current_state = $${paramIndex++}`);
242
+ values.push(data.currentState);
243
+ }
244
+ if (data.messageCount !== undefined) {
245
+ fields.push(`message_count = $${paramIndex++}`);
246
+ values.push(data.messageCount);
247
+ }
248
+ if (data.lastMessageAt !== undefined) {
249
+ fields.push(`last_message_at = $${paramIndex++}`);
250
+ values.push(data.lastMessageAt);
251
+ }
252
+ if (data.completedAt !== undefined) {
253
+ fields.push(`completed_at = $${paramIndex++}`);
254
+ values.push(data.completedAt);
255
+ }
256
+
257
+ fields.push(`updated_at = $${paramIndex++}`);
258
+ values.push(new Date());
259
+
260
+ values.push(id);
261
+
262
+ const result = await this.client.query<SessionData>(
263
+ `UPDATE ${this.tableName}
264
+ SET ${fields.join(", ")}
265
+ WHERE id = $${paramIndex}
266
+ RETURNING *`,
267
+ values
268
+ );
269
+
270
+ return result.rows[0] || null;
271
+ }
272
+
273
+ async updateStatus(
274
+ id: string,
275
+ status: SessionStatus,
276
+ completedAt?: Date
277
+ ): Promise<SessionData | null> {
278
+ return await this.update(id, { status, completedAt });
279
+ }
280
+
281
+ async updateCollectedData(
282
+ id: string,
283
+ collectedData: Record<string, unknown>
284
+ ): Promise<SessionData | null> {
285
+ return await this.update(id, { collectedData });
286
+ }
287
+
288
+ async updateRouteState(
289
+ id: string,
290
+ route?: string,
291
+ state?: string
292
+ ): Promise<SessionData | null> {
293
+ return await this.update(id, {
294
+ currentRoute: route,
295
+ currentState: state,
296
+ });
297
+ }
298
+
299
+ async incrementMessageCount(id: string): Promise<SessionData | null> {
300
+ const result = await this.client.query<SessionData>(
301
+ `UPDATE ${this.tableName}
302
+ SET message_count = message_count + 1,
303
+ last_message_at = NOW(),
304
+ updated_at = NOW()
305
+ WHERE id = $1
306
+ RETURNING *`,
307
+ [id]
308
+ );
309
+
310
+ return result.rows[0] || null;
311
+ }
312
+
313
+ async delete(id: string): Promise<boolean> {
314
+ const result = await this.client.query(
315
+ `DELETE FROM ${this.tableName} WHERE id = $1`,
316
+ [id]
317
+ );
318
+
319
+ return result.rowCount > 0;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * PostgreSQL Message Repository
325
+ */
326
+ class PostgreSQLMessageRepository implements MessageRepository {
327
+ constructor(private client: PgClient, private tableName: string) {}
328
+
329
+ async create(
330
+ data: Omit<MessageData, "id" | "createdAt">
331
+ ): Promise<MessageData> {
332
+ const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
333
+
334
+ const result = await this.client.query<MessageData>(
335
+ `INSERT INTO ${this.tableName}
336
+ (id, session_id, user_id, role, content, route, state, tool_calls, event, created_at)
337
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
338
+ RETURNING *`,
339
+ [
340
+ id,
341
+ data.sessionId,
342
+ data.userId || null,
343
+ data.role,
344
+ data.content,
345
+ data.route || null,
346
+ data.state || null,
347
+ JSON.stringify(data.toolCalls || null),
348
+ JSON.stringify(data.event || null),
349
+ ]
350
+ );
351
+
352
+ return result.rows[0];
353
+ }
354
+
355
+ async findById(id: string): Promise<MessageData | null> {
356
+ const result = await this.client.query<MessageData>(
357
+ `SELECT * FROM ${this.tableName} WHERE id = $1`,
358
+ [id]
359
+ );
360
+
361
+ return result.rows[0] || null;
362
+ }
363
+
364
+ async findBySessionId(
365
+ sessionId: string,
366
+ limit = 1000
367
+ ): Promise<MessageData[]> {
368
+ const result = await this.client.query<MessageData>(
369
+ `SELECT * FROM ${this.tableName}
370
+ WHERE session_id = $1
371
+ ORDER BY created_at ASC
372
+ LIMIT $2`,
373
+ [sessionId, limit]
374
+ );
375
+
376
+ return result.rows;
377
+ }
378
+
379
+ async findByUserId(userId: string, limit = 100): Promise<MessageData[]> {
380
+ const result = await this.client.query<MessageData>(
381
+ `SELECT * FROM ${this.tableName}
382
+ WHERE user_id = $1
383
+ ORDER BY created_at DESC
384
+ LIMIT $2`,
385
+ [userId, limit]
386
+ );
387
+
388
+ return result.rows;
389
+ }
390
+
391
+ async delete(id: string): Promise<boolean> {
392
+ const result = await this.client.query(
393
+ `DELETE FROM ${this.tableName} WHERE id = $1`,
394
+ [id]
395
+ );
396
+
397
+ return result.rowCount > 0;
398
+ }
399
+
400
+ async deleteBySessionId(sessionId: string): Promise<number> {
401
+ const result = await this.client.query(
402
+ `DELETE FROM ${this.tableName} WHERE session_id = $1`,
403
+ [sessionId]
404
+ );
405
+
406
+ return result.rowCount;
407
+ }
408
+
409
+ async deleteByUserId(userId: string): Promise<number> {
410
+ const result = await this.client.query(
411
+ `DELETE FROM ${this.tableName} WHERE user_id = $1`,
412
+ [userId]
413
+ );
414
+
415
+ return result.rowCount;
416
+ }
417
+ }