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