@aigne/afs-history 1.2.0-beta.5 → 1.2.0-beta.6
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/CHANGELOG.md +7 -0
- package/lib/cjs/index.d.ts +2 -2
- package/lib/cjs/index.js +164 -142
- package/lib/cjs/storage/index.d.ts +4 -6
- package/lib/cjs/storage/index.js +49 -52
- package/lib/cjs/storage/migrate.js +9 -1
- package/lib/cjs/storage/migrations/004-add-memory-table.d.ts +2 -0
- package/lib/cjs/storage/migrations/004-add-memory-table.js +23 -0
- package/lib/cjs/storage/migrations/005-add-indexes.d.ts +2 -0
- package/lib/cjs/storage/migrations/005-add-indexes.js +31 -0
- package/lib/cjs/storage/models/compact.d.ts +1 -1
- package/lib/cjs/storage/models/memory.d.ts +182 -0
- package/lib/cjs/storage/models/memory.js +27 -0
- package/lib/cjs/storage/type.d.ts +20 -8
- package/lib/dts/index.d.ts +2 -2
- package/lib/dts/storage/index.d.ts +4 -6
- package/lib/dts/storage/migrations/004-add-memory-table.d.ts +2 -0
- package/lib/dts/storage/migrations/005-add-indexes.d.ts +2 -0
- package/lib/dts/storage/models/compact.d.ts +1 -1
- package/lib/dts/storage/models/memory.d.ts +182 -0
- package/lib/dts/storage/type.d.ts +20 -8
- package/lib/esm/index.d.ts +2 -2
- package/lib/esm/index.js +164 -142
- package/lib/esm/storage/index.d.ts +4 -6
- package/lib/esm/storage/index.js +49 -52
- package/lib/esm/storage/migrate.js +9 -1
- package/lib/esm/storage/migrations/004-add-memory-table.d.ts +2 -0
- package/lib/esm/storage/migrations/004-add-memory-table.js +20 -0
- package/lib/esm/storage/migrations/005-add-indexes.d.ts +2 -0
- package/lib/esm/storage/migrations/005-add-indexes.js +28 -0
- package/lib/esm/storage/models/compact.d.ts +1 -1
- package/lib/esm/storage/models/memory.d.ts +182 -0
- package/lib/esm/storage/models/memory.js +22 -0
- package/lib/esm/storage/type.d.ts +20 -8
- package/package.json +1 -1
package/lib/esm/index.js
CHANGED
|
@@ -15,36 +15,80 @@ export class AFSHistory {
|
|
|
15
15
|
afs;
|
|
16
16
|
router = createRouter({
|
|
17
17
|
routes: {
|
|
18
|
-
"/
|
|
19
|
-
"/by-session": { type: "
|
|
20
|
-
"/by-session/:sessionId": { type: "
|
|
21
|
-
"/by-session/:sessionId/@metadata/compact": {
|
|
22
|
-
|
|
18
|
+
"/by-session": { action: "list", type: "history", scope: "session" },
|
|
19
|
+
"/by-session/:sessionId": { action: "list", type: "history", scope: "session" },
|
|
20
|
+
"/by-session/:sessionId/new": { action: "create", type: "history", scope: "session" },
|
|
21
|
+
"/by-session/:sessionId/@metadata/compact": {
|
|
22
|
+
action: "list",
|
|
23
|
+
type: "compact",
|
|
24
|
+
scope: "session",
|
|
25
|
+
},
|
|
26
|
+
"/by-session/:sessionId/@metadata/compact/new": {
|
|
27
|
+
action: "create",
|
|
28
|
+
type: "compact",
|
|
29
|
+
scope: "session",
|
|
30
|
+
},
|
|
23
31
|
"/by-session/:sessionId/@metadata/compact/:compactId": {
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
action: "read",
|
|
33
|
+
type: "compact",
|
|
34
|
+
scope: "session",
|
|
35
|
+
},
|
|
36
|
+
"/by-session/:sessionId/@metadata/memory": {
|
|
37
|
+
action: "list",
|
|
38
|
+
type: "memory",
|
|
39
|
+
scope: "session",
|
|
40
|
+
},
|
|
41
|
+
"/by-session/:sessionId/@metadata/memory/new": {
|
|
42
|
+
action: "create",
|
|
43
|
+
type: "memory",
|
|
44
|
+
scope: "session",
|
|
45
|
+
},
|
|
46
|
+
"/by-session/:sessionId/@metadata/memory/:memoryId": {
|
|
47
|
+
action: "read",
|
|
48
|
+
type: "memory",
|
|
49
|
+
scope: "session",
|
|
50
|
+
},
|
|
51
|
+
"/by-session/:sessionId/:entryId": { action: "read", type: "history", scope: "session" },
|
|
52
|
+
"/by-user": { action: "list", type: "history", scope: "user" },
|
|
53
|
+
"/by-user/:userId": { action: "list", type: "history", scope: "user" },
|
|
54
|
+
"/by-user/:userId/new": { action: "create", type: "history", scope: "user" },
|
|
55
|
+
"/by-user/:userId/@metadata/compact": { action: "list", type: "compact", scope: "user" },
|
|
56
|
+
"/by-user/:userId/@metadata/compact/new": {
|
|
57
|
+
action: "create",
|
|
58
|
+
type: "compact",
|
|
59
|
+
scope: "user",
|
|
60
|
+
},
|
|
61
|
+
"/by-user/:userId/@metadata/compact/:compactId": {
|
|
62
|
+
action: "read",
|
|
63
|
+
type: "compact",
|
|
64
|
+
scope: "user",
|
|
26
65
|
},
|
|
27
|
-
"/by-
|
|
28
|
-
"/by-user": { type: "
|
|
29
|
-
"/by-user/:userId": {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"/by-
|
|
35
|
-
"/by-agent
|
|
36
|
-
"/by-agent/:agentId
|
|
37
|
-
"/by-agent/:agentId
|
|
38
|
-
"/by-agent/:agentId/@metadata/compact
|
|
39
|
-
"/by-agent/:agentId
|
|
66
|
+
"/by-user/:userId/@metadata/memory": { action: "list", type: "memory", scope: "user" },
|
|
67
|
+
"/by-user/:userId/@metadata/memory/new": { action: "create", type: "memory", scope: "user" },
|
|
68
|
+
"/by-user/:userId/@metadata/memory/:memoryId": {
|
|
69
|
+
action: "read",
|
|
70
|
+
type: "memory",
|
|
71
|
+
scope: "user",
|
|
72
|
+
},
|
|
73
|
+
"/by-user/:userId/:entryId": { action: "read", type: "history", scope: "user" },
|
|
74
|
+
"/by-agent": { action: "list", type: "history", scope: "agent" },
|
|
75
|
+
"/by-agent/:agentId": { action: "list", type: "history", scope: "agent" },
|
|
76
|
+
"/by-agent/:agentId/new": { action: "create", type: "history", scope: "agent" },
|
|
77
|
+
"/by-agent/:agentId/@metadata/compact": { action: "list", type: "compact", scope: "agent" },
|
|
78
|
+
"/by-agent/:agentId/@metadata/compact/new": {
|
|
79
|
+
action: "create",
|
|
80
|
+
type: "compact",
|
|
81
|
+
scope: "agent",
|
|
82
|
+
},
|
|
83
|
+
"/by-agent/:agentId/@metadata/compact/:compactId": {
|
|
84
|
+
action: "read",
|
|
85
|
+
type: "compact",
|
|
86
|
+
scope: "agent",
|
|
87
|
+
},
|
|
88
|
+
"/by-agent/:agentId/:entryId": { action: "read", type: "history", scope: "agent" },
|
|
40
89
|
},
|
|
41
90
|
});
|
|
42
91
|
rootEntries = [
|
|
43
|
-
{
|
|
44
|
-
id: "new-history",
|
|
45
|
-
path: "/new",
|
|
46
|
-
description: "Write to this path to create a new history entry, generating a UUID-based path.",
|
|
47
|
-
},
|
|
48
92
|
{
|
|
49
93
|
id: "by-session",
|
|
50
94
|
path: "/by-session",
|
|
@@ -67,148 +111,126 @@ export class AFSHistory {
|
|
|
67
111
|
async list(path, options) {
|
|
68
112
|
if (path === "/")
|
|
69
113
|
return { data: this.rootEntries };
|
|
70
|
-
// Parse virtual path and extract filter conditions
|
|
71
114
|
const match = this.router.lookup(path);
|
|
72
|
-
|
|
73
|
-
if (!match) {
|
|
115
|
+
if (!match || match.action !== "list") {
|
|
74
116
|
return { data: [] };
|
|
75
117
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Add virtual path prefix to each entry's path
|
|
95
|
-
return {
|
|
96
|
-
...result,
|
|
97
|
-
data: result.data.map((entry) => ({
|
|
98
|
-
...entry,
|
|
99
|
-
path: this.normalizePath(entry, matchId),
|
|
100
|
-
})),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
if (match.type === "compact-list" &&
|
|
104
|
-
(matchId === "by-session" || matchId === "by-user" || matchId === "by-agent")) {
|
|
105
|
-
const compactType = this.getCompactType(matchId);
|
|
106
|
-
const mergedFilter = {
|
|
107
|
-
...options?.filter,
|
|
108
|
-
...match.params,
|
|
109
|
-
};
|
|
110
|
-
const result = await this.storage.listCompact(compactType, {
|
|
111
|
-
...options,
|
|
112
|
-
filter: mergedFilter,
|
|
113
|
-
});
|
|
114
|
-
return { data: result.data };
|
|
115
|
-
}
|
|
116
|
-
return { data: [] };
|
|
118
|
+
const { type, scope } = match;
|
|
119
|
+
const mergedFilter = {
|
|
120
|
+
...options?.filter,
|
|
121
|
+
...match.params,
|
|
122
|
+
};
|
|
123
|
+
const result = await this.storage.list({
|
|
124
|
+
...options,
|
|
125
|
+
type,
|
|
126
|
+
scope,
|
|
127
|
+
filter: mergedFilter,
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
...result,
|
|
131
|
+
data: result.data.map((entry) => ({
|
|
132
|
+
...entry,
|
|
133
|
+
path: this.normalizePath(entry, scope, type),
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
117
136
|
}
|
|
118
137
|
async read(path, options) {
|
|
119
|
-
// Parse virtual path and extract filter conditions
|
|
120
138
|
const match = this.router.lookup(path);
|
|
121
|
-
if (!match)
|
|
139
|
+
if (!match || match.action !== "read")
|
|
122
140
|
return {};
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
if (match.type === "compact-detail" &&
|
|
145
|
-
(match.id === "by-session" || match.id === "by-user" || match.id === "by-agent")) {
|
|
146
|
-
const compactId = match.params?.compactId;
|
|
147
|
-
if (!compactId)
|
|
148
|
-
throw new Error("Compact ID is required in the path to read compact detail.");
|
|
149
|
-
const compactType = this.getCompactType(match.id);
|
|
150
|
-
const mergedFilter = {
|
|
151
|
-
...options?.filter,
|
|
152
|
-
...match.params,
|
|
153
|
-
};
|
|
154
|
-
const data = await this.storage.readCompact(compactType, compactId, {
|
|
155
|
-
filter: mergedFilter,
|
|
156
|
-
});
|
|
157
|
-
return { data };
|
|
158
|
-
}
|
|
159
|
-
return {};
|
|
141
|
+
const { type, scope } = match;
|
|
142
|
+
const entryId = match.params?.entryId ?? match.params?.compactId ?? match.params?.memoryId;
|
|
143
|
+
if (!entryId)
|
|
144
|
+
throw new Error(`Entry ID is required in the path to read ${type}.`);
|
|
145
|
+
const mergedFilter = {
|
|
146
|
+
...options?.filter,
|
|
147
|
+
...match.params,
|
|
148
|
+
};
|
|
149
|
+
const data = await this.storage.read(entryId, {
|
|
150
|
+
type,
|
|
151
|
+
scope,
|
|
152
|
+
filter: mergedFilter,
|
|
153
|
+
});
|
|
154
|
+
// Add virtual path prefix for entries
|
|
155
|
+
return {
|
|
156
|
+
data: data && {
|
|
157
|
+
...data,
|
|
158
|
+
path: this.normalizePath(data, scope, type),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
160
161
|
}
|
|
161
162
|
async write(path, content) {
|
|
162
163
|
const id = v7();
|
|
163
164
|
const match = this.router.lookup(path);
|
|
164
|
-
if (match
|
|
165
|
-
|
|
166
|
-
const entry = await this.storage.createCompact(compactType, {
|
|
167
|
-
...match.params,
|
|
168
|
-
...content,
|
|
169
|
-
id,
|
|
170
|
-
path: joinURL("/", compactType, id),
|
|
171
|
-
});
|
|
172
|
-
return { data: entry };
|
|
165
|
+
if (!match || match.action !== "create") {
|
|
166
|
+
throw new Error("Can only write to paths with 'new' suffix: /by-{scope}/{scopeId}/new or /by-{scope}/{scopeId}/@metadata/{type}/new");
|
|
173
167
|
}
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
const { type, scope } = match;
|
|
169
|
+
// Validate that scope ID is provided in path params
|
|
170
|
+
const scopeIdField = `${scope}Id`;
|
|
171
|
+
const scopeIdValue = match.params?.[scopeIdField];
|
|
172
|
+
if (!scopeIdValue) {
|
|
173
|
+
throw new Error(`${scopeIdField} is required in the path to create ${type} entry.`);
|
|
176
174
|
}
|
|
177
|
-
if (!content.sessionId)
|
|
178
|
-
throw new Error("sessionId is required to create a history entry.");
|
|
179
175
|
const entry = await this.storage.create({
|
|
176
|
+
...match.params,
|
|
180
177
|
...content,
|
|
181
178
|
id,
|
|
182
|
-
path: joinURL("/", id),
|
|
183
|
-
});
|
|
184
|
-
|
|
179
|
+
path: joinURL("/", type, id),
|
|
180
|
+
}, { type, scope });
|
|
181
|
+
// Emit event for history entries
|
|
182
|
+
if (type === "history") {
|
|
183
|
+
this.afs?.emit("historyCreated", { entry });
|
|
184
|
+
}
|
|
185
185
|
return {
|
|
186
186
|
data: {
|
|
187
187
|
...entry,
|
|
188
|
-
path: this.normalizePath(entry,
|
|
188
|
+
path: this.normalizePath(entry, scope, type),
|
|
189
189
|
},
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
async delete(path, _options) {
|
|
193
|
+
const match = this.router.lookup(path);
|
|
194
|
+
if (!match || match.action !== "read") {
|
|
195
|
+
throw new Error(`Cannot delete: path not found or not a valid entry path`);
|
|
196
|
+
}
|
|
197
|
+
const { type, scope } = match;
|
|
198
|
+
const entryId = match.params?.entryId ?? match.params?.compactId ?? match.params?.memoryId;
|
|
199
|
+
if (!entryId)
|
|
200
|
+
throw new Error(`Entry ID is required in the path to delete ${type}.`);
|
|
201
|
+
const result = await this.storage.delete(entryId, {
|
|
202
|
+
type,
|
|
203
|
+
scope,
|
|
204
|
+
filter: match.params,
|
|
205
|
+
});
|
|
206
|
+
if (result.deletedCount === 0) {
|
|
207
|
+
return {
|
|
208
|
+
message: `No ${type} entry found with id ${entryId}`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
message: `Deleted ${result.deletedCount} ${type} entry`,
|
|
197
213
|
};
|
|
198
|
-
const type = mapping[id];
|
|
199
|
-
if (!type)
|
|
200
|
-
throw new Error(`Invalid compact type for id: ${id}`);
|
|
201
|
-
return type;
|
|
202
214
|
}
|
|
203
|
-
normalizePath(entry,
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
215
|
+
normalizePath(entry, scope, entryType) {
|
|
216
|
+
const scopeIdMap = {
|
|
217
|
+
session: entry.sessionId,
|
|
218
|
+
user: entry.userId,
|
|
219
|
+
agent: entry.agentId,
|
|
220
|
+
};
|
|
221
|
+
const scopeId = scopeIdMap[scope];
|
|
222
|
+
if (!scopeId) {
|
|
223
|
+
throw new Error(`Cannot reset path for entry without ${scope} info.`);
|
|
224
|
+
}
|
|
225
|
+
const prefix = `by-${scope}`;
|
|
226
|
+
// Build path based on entry type
|
|
227
|
+
if (entryType === "compact") {
|
|
228
|
+
return joinURL("/", prefix, scopeId, "@metadata/compact", entry.id);
|
|
229
|
+
}
|
|
230
|
+
if (entryType === "memory") {
|
|
231
|
+
return joinURL("/", prefix, scopeId, "@metadata/memory", entry.id);
|
|
211
232
|
}
|
|
233
|
+
// Default: history entry
|
|
212
234
|
return joinURL("/", prefix, scopeId, entry.id);
|
|
213
235
|
}
|
|
214
236
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AFSEntry, AFSModule } from "@aigne/afs";
|
|
2
2
|
import { initDatabase } from "@aigne/sqlite";
|
|
3
|
-
import type { AFSStorage, AFSStorageCreatePayload, AFSStorageListOptions, AFSStorageReadOptions
|
|
3
|
+
import type { AFSStorage, AFSStorageCreateOptions, AFSStorageCreatePayload, AFSStorageDeleteOptions, AFSStorageListOptions, AFSStorageReadOptions } from "./type.js";
|
|
4
4
|
export * from "./type.js";
|
|
5
5
|
export interface SharedAFSStorageOptions {
|
|
6
6
|
url?: string;
|
|
@@ -24,10 +24,8 @@ export declare class AFSStorageWithModule implements AFSStorage {
|
|
|
24
24
|
data: AFSEntry[];
|
|
25
25
|
}>;
|
|
26
26
|
read(id: string, options?: AFSStorageReadOptions): Promise<AFSEntry | undefined>;
|
|
27
|
-
create(entry: AFSStorageCreatePayload): Promise<AFSEntry>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
data: AFSEntry[];
|
|
27
|
+
create(entry: AFSStorageCreatePayload, options?: AFSStorageCreateOptions): Promise<AFSEntry>;
|
|
28
|
+
delete(id: string, options?: AFSStorageDeleteOptions): Promise<{
|
|
29
|
+
deletedCount: number;
|
|
31
30
|
}>;
|
|
32
|
-
readCompact(type: CompactType, id: string, options?: AFSStorageReadOptions): Promise<AFSEntry | undefined>;
|
|
33
31
|
}
|
package/lib/esm/storage/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { and, asc, desc, eq, gt, initDatabase, lt, sql } from "@aigne/sqlite";
|
|
|
2
2
|
import { migrate } from "./migrate.js";
|
|
3
3
|
import { compactTable } from "./models/compact.js";
|
|
4
4
|
import { entriesTable } from "./models/entries.js";
|
|
5
|
+
import { memoryTable } from "./models/memory.js";
|
|
5
6
|
export * from "./type.js";
|
|
6
7
|
const DEFAULT_AFS_STORAGE_LIST_LIMIT = 10;
|
|
7
8
|
export class SharedAFSStorage {
|
|
@@ -32,86 +33,82 @@ export class AFSStorageWithModule {
|
|
|
32
33
|
db,
|
|
33
34
|
entries: entriesTable(this.module),
|
|
34
35
|
compact: compactTable(this.module),
|
|
36
|
+
memory: memoryTable(this.module),
|
|
35
37
|
})));
|
|
36
38
|
}
|
|
37
39
|
return this._tables;
|
|
38
40
|
}
|
|
39
41
|
async list(options = {}) {
|
|
40
|
-
const { filter, limit = DEFAULT_AFS_STORAGE_LIST_LIMIT } = options;
|
|
41
|
-
const { db, entries } = await this.tables;
|
|
42
|
+
const { type = "history", scope, filter, limit = DEFAULT_AFS_STORAGE_LIST_LIMIT } = options;
|
|
43
|
+
const { db, entries, compact, memory } = await this.tables;
|
|
44
|
+
// Select table based on type
|
|
45
|
+
const table = type === "compact" ? compact : type === "memory" ? memory : entries;
|
|
42
46
|
const data = await db
|
|
43
47
|
.select()
|
|
44
|
-
.from(
|
|
45
|
-
.where(and(
|
|
46
|
-
|
|
48
|
+
.from(table)
|
|
49
|
+
.where(and(
|
|
50
|
+
// Add scope filter for compact/memory tables
|
|
51
|
+
scope && type !== "history"
|
|
52
|
+
? eq(sql `json_extract(${table.metadata}, '$.scope')`, scope)
|
|
53
|
+
: undefined, filter?.agentId ? eq(table.agentId, filter.agentId) : undefined, filter?.userId ? eq(table.userId, filter.userId) : undefined, filter?.sessionId ? eq(table.sessionId, filter.sessionId) : undefined, filter?.before ? lt(table.createdAt, new Date(filter.before)) : undefined, filter?.after ? gt(table.createdAt, new Date(filter.after)) : undefined))
|
|
54
|
+
.orderBy(...(options.orderBy ?? [["createdAt", "desc"]]).map((item) => (item[1] === "asc" ? asc : desc)(sql.identifier(item[0]))))
|
|
47
55
|
.limit(limit)
|
|
48
56
|
.execute();
|
|
49
57
|
return { data };
|
|
50
58
|
}
|
|
51
59
|
async read(id, options) {
|
|
52
|
-
const {
|
|
60
|
+
const { type = "history", scope, filter } = options ?? {};
|
|
61
|
+
const { db, entries, compact, memory } = await this.tables;
|
|
62
|
+
// Select table based on type
|
|
63
|
+
const table = type === "compact" ? compact : type === "memory" ? memory : entries;
|
|
53
64
|
return db
|
|
54
65
|
.select()
|
|
55
|
-
.from(
|
|
56
|
-
.where(and(eq(
|
|
66
|
+
.from(table)
|
|
67
|
+
.where(and(eq(table.id, id),
|
|
68
|
+
// Add scope filter for compact/memory tables
|
|
69
|
+
scope && type !== "history"
|
|
70
|
+
? eq(sql `json_extract(${table.metadata}, '$.scope')`, scope)
|
|
71
|
+
: undefined, filter?.agentId ? eq(table.agentId, filter.agentId) : undefined, filter?.userId ? eq(table.userId, filter.userId) : undefined, filter?.sessionId ? eq(table.sessionId, filter.sessionId) : undefined))
|
|
57
72
|
.limit(1)
|
|
58
73
|
.execute()
|
|
59
|
-
.then((
|
|
74
|
+
.then((rows) => rows.at(0));
|
|
60
75
|
}
|
|
61
|
-
async create(entry) {
|
|
62
|
-
const {
|
|
76
|
+
async create(entry, options) {
|
|
77
|
+
const { type = "history", scope } = options ?? {};
|
|
78
|
+
const { db, entries, compact, memory } = await this.tables;
|
|
79
|
+
// Select table based on type
|
|
80
|
+
const table = type === "compact" ? compact : type === "memory" ? memory : entries;
|
|
81
|
+
// Prepare entry data - only add scope to metadata for compact/memory types
|
|
82
|
+
const entryData = { ...entry, metadata: { ...entry.metadata, scope } };
|
|
83
|
+
// Try update first, then insert if not found (upsert)
|
|
63
84
|
let result = await db
|
|
64
|
-
.update(
|
|
65
|
-
.set(
|
|
66
|
-
.where(eq(
|
|
85
|
+
.update(table)
|
|
86
|
+
.set(entryData)
|
|
87
|
+
.where(eq(table.id, entry.id))
|
|
67
88
|
.returning()
|
|
68
89
|
.execute();
|
|
69
90
|
if (!result.length) {
|
|
70
|
-
result = await db.insert(
|
|
91
|
+
result = await db.insert(table).values(entryData).returning().execute();
|
|
71
92
|
}
|
|
72
93
|
const [res] = result;
|
|
73
94
|
if (!res)
|
|
74
|
-
throw new Error(
|
|
95
|
+
throw new Error(`Failed to create ${type} entry, no result`);
|
|
75
96
|
return res;
|
|
76
97
|
}
|
|
77
|
-
async
|
|
78
|
-
const {
|
|
98
|
+
async delete(id, options) {
|
|
99
|
+
const { type = "history", scope, filter } = options ?? {};
|
|
100
|
+
const { db, entries, compact, memory } = await this.tables;
|
|
101
|
+
// Select table based on type
|
|
102
|
+
const table = type === "compact" ? compact : type === "memory" ? memory : entries;
|
|
79
103
|
const result = await db
|
|
80
|
-
.
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
},
|
|
87
|
-
})
|
|
104
|
+
.delete(table)
|
|
105
|
+
.where(and(eq(table.id, id),
|
|
106
|
+
// Add scope filter for compact/memory tables
|
|
107
|
+
scope && type !== "history"
|
|
108
|
+
? eq(sql `json_extract(${table.metadata}, '$.scope')`, scope)
|
|
109
|
+
: undefined, filter?.agentId ? eq(table.agentId, filter.agentId) : undefined, filter?.userId ? eq(table.userId, filter.userId) : undefined, filter?.sessionId ? eq(table.sessionId, filter.sessionId) : undefined))
|
|
88
110
|
.returning()
|
|
89
111
|
.execute();
|
|
90
|
-
|
|
91
|
-
if (!res)
|
|
92
|
-
throw new Error(`Failed to create ${type} compact entry, no result`);
|
|
93
|
-
return res;
|
|
94
|
-
}
|
|
95
|
-
async listCompact(type, options = {}) {
|
|
96
|
-
const { db, compact } = await this.tables;
|
|
97
|
-
const { filter, limit = DEFAULT_AFS_STORAGE_LIST_LIMIT } = options;
|
|
98
|
-
const data = await db
|
|
99
|
-
.select()
|
|
100
|
-
.from(compact)
|
|
101
|
-
.where(and(eq(sql `json_extract(${compact.metadata}, '$.type')`, type), filter?.agentId ? eq(compact.agentId, filter.agentId) : undefined, filter?.userId ? eq(compact.userId, filter.userId) : undefined, filter?.sessionId ? eq(compact.sessionId, filter.sessionId) : undefined, filter?.before ? lt(compact.createdAt, new Date(filter.before)) : undefined, filter?.after ? gt(compact.createdAt, new Date(filter.after)) : undefined))
|
|
102
|
-
.orderBy(...(options.orderBy ?? []).map((item) => (item[1] === "asc" ? asc : desc)(sql.identifier(item[0]))))
|
|
103
|
-
.limit(limit)
|
|
104
|
-
.execute();
|
|
105
|
-
return { data };
|
|
106
|
-
}
|
|
107
|
-
async readCompact(type, id, options) {
|
|
108
|
-
const { db, compact } = await this.tables;
|
|
109
|
-
return db
|
|
110
|
-
.select()
|
|
111
|
-
.from(compact)
|
|
112
|
-
.where(and(eq(sql `json_extract(${compact.metadata}, '$.type')`, type), eq(compact.id, id), options?.filter?.agentId ? eq(compact.agentId, options.filter.agentId) : undefined, options?.filter?.userId ? eq(compact.userId, options.filter.userId) : undefined, options?.filter?.sessionId ? eq(compact.sessionId, options.filter.sessionId) : undefined))
|
|
113
|
-
.limit(1)
|
|
114
|
-
.execute()
|
|
115
|
-
.then((rows) => rows.at(0));
|
|
112
|
+
return { deletedCount: result.length };
|
|
116
113
|
}
|
|
117
114
|
}
|
|
@@ -3,8 +3,16 @@ import { v7 } from "@aigne/uuid";
|
|
|
3
3
|
import { init } from "./migrations/001-init.js";
|
|
4
4
|
import { addAgentId } from "./migrations/002-add-agent-id.js";
|
|
5
5
|
import { addCompactTable } from "./migrations/003-add-compact-table.js";
|
|
6
|
+
import { addMemoryTable } from "./migrations/004-add-memory-table.js";
|
|
7
|
+
import { addIndexes } from "./migrations/005-add-indexes.js";
|
|
6
8
|
export async function migrate(db, module) {
|
|
7
|
-
const migrations = [
|
|
9
|
+
const migrations = [
|
|
10
|
+
init,
|
|
11
|
+
addAgentId,
|
|
12
|
+
addCompactTable,
|
|
13
|
+
addMemoryTable,
|
|
14
|
+
addIndexes,
|
|
15
|
+
];
|
|
8
16
|
const migrationsTable = "__drizzle_migrations";
|
|
9
17
|
const migrationTableCreate = sql `
|
|
10
18
|
CREATE TABLE IF NOT EXISTS ${sql.identifier(migrationsTable)} (
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { sql } from "@aigne/sqlite";
|
|
2
|
+
import { memoryTableName } from "../models/memory.js";
|
|
3
|
+
export const addMemoryTable = {
|
|
4
|
+
hash: "004-add-memory-table",
|
|
5
|
+
sql: (module) => [
|
|
6
|
+
sql `\
|
|
7
|
+
CREATE TABLE IF NOT EXISTS ${sql.identifier(memoryTableName(module))} (
|
|
8
|
+
"id" TEXT NOT NULL PRIMARY KEY,
|
|
9
|
+
"createdAt" DATETIME NOT NULL,
|
|
10
|
+
"updatedAt" DATETIME NOT NULL,
|
|
11
|
+
"path" TEXT NOT NULL,
|
|
12
|
+
"sessionId" TEXT,
|
|
13
|
+
"userId" TEXT,
|
|
14
|
+
"agentId" TEXT,
|
|
15
|
+
"metadata" JSON,
|
|
16
|
+
"content" JSON
|
|
17
|
+
)
|
|
18
|
+
`,
|
|
19
|
+
],
|
|
20
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { sql } from "@aigne/sqlite";
|
|
2
|
+
import { compactTableName } from "../models/compact.js";
|
|
3
|
+
import { entriesTableName } from "../models/entries.js";
|
|
4
|
+
import { memoryTableName } from "../models/memory.js";
|
|
5
|
+
export const addIndexes = {
|
|
6
|
+
hash: "005-add-indexes",
|
|
7
|
+
sql: (module) => {
|
|
8
|
+
const entriesTable = entriesTableName(module);
|
|
9
|
+
const memoryTable = memoryTableName(module);
|
|
10
|
+
const compactTable = compactTableName(module);
|
|
11
|
+
return [
|
|
12
|
+
// Entries table indexes
|
|
13
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_entries_session_created" ON ${sql.identifier(entriesTable)} ("sessionId", "createdAt" DESC)`,
|
|
14
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_entries_user_created" ON ${sql.identifier(entriesTable)} ("userId", "createdAt" DESC)`,
|
|
15
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_entries_agent_created" ON ${sql.identifier(entriesTable)} ("agentId", "createdAt" DESC)`,
|
|
16
|
+
// Memory table indexes
|
|
17
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_memory_session_created" ON ${sql.identifier(memoryTable)} ("sessionId", "createdAt" DESC)`,
|
|
18
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_memory_user_created" ON ${sql.identifier(memoryTable)} ("userId", "createdAt" DESC)`,
|
|
19
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_memory_agent_created" ON ${sql.identifier(memoryTable)} ("agentId", "createdAt" DESC)`,
|
|
20
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_memory_scope" ON ${sql.identifier(memoryTable)} (json_extract("metadata", '$.scope'))`,
|
|
21
|
+
// Compact table indexes
|
|
22
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_compact_session_created" ON ${sql.identifier(compactTable)} ("sessionId", "createdAt" DESC)`,
|
|
23
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_compact_user_created" ON ${sql.identifier(compactTable)} ("userId", "createdAt" DESC)`,
|
|
24
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_compact_agent_created" ON ${sql.identifier(compactTable)} ("agentId", "createdAt" DESC)`,
|
|
25
|
+
sql `CREATE INDEX IF NOT EXISTS "idx_compact_scope" ON ${sql.identifier(compactTable)} (json_extract("metadata", '$.scope'))`,
|
|
26
|
+
];
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -143,7 +143,7 @@ export declare const compactTable: (module: AFSModule) => import("@aigne/sqlite"
|
|
|
143
143
|
dataType: "custom";
|
|
144
144
|
columnType: "SQLiteCustomColumn";
|
|
145
145
|
data: {
|
|
146
|
-
|
|
146
|
+
scope?: string;
|
|
147
147
|
latestEntryId?: string;
|
|
148
148
|
} & Record<string, unknown>;
|
|
149
149
|
driverParam: string;
|