@aigne/afs-history 1.2.0-beta.5 → 1.2.0-beta.7

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 +16 -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 +2 -2
package/lib/esm/index.js CHANGED
@@ -15,36 +15,80 @@ export class AFSHistory {
15
15
  afs;
16
16
  router = createRouter({
17
17
  routes: {
18
- "/new": { type: "root", id: "new-history" },
19
- "/by-session": { type: "root", id: "by-session" },
20
- "/by-session/:sessionId": { type: "list", id: "by-session" },
21
- "/by-session/:sessionId/@metadata/compact": { type: "compact-list", id: "by-session" },
22
- "/by-session/:sessionId/@metadata/compact/new": { type: "compact-new", id: "by-session" },
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
- type: "compact-detail",
25
- id: "by-session",
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-session/:sessionId/:entryId": { type: "detail", id: "by-session" },
28
- "/by-user": { type: "root", id: "by-user" },
29
- "/by-user/:userId": { type: "list", id: "by-user" },
30
- "/by-user/:userId/@metadata/compact": { type: "compact-list", id: "by-user" },
31
- "/by-user/:userId/@metadata/compact/new": { type: "compact-new", id: "by-user" },
32
- "/by-user/:userId/@metadata/compact/:compactId": { type: "compact-detail", id: "by-user" },
33
- "/by-user/:userId/:entryId": { type: "detail", id: "by-user" },
34
- "/by-agent": { type: "root", id: "by-agent" },
35
- "/by-agent/:agentId": { type: "list", id: "by-agent" },
36
- "/by-agent/:agentId/@metadata/compact": { type: "compact-list", id: "by-agent" },
37
- "/by-agent/:agentId/@metadata/compact/new": { type: "compact-new", id: "by-agent" },
38
- "/by-agent/:agentId/@metadata/compact/:compactId": { type: "compact-detail", id: "by-agent" },
39
- "/by-agent/:agentId/:entryId": { type: "detail", id: "by-agent" },
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
- // If path doesn't match any virtual path pattern, return empty
73
- if (!match) {
115
+ if (!match || match.action !== "list") {
74
116
  return { data: [] };
75
117
  }
76
- const rootEntry = this.rootEntries.find((entry) => entry.path === `/${match.id}`);
77
- if (!rootEntry)
78
- return { data: [] };
79
- if (match.type === "root") {
80
- return { data: [rootEntry] };
81
- }
82
- const matchId = match.id;
83
- if (match.type === "list" &&
84
- (matchId === "by-session" || matchId === "by-user" || matchId === "by-agent")) {
85
- // Merge virtual path filter with explicit filter options
86
- const mergedFilter = {
87
- ...options?.filter,
88
- ...match.params,
89
- };
90
- const result = await this.storage.list({
91
- ...options,
92
- filter: mergedFilter,
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 rootEntry = this.rootEntries.find((entry) => entry.path === `/${match.id}`);
124
- if (!rootEntry)
125
- return {};
126
- if (match.type === "root") {
127
- return { data: rootEntry };
128
- }
129
- if (match.type === "detail" &&
130
- (match.id === "by-session" || match.id === "by-user" || match.id === "by-agent")) {
131
- const entryId = match.params?.entryId;
132
- if (!entryId)
133
- throw new Error("Entry ID is required in the path to read detail.");
134
- const data = await this.storage.read(entryId, {
135
- filter: match.params,
136
- });
137
- return {
138
- data: data && {
139
- ...data,
140
- path: this.normalizePath(data, match.id),
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?.type === "compact-new") {
165
- const compactType = this.getCompactType(match.id);
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
- if (match?.id !== "new-history") {
175
- throw new Error("Can only write to /new or @metadata/compact/new paths.");
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
- this.afs?.emit("historyCreated", { entry });
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, "by-session"),
188
+ path: this.normalizePath(entry, scope, type),
189
189
  },
190
190
  };
191
191
  }
192
- getCompactType(id) {
193
- const mapping = {
194
- "by-session": "session",
195
- "by-user": "user",
196
- "by-agent": "agent",
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, type) {
204
- const [prefix, scopeId] = {
205
- "by-session": ["by-session", entry.sessionId],
206
- "by-user": ["by-user", entry.userId],
207
- "by-agent": ["by-agent", entry.agentId],
208
- }[type] || [];
209
- if (!prefix || !scopeId) {
210
- throw new Error(`Cannot reset path for entry without ${type} info.`);
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, 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;
@@ -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
- createCompact(type: CompactType, entry: AFSStorageCreatePayload): Promise<AFSEntry>;
29
- listCompact(type: CompactType, options?: AFSStorageListOptions): Promise<{
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
  }
@@ -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(entries)
45
- .where(and(filter?.agentId ? eq(entries.agentId, filter.agentId) : undefined, filter?.userId ? eq(entries.userId, filter.userId) : undefined, filter?.sessionId ? eq(entries.sessionId, filter.sessionId) : undefined, filter?.before ? lt(entries.createdAt, new Date(filter.before)) : undefined, filter?.after ? gt(entries.createdAt, new Date(filter.after)) : undefined))
46
- .orderBy(...(options.orderBy ?? []).map((item) => (item[1] === "asc" ? asc : desc)(sql.identifier(item[0]))))
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 { db, entries } = await this.tables;
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(entries)
56
- .where(and(eq(entries.id, id), options?.filter?.agentId ? eq(entries.agentId, options.filter.agentId) : undefined, options?.filter?.userId ? eq(entries.userId, options.filter.userId) : undefined, options?.filter?.sessionId ? eq(entries.sessionId, options.filter.sessionId) : undefined))
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((memory) => memory.at(0));
74
+ .then((rows) => rows.at(0));
60
75
  }
61
- async create(entry) {
62
- const { db, entries } = await this.tables;
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(entries)
65
- .set(entry)
66
- .where(eq(entries.id, entry.id))
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(entries).values(entry).returning().execute();
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("Failed to create AFS entry, no result");
95
+ throw new Error(`Failed to create ${type} entry, no result`);
75
96
  return res;
76
97
  }
77
- async createCompact(type, entry) {
78
- const { db, compact } = await this.tables;
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
- .insert(compact)
81
- .values({
82
- ...entry,
83
- metadata: {
84
- ...entry.metadata,
85
- type,
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
- const [res] = result;
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 = [init, addAgentId, addCompactTable];
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,2 @@
1
+ import type { AFSStorageMigrations } from "../type.js";
2
+ export declare const addMemoryTable: AFSStorageMigrations;
@@ -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,2 @@
1
+ import type { AFSStorageMigrations } from "../type.js";
2
+ export declare const addIndexes: AFSStorageMigrations;
@@ -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
- type?: string;
146
+ scope?: string;
147
147
  latestEntryId?: string;
148
148
  } & Record<string, unknown>;
149
149
  driverParam: string;