@agentxjs/node-platform 2.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/README.md +134 -0
- package/dist/WebSocketConnection-BUL85bFC.d.ts +66 -0
- package/dist/WebSocketFactory-SDWPRZVB.js +8 -0
- package/dist/WebSocketFactory-SDWPRZVB.js.map +1 -0
- package/dist/chunk-BBZV6B5R.js +264 -0
- package/dist/chunk-BBZV6B5R.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-PK2K7CCJ.js +213 -0
- package/dist/chunk-PK2K7CCJ.js.map +1 -0
- package/dist/chunk-TXESAX3X.js +361 -0
- package/dist/chunk-TXESAX3X.js.map +1 -0
- package/dist/chunk-V664KD3R.js +14 -0
- package/dist/chunk-V664KD3R.js.map +1 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +223 -0
- package/dist/index.js.map +1 -0
- package/dist/mq/index.d.ts +63 -0
- package/dist/mq/index.js +10 -0
- package/dist/mq/index.js.map +1 -0
- package/dist/network/index.d.ts +17 -0
- package/dist/network/index.js +14 -0
- package/dist/network/index.js.map +1 -0
- package/dist/persistence/index.d.ts +175 -0
- package/dist/persistence/index.js +18 -0
- package/dist/persistence/index.js.map +1 -0
- package/package.json +50 -0
- package/src/bash/NodeBashProvider.ts +54 -0
- package/src/index.ts +151 -0
- package/src/logger/FileLoggerFactory.ts +175 -0
- package/src/logger/index.ts +5 -0
- package/src/mq/OffsetGenerator.ts +48 -0
- package/src/mq/SqliteMessageQueue.ts +240 -0
- package/src/mq/index.ts +30 -0
- package/src/network/WebSocketConnection.ts +206 -0
- package/src/network/WebSocketFactory.ts +17 -0
- package/src/network/WebSocketServer.ts +156 -0
- package/src/network/index.ts +32 -0
- package/src/persistence/Persistence.ts +53 -0
- package/src/persistence/StorageContainerRepository.ts +58 -0
- package/src/persistence/StorageImageRepository.ts +153 -0
- package/src/persistence/StorageSessionRepository.ts +171 -0
- package/src/persistence/index.ts +38 -0
- package/src/persistence/memory.ts +27 -0
- package/src/persistence/sqlite.ts +111 -0
- package/src/persistence/types.ts +32 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence - Core persistence implementation
|
|
3
|
+
*
|
|
4
|
+
* Creates a Persistence instance from a driver.
|
|
5
|
+
* Each driver provides a createStorage() method that returns an unstorage Storage instance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Storage } from "unstorage";
|
|
9
|
+
import { createLogger } from "commonxjs/logger";
|
|
10
|
+
import type { Persistence, PersistenceDriver } from "./types";
|
|
11
|
+
import { StorageContainerRepository } from "./StorageContainerRepository";
|
|
12
|
+
import { StorageImageRepository } from "./StorageImageRepository";
|
|
13
|
+
import { StorageSessionRepository } from "./StorageSessionRepository";
|
|
14
|
+
|
|
15
|
+
const logger = createLogger("node-platform/Persistence");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* PersistenceImpl - Internal implementation
|
|
19
|
+
*/
|
|
20
|
+
class PersistenceImpl implements Persistence {
|
|
21
|
+
readonly containers: StorageContainerRepository;
|
|
22
|
+
readonly images: StorageImageRepository;
|
|
23
|
+
readonly sessions: StorageSessionRepository;
|
|
24
|
+
|
|
25
|
+
constructor(storage: Storage) {
|
|
26
|
+
this.containers = new StorageContainerRepository(storage);
|
|
27
|
+
this.images = new StorageImageRepository(storage);
|
|
28
|
+
this.sessions = new StorageSessionRepository(storage);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a Persistence instance from a driver
|
|
34
|
+
*
|
|
35
|
+
* @param driver - The persistence driver to use
|
|
36
|
+
* @returns Promise<Persistence> instance
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { createPersistence, memoryDriver } from "@agentxjs/node-platform/persistence";
|
|
41
|
+
*
|
|
42
|
+
* const persistence = await createPersistence(memoryDriver());
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export async function createPersistence(driver: PersistenceDriver): Promise<Persistence> {
|
|
46
|
+
logger.debug("Creating persistence");
|
|
47
|
+
|
|
48
|
+
const storage = await driver.createStorage();
|
|
49
|
+
const persistence = new PersistenceImpl(storage);
|
|
50
|
+
|
|
51
|
+
logger.info("Persistence created successfully");
|
|
52
|
+
return persistence;
|
|
53
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StorageContainerRepository - unstorage-based ContainerRepository
|
|
3
|
+
*
|
|
4
|
+
* Uses unstorage for backend-agnostic storage (Memory, Redis, SQLite, etc.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Storage } from "unstorage";
|
|
8
|
+
import type { ContainerRepository, ContainerRecord } from "@agentxjs/core/persistence";
|
|
9
|
+
import { createLogger } from "commonxjs/logger";
|
|
10
|
+
|
|
11
|
+
const logger = createLogger("node-platform/ContainerRepository");
|
|
12
|
+
|
|
13
|
+
/** Key prefix for containers */
|
|
14
|
+
const PREFIX = "containers";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* StorageContainerRepository - unstorage implementation
|
|
18
|
+
*/
|
|
19
|
+
export class StorageContainerRepository implements ContainerRepository {
|
|
20
|
+
constructor(private readonly storage: Storage) {}
|
|
21
|
+
|
|
22
|
+
private key(containerId: string): string {
|
|
23
|
+
return `${PREFIX}:${containerId}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async saveContainer(record: ContainerRecord): Promise<void> {
|
|
27
|
+
await this.storage.setItem(this.key(record.containerId), record);
|
|
28
|
+
logger.debug("Container saved", { containerId: record.containerId });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async findContainerById(containerId: string): Promise<ContainerRecord | null> {
|
|
32
|
+
const record = await this.storage.getItem<ContainerRecord>(this.key(containerId));
|
|
33
|
+
return record ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async findAllContainers(): Promise<ContainerRecord[]> {
|
|
37
|
+
const keys = await this.storage.getKeys(PREFIX);
|
|
38
|
+
const records: ContainerRecord[] = [];
|
|
39
|
+
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
const record = await this.storage.getItem<ContainerRecord>(key);
|
|
42
|
+
if (record) {
|
|
43
|
+
records.push(record);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async deleteContainer(containerId: string): Promise<void> {
|
|
51
|
+
await this.storage.removeItem(this.key(containerId));
|
|
52
|
+
logger.debug("Container deleted", { containerId });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async containerExists(containerId: string): Promise<boolean> {
|
|
56
|
+
return await this.storage.hasItem(this.key(containerId));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StorageImageRepository - unstorage-based ImageRepository
|
|
3
|
+
*
|
|
4
|
+
* Uses unstorage for backend-agnostic storage (Memory, Redis, SQLite, etc.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Storage } from "unstorage";
|
|
8
|
+
import type { ImageRepository, ImageRecord, ImageMetadata } from "@agentxjs/core/persistence";
|
|
9
|
+
import { createLogger } from "commonxjs/logger";
|
|
10
|
+
|
|
11
|
+
const logger = createLogger("node-platform/ImageRepository");
|
|
12
|
+
|
|
13
|
+
/** Key prefix for images */
|
|
14
|
+
const PREFIX = "images";
|
|
15
|
+
|
|
16
|
+
/** Index prefix for name lookup */
|
|
17
|
+
const INDEX_BY_NAME = "idx:images:name";
|
|
18
|
+
|
|
19
|
+
/** Index prefix for container lookup */
|
|
20
|
+
const INDEX_BY_CONTAINER = "idx:images:container";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* StorageImageRepository - unstorage implementation
|
|
24
|
+
*/
|
|
25
|
+
export class StorageImageRepository implements ImageRepository {
|
|
26
|
+
constructor(private readonly storage: Storage) {}
|
|
27
|
+
|
|
28
|
+
private key(imageId: string): string {
|
|
29
|
+
return `${PREFIX}:${imageId}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private nameIndexKey(name: string, imageId: string): string {
|
|
33
|
+
return `${INDEX_BY_NAME}:${name}:${imageId}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private containerIndexKey(containerId: string, imageId: string): string {
|
|
37
|
+
return `${INDEX_BY_CONTAINER}:${containerId}:${imageId}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async saveImage(record: ImageRecord): Promise<void> {
|
|
41
|
+
// Save main record
|
|
42
|
+
await this.storage.setItem(this.key(record.imageId), record);
|
|
43
|
+
|
|
44
|
+
// Save index for name lookup
|
|
45
|
+
await this.storage.setItem(this.nameIndexKey(record.name, record.imageId), record.imageId);
|
|
46
|
+
|
|
47
|
+
// Save index for container lookup
|
|
48
|
+
await this.storage.setItem(
|
|
49
|
+
this.containerIndexKey(record.containerId, record.imageId),
|
|
50
|
+
record.imageId
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
logger.debug("Image saved", { imageId: record.imageId });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async findImageById(imageId: string): Promise<ImageRecord | null> {
|
|
57
|
+
const record = await this.storage.getItem<ImageRecord>(this.key(imageId));
|
|
58
|
+
return record ?? null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async findAllImages(): Promise<ImageRecord[]> {
|
|
62
|
+
const keys = await this.storage.getKeys(PREFIX);
|
|
63
|
+
const records: ImageRecord[] = [];
|
|
64
|
+
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
// Skip index keys
|
|
67
|
+
if (key.startsWith("idx:")) continue;
|
|
68
|
+
|
|
69
|
+
const record = await this.storage.getItem<ImageRecord>(key);
|
|
70
|
+
if (record) {
|
|
71
|
+
records.push(record);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async findImagesByName(name: string): Promise<ImageRecord[]> {
|
|
79
|
+
const indexPrefix = `${INDEX_BY_NAME}:${name}`;
|
|
80
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
81
|
+
const records: ImageRecord[] = [];
|
|
82
|
+
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
const imageId = await this.storage.getItem<string>(key);
|
|
85
|
+
if (imageId) {
|
|
86
|
+
const record = await this.findImageById(imageId);
|
|
87
|
+
if (record) {
|
|
88
|
+
records.push(record);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async findImagesByContainerId(containerId: string): Promise<ImageRecord[]> {
|
|
97
|
+
const indexPrefix = `${INDEX_BY_CONTAINER}:${containerId}`;
|
|
98
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
99
|
+
const records: ImageRecord[] = [];
|
|
100
|
+
|
|
101
|
+
for (const key of keys) {
|
|
102
|
+
const imageId = await this.storage.getItem<string>(key);
|
|
103
|
+
if (imageId) {
|
|
104
|
+
const record = await this.findImageById(imageId);
|
|
105
|
+
if (record) {
|
|
106
|
+
records.push(record);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async deleteImage(imageId: string): Promise<void> {
|
|
115
|
+
// Get record to find name and containerId for index cleanup
|
|
116
|
+
const record = await this.findImageById(imageId);
|
|
117
|
+
|
|
118
|
+
// Delete main record
|
|
119
|
+
await this.storage.removeItem(this.key(imageId));
|
|
120
|
+
|
|
121
|
+
// Delete indexes
|
|
122
|
+
if (record) {
|
|
123
|
+
await this.storage.removeItem(this.nameIndexKey(record.name, imageId));
|
|
124
|
+
await this.storage.removeItem(this.containerIndexKey(record.containerId, imageId));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
logger.debug("Image deleted", { imageId });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async imageExists(imageId: string): Promise<boolean> {
|
|
131
|
+
return await this.storage.hasItem(this.key(imageId));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async updateMetadata(imageId: string, metadata: Partial<ImageMetadata>): Promise<void> {
|
|
135
|
+
const record = await this.findImageById(imageId);
|
|
136
|
+
if (!record) {
|
|
137
|
+
throw new Error(`Image not found: ${imageId}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Merge metadata
|
|
141
|
+
const updatedRecord: ImageRecord = {
|
|
142
|
+
...record,
|
|
143
|
+
metadata: {
|
|
144
|
+
...record.metadata,
|
|
145
|
+
...metadata,
|
|
146
|
+
},
|
|
147
|
+
updatedAt: Date.now(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
await this.storage.setItem(this.key(imageId), updatedRecord);
|
|
151
|
+
logger.debug("Image metadata updated", { imageId, metadata });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StorageSessionRepository - unstorage-based SessionRepository
|
|
3
|
+
*
|
|
4
|
+
* Uses unstorage for backend-agnostic storage (Memory, Redis, SQLite, etc.)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Storage } from "unstorage";
|
|
8
|
+
import type { SessionRepository, SessionRecord } from "@agentxjs/core/persistence";
|
|
9
|
+
import type { Message } from "@agentxjs/core/agent";
|
|
10
|
+
import { createLogger } from "commonxjs/logger";
|
|
11
|
+
|
|
12
|
+
const logger = createLogger("node-platform/SessionRepository");
|
|
13
|
+
|
|
14
|
+
/** Key prefix for sessions */
|
|
15
|
+
const PREFIX = "sessions";
|
|
16
|
+
|
|
17
|
+
/** Key prefix for messages */
|
|
18
|
+
const MESSAGES_PREFIX = "messages";
|
|
19
|
+
|
|
20
|
+
/** Index prefix for image lookup */
|
|
21
|
+
const INDEX_BY_IMAGE = "idx:sessions:image";
|
|
22
|
+
|
|
23
|
+
/** Index prefix for container lookup */
|
|
24
|
+
const INDEX_BY_CONTAINER = "idx:sessions:container";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* StorageSessionRepository - unstorage implementation
|
|
28
|
+
*/
|
|
29
|
+
export class StorageSessionRepository implements SessionRepository {
|
|
30
|
+
constructor(private readonly storage: Storage) {}
|
|
31
|
+
|
|
32
|
+
private key(sessionId: string): string {
|
|
33
|
+
return `${PREFIX}:${sessionId}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private messagesKey(sessionId: string): string {
|
|
37
|
+
return `${MESSAGES_PREFIX}:${sessionId}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private imageIndexKey(imageId: string, sessionId: string): string {
|
|
41
|
+
return `${INDEX_BY_IMAGE}:${imageId}:${sessionId}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private containerIndexKey(containerId: string, sessionId: string): string {
|
|
45
|
+
return `${INDEX_BY_CONTAINER}:${containerId}:${sessionId}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async saveSession(record: SessionRecord): Promise<void> {
|
|
49
|
+
// Save main record
|
|
50
|
+
await this.storage.setItem(this.key(record.sessionId), record);
|
|
51
|
+
|
|
52
|
+
// Save index for image lookup
|
|
53
|
+
await this.storage.setItem(
|
|
54
|
+
this.imageIndexKey(record.imageId, record.sessionId),
|
|
55
|
+
record.sessionId
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Save index for container lookup
|
|
59
|
+
await this.storage.setItem(
|
|
60
|
+
this.containerIndexKey(record.containerId, record.sessionId),
|
|
61
|
+
record.sessionId
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
logger.debug("Session saved", { sessionId: record.sessionId });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async findSessionById(sessionId: string): Promise<SessionRecord | null> {
|
|
68
|
+
const record = await this.storage.getItem<SessionRecord>(this.key(sessionId));
|
|
69
|
+
return record ?? null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async findSessionByImageId(imageId: string): Promise<SessionRecord | null> {
|
|
73
|
+
const indexPrefix = `${INDEX_BY_IMAGE}:${imageId}`;
|
|
74
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
75
|
+
|
|
76
|
+
if (keys.length === 0) return null;
|
|
77
|
+
|
|
78
|
+
// Return the first (most recent) session for this image
|
|
79
|
+
const sessionId = await this.storage.getItem<string>(keys[0]);
|
|
80
|
+
if (!sessionId) return null;
|
|
81
|
+
|
|
82
|
+
return this.findSessionById(sessionId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async findSessionsByContainerId(containerId: string): Promise<SessionRecord[]> {
|
|
86
|
+
const indexPrefix = `${INDEX_BY_CONTAINER}:${containerId}`;
|
|
87
|
+
const keys = await this.storage.getKeys(indexPrefix);
|
|
88
|
+
const records: SessionRecord[] = [];
|
|
89
|
+
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
const sessionId = await this.storage.getItem<string>(key);
|
|
92
|
+
if (sessionId) {
|
|
93
|
+
const record = await this.findSessionById(sessionId);
|
|
94
|
+
if (record) {
|
|
95
|
+
records.push(record);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async findAllSessions(): Promise<SessionRecord[]> {
|
|
104
|
+
const keys = await this.storage.getKeys(PREFIX);
|
|
105
|
+
const records: SessionRecord[] = [];
|
|
106
|
+
|
|
107
|
+
for (const key of keys) {
|
|
108
|
+
// Skip index keys
|
|
109
|
+
if (key.startsWith("idx:")) continue;
|
|
110
|
+
|
|
111
|
+
const record = await this.storage.getItem<SessionRecord>(key);
|
|
112
|
+
if (record) {
|
|
113
|
+
records.push(record);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return records.sort((a, b) => b.createdAt - a.createdAt);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async deleteSession(sessionId: string): Promise<void> {
|
|
121
|
+
// Get record for index cleanup
|
|
122
|
+
const record = await this.findSessionById(sessionId);
|
|
123
|
+
|
|
124
|
+
// Delete main record
|
|
125
|
+
await this.storage.removeItem(this.key(sessionId));
|
|
126
|
+
|
|
127
|
+
// Delete messages
|
|
128
|
+
await this.storage.removeItem(this.messagesKey(sessionId));
|
|
129
|
+
|
|
130
|
+
// Delete indexes
|
|
131
|
+
if (record) {
|
|
132
|
+
await this.storage.removeItem(this.imageIndexKey(record.imageId, sessionId));
|
|
133
|
+
await this.storage.removeItem(this.containerIndexKey(record.containerId, sessionId));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
logger.debug("Session deleted", { sessionId });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async sessionExists(sessionId: string): Promise<boolean> {
|
|
140
|
+
return await this.storage.hasItem(this.key(sessionId));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ==================== Message Operations ====================
|
|
144
|
+
|
|
145
|
+
async addMessage(sessionId: string, message: Message): Promise<void> {
|
|
146
|
+
const messages = await this.getMessages(sessionId);
|
|
147
|
+
messages.push(message);
|
|
148
|
+
await this.storage.setItem(this.messagesKey(sessionId), messages);
|
|
149
|
+
logger.debug("Message added to session", { sessionId, subtype: message.subtype });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getMessages(sessionId: string): Promise<Message[]> {
|
|
153
|
+
const messages = await this.storage.getItem<Message[]>(this.messagesKey(sessionId));
|
|
154
|
+
// Ensure we always return an array (handle corrupted data)
|
|
155
|
+
if (!messages || !Array.isArray(messages)) {
|
|
156
|
+
if (messages) {
|
|
157
|
+
logger.warn("Messages data is not an array, resetting", {
|
|
158
|
+
sessionId,
|
|
159
|
+
type: typeof messages,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
return messages;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async clearMessages(sessionId: string): Promise<void> {
|
|
168
|
+
await this.storage.removeItem(this.messagesKey(sessionId));
|
|
169
|
+
logger.debug("Messages cleared for session", { sessionId });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence Module
|
|
3
|
+
*
|
|
4
|
+
* Provides storage implementations for Node.js.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { createPersistence, sqliteDriver, memoryDriver } from "@agentxjs/node-platform/persistence";
|
|
9
|
+
*
|
|
10
|
+
* // SQLite (persistent)
|
|
11
|
+
* const persistence = await createPersistence(
|
|
12
|
+
* sqliteDriver({ path: "./data/agentx.db" })
|
|
13
|
+
* );
|
|
14
|
+
*
|
|
15
|
+
* // Memory (testing)
|
|
16
|
+
* const testPersistence = await createPersistence(memoryDriver());
|
|
17
|
+
*
|
|
18
|
+
* // Use repositories
|
|
19
|
+
* await persistence.containers.saveContainer(record);
|
|
20
|
+
* await persistence.images.saveImage(imageRecord);
|
|
21
|
+
* await persistence.sessions.addMessage(sessionId, message);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// Types
|
|
26
|
+
export type { PersistenceDriver, Persistence } from "./types";
|
|
27
|
+
|
|
28
|
+
// Factory
|
|
29
|
+
export { createPersistence } from "./Persistence";
|
|
30
|
+
|
|
31
|
+
// Drivers
|
|
32
|
+
export { sqliteDriver, type SqliteDriverOptions } from "./sqlite";
|
|
33
|
+
export { memoryDriver } from "./memory";
|
|
34
|
+
|
|
35
|
+
// Repositories (for advanced use cases)
|
|
36
|
+
export { StorageContainerRepository } from "./StorageContainerRepository";
|
|
37
|
+
export { StorageImageRepository } from "./StorageImageRepository";
|
|
38
|
+
export { StorageSessionRepository } from "./StorageSessionRepository";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Driver - In-memory storage
|
|
3
|
+
*
|
|
4
|
+
* Useful for testing and development.
|
|
5
|
+
* Data is lost when the process exits.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createPersistence, memoryDriver } from "@agentxjs/node-platform/persistence";
|
|
10
|
+
*
|
|
11
|
+
* const persistence = await createPersistence(memoryDriver());
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createStorage, type Storage } from "unstorage";
|
|
16
|
+
import type { PersistenceDriver } from "./types";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a memory driver
|
|
20
|
+
*/
|
|
21
|
+
export function memoryDriver(): PersistenceDriver {
|
|
22
|
+
return {
|
|
23
|
+
async createStorage(): Promise<Storage> {
|
|
24
|
+
return createStorage();
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite Driver - SQLite database storage
|
|
3
|
+
*
|
|
4
|
+
* Uses commonxjs SQLite abstraction with automatic runtime detection:
|
|
5
|
+
* - Bun: uses bun:sqlite (built-in)
|
|
6
|
+
* - Node.js 22+: uses node:sqlite (built-in)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { createPersistence, sqliteDriver } from "@agentxjs/node-platform/persistence";
|
|
11
|
+
*
|
|
12
|
+
* const persistence = await createPersistence(
|
|
13
|
+
* sqliteDriver({ path: "./data/agentx.db" })
|
|
14
|
+
* );
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createStorage, type Storage, type Driver } from "unstorage";
|
|
19
|
+
import { openDatabase, type Database } from "commonxjs/sqlite";
|
|
20
|
+
import type { PersistenceDriver } from "./types";
|
|
21
|
+
|
|
22
|
+
export interface SqliteDriverOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Path to SQLite database file
|
|
25
|
+
* @example "./data/agentx.db"
|
|
26
|
+
*/
|
|
27
|
+
path: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a custom unstorage driver using our SQLite abstraction
|
|
32
|
+
*/
|
|
33
|
+
function createSqliteUnstorageDriver(db: Database): Driver {
|
|
34
|
+
// Initialize schema
|
|
35
|
+
db.exec(`
|
|
36
|
+
CREATE TABLE IF NOT EXISTS kv_storage (
|
|
37
|
+
key TEXT PRIMARY KEY,
|
|
38
|
+
value TEXT NOT NULL,
|
|
39
|
+
created_at INTEGER NOT NULL,
|
|
40
|
+
updated_at INTEGER NOT NULL
|
|
41
|
+
);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_kv_key ON kv_storage(key);
|
|
43
|
+
`);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name: "agentx-sqlite",
|
|
47
|
+
|
|
48
|
+
hasItem(key: string): boolean {
|
|
49
|
+
const row = db.prepare("SELECT 1 FROM kv_storage WHERE key = ?").get(key);
|
|
50
|
+
// Note: row can be null (not found) or object (found)
|
|
51
|
+
// Must use != null to handle both null and undefined
|
|
52
|
+
return row != null;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
getItem(key: string): string | null {
|
|
56
|
+
const row = db.prepare("SELECT value FROM kv_storage WHERE key = ?").get(key) as
|
|
57
|
+
| { value: string }
|
|
58
|
+
| undefined;
|
|
59
|
+
return row?.value ?? null;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
setItem(key: string, value: string): void {
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const existing = db.prepare("SELECT 1 FROM kv_storage WHERE key = ?").get(key);
|
|
65
|
+
if (existing) {
|
|
66
|
+
db.prepare("UPDATE kv_storage SET value = ?, updated_at = ? WHERE key = ?").run(
|
|
67
|
+
value,
|
|
68
|
+
now,
|
|
69
|
+
key
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
db.prepare(
|
|
73
|
+
"INSERT INTO kv_storage (key, value, created_at, updated_at) VALUES (?, ?, ?, ?)"
|
|
74
|
+
).run(key, value, now, now);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
removeItem(key: string): void {
|
|
79
|
+
db.prepare("DELETE FROM kv_storage WHERE key = ?").run(key);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
getKeys(): string[] {
|
|
83
|
+
const rows = db.prepare("SELECT key FROM kv_storage").all() as { key: string }[];
|
|
84
|
+
return rows.map((r) => r.key);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
clear(): void {
|
|
88
|
+
db.exec("DELETE FROM kv_storage");
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
dispose(): void {
|
|
92
|
+
db.close();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create a SQLite driver
|
|
99
|
+
*
|
|
100
|
+
* @param options - Driver options
|
|
101
|
+
*/
|
|
102
|
+
export function sqliteDriver(options: SqliteDriverOptions): PersistenceDriver {
|
|
103
|
+
return {
|
|
104
|
+
async createStorage(): Promise<Storage> {
|
|
105
|
+
const db = openDatabase(options.path);
|
|
106
|
+
const driver = createSqliteUnstorageDriver(db);
|
|
107
|
+
|
|
108
|
+
return createStorage({ driver });
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence Types for Node Provider
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Storage } from "unstorage";
|
|
6
|
+
import type {
|
|
7
|
+
ContainerRepository,
|
|
8
|
+
ImageRepository,
|
|
9
|
+
SessionRepository,
|
|
10
|
+
} from "@agentxjs/core/persistence";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Persistence driver interface
|
|
14
|
+
*
|
|
15
|
+
* Each driver must implement this interface.
|
|
16
|
+
* The createStorage() method is called once during initialization.
|
|
17
|
+
*/
|
|
18
|
+
export interface PersistenceDriver {
|
|
19
|
+
/**
|
|
20
|
+
* Create the underlying storage instance
|
|
21
|
+
*/
|
|
22
|
+
createStorage(): Promise<Storage>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Persistence - Aggregated repositories
|
|
27
|
+
*/
|
|
28
|
+
export interface Persistence {
|
|
29
|
+
readonly containers: ContainerRepository;
|
|
30
|
+
readonly images: ImageRepository;
|
|
31
|
+
readonly sessions: SessionRepository;
|
|
32
|
+
}
|