aimemory-core 1.0.1
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/.eslintrc.json +22 -0
- package/.github/workflows/ci.yml +57 -0
- package/.prettierrc +8 -0
- package/README.md +197 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +41 -0
- package/dist/config.js.map +1 -0
- package/dist/contextBuilder.d.ts +16 -0
- package/dist/contextBuilder.d.ts.map +1 -0
- package/dist/contextBuilder.js +139 -0
- package/dist/contextBuilder.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/licensing.d.ts +45 -0
- package/dist/licensing.d.ts.map +1 -0
- package/dist/licensing.js +99 -0
- package/dist/licensing.js.map +1 -0
- package/dist/memoryManager.d.ts +35 -0
- package/dist/memoryManager.d.ts.map +1 -0
- package/dist/memoryManager.js +265 -0
- package/dist/memoryManager.js.map +1 -0
- package/dist/metadataStore.d.ts +24 -0
- package/dist/metadataStore.d.ts.map +1 -0
- package/dist/metadataStore.js +247 -0
- package/dist/metadataStore.js.map +1 -0
- package/dist/planManager.d.ts +80 -0
- package/dist/planManager.d.ts.map +1 -0
- package/dist/planManager.js +327 -0
- package/dist/planManager.js.map +1 -0
- package/dist/rateLimiter.d.ts +49 -0
- package/dist/rateLimiter.d.ts.map +1 -0
- package/dist/rateLimiter.js +142 -0
- package/dist/rateLimiter.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/postgres.d.ts +31 -0
- package/dist/storage/postgres.d.ts.map +1 -0
- package/dist/storage/postgres.js +171 -0
- package/dist/storage/postgres.js.map +1 -0
- package/dist/storage/redis.d.ts +34 -0
- package/dist/storage/redis.d.ts.map +1 -0
- package/dist/storage/redis.js +101 -0
- package/dist/storage/redis.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/usageTracker.d.ts +63 -0
- package/dist/usageTracker.d.ts.map +1 -0
- package/dist/usageTracker.js +238 -0
- package/dist/usageTracker.js.map +1 -0
- package/dist/vectorStore.d.ts +18 -0
- package/dist/vectorStore.d.ts.map +1 -0
- package/dist/vectorStore.js +97 -0
- package/dist/vectorStore.js.map +1 -0
- package/examples/advanced.ts +164 -0
- package/examples/basic.ts +87 -0
- package/package.json +60 -0
- package/src/config.ts +65 -0
- package/src/contextBuilder.ts +184 -0
- package/src/index.ts +209 -0
- package/src/licensing.ts +138 -0
- package/src/memoryManager.ts +340 -0
- package/src/metadataStore.ts +298 -0
- package/src/planManager.ts +417 -0
- package/src/rateLimiter.ts +186 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/postgres.ts +209 -0
- package/src/storage/redis.ts +117 -0
- package/src/types.ts +114 -0
- package/src/usageTracker.ts +325 -0
- package/src/vectorStore.ts +116 -0
- package/tests/aibrain.test.ts +171 -0
- package/tests/contextBuilder.test.ts +138 -0
- package/tests/memoryManager.test.ts +205 -0
- package/tests/metadataStore.test.ts +131 -0
- package/tests/rateLimiter.test.ts +57 -0
- package/tests/usageTracker.test.ts +62 -0
- package/tests/vectorStore.test.ts +106 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { Memory, MemoryFilter, MemoryType, MemoryStats } from './types.js';
|
|
2
|
+
|
|
3
|
+
export class MetadataStore {
|
|
4
|
+
private memories: Map<string, Memory> = new Map();
|
|
5
|
+
private indexByType: Map<MemoryType, Set<string>> = new Map();
|
|
6
|
+
private indexByTag: Map<string, Set<string>> = new Map();
|
|
7
|
+
private indexByUser: Map<string, Set<string>> = new Map();
|
|
8
|
+
private indexBySession: Map<string, Set<string>> = new Map();
|
|
9
|
+
private indexByTime: string[] = [];
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.initializeIndexes();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private initializeIndexes(): void {
|
|
16
|
+
const types: MemoryType[] = ['conversation', 'fact', 'preference', 'instruction', 'context', 'custom'];
|
|
17
|
+
for (const type of types) {
|
|
18
|
+
this.indexByType.set(type, new Set());
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async add(memory: Memory): Promise<void> {
|
|
23
|
+
if (this.memories.has(memory.id)) {
|
|
24
|
+
await this.update(memory);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.memories.set(memory.id, { ...memory });
|
|
29
|
+
|
|
30
|
+
this.indexByType.get(memory.metadata.type)?.add(memory.id);
|
|
31
|
+
|
|
32
|
+
const tags = memory.metadata.tags || [];
|
|
33
|
+
for (const tag of tags) {
|
|
34
|
+
if (!this.indexByTag.has(tag)) {
|
|
35
|
+
this.indexByTag.set(tag, new Set());
|
|
36
|
+
}
|
|
37
|
+
this.indexByTag.get(tag)!.add(memory.id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (memory.metadata.userId) {
|
|
41
|
+
if (!this.indexByUser.has(memory.metadata.userId)) {
|
|
42
|
+
this.indexByUser.set(memory.metadata.userId, new Set());
|
|
43
|
+
}
|
|
44
|
+
this.indexByUser.get(memory.metadata.userId)!.add(memory.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (memory.metadata.sessionId) {
|
|
48
|
+
if (!this.indexBySession.has(memory.metadata.sessionId)) {
|
|
49
|
+
this.indexBySession.set(memory.metadata.sessionId, new Set());
|
|
50
|
+
}
|
|
51
|
+
this.indexBySession.get(memory.metadata.sessionId)!.add(memory.id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.insertByTime(memory.id, memory.timestamp);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private insertByTime(id: string, timestamp: number): void {
|
|
58
|
+
let left = 0;
|
|
59
|
+
let right = this.indexByTime.length - 1;
|
|
60
|
+
|
|
61
|
+
while (left <= right) {
|
|
62
|
+
const mid = Math.floor((left + right) / 2);
|
|
63
|
+
const midTime = this.memories.get(this.indexByTime[mid])?.timestamp || 0;
|
|
64
|
+
|
|
65
|
+
if (midTime < timestamp) {
|
|
66
|
+
left = mid + 1;
|
|
67
|
+
} else {
|
|
68
|
+
right = mid - 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.indexByTime.splice(left, 0, id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async update(memory: Memory): Promise<void> {
|
|
76
|
+
const existing = this.memories.get(memory.id);
|
|
77
|
+
if (!existing) {
|
|
78
|
+
throw new Error(`Memory ${memory.id} not found`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (existing.metadata.type !== memory.metadata.type) {
|
|
82
|
+
this.indexByType.get(existing.metadata.type)?.delete(memory.id);
|
|
83
|
+
this.indexByType.get(memory.metadata.type)?.add(memory.id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const oldTags = new Set(existing.metadata.tags || []);
|
|
87
|
+
const newTags = new Set(memory.metadata.tags || []);
|
|
88
|
+
|
|
89
|
+
for (const tag of oldTags) {
|
|
90
|
+
if (!newTags.has(tag)) {
|
|
91
|
+
this.indexByTag.get(tag)?.delete(memory.id);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const tag of newTags) {
|
|
95
|
+
if (!oldTags.has(tag)) {
|
|
96
|
+
if (!this.indexByTag.has(tag)) {
|
|
97
|
+
this.indexByTag.set(tag, new Set());
|
|
98
|
+
}
|
|
99
|
+
this.indexByTag.get(tag)!.add(memory.id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.memories.set(memory.id, { ...memory });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async delete(id: string): Promise<void> {
|
|
107
|
+
const memory = this.memories.get(id);
|
|
108
|
+
if (!memory) return;
|
|
109
|
+
|
|
110
|
+
this.indexByType.get(memory.metadata.type)?.delete(id);
|
|
111
|
+
|
|
112
|
+
for (const tag of memory.metadata.tags) {
|
|
113
|
+
this.indexByTag.get(tag)?.delete(id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (memory.metadata.userId) {
|
|
117
|
+
this.indexByUser.get(memory.metadata.userId)?.delete(id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (memory.metadata.sessionId) {
|
|
121
|
+
this.indexBySession.get(memory.metadata.sessionId)?.delete(id);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const timeIndex = this.indexByTime.indexOf(id);
|
|
125
|
+
if (timeIndex !== -1) {
|
|
126
|
+
this.indexByTime.splice(timeIndex, 1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.memories.delete(id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getById(id: string): Promise<Memory | null> {
|
|
133
|
+
const memory = this.memories.get(id);
|
|
134
|
+
if (!memory) return null;
|
|
135
|
+
return { ...memory };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async getAll(): Promise<Memory[]> {
|
|
139
|
+
return Array.from(this.memories.values()).map(m => ({ ...m }));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async find(filter: MemoryFilter, limit: number = 100): Promise<Memory[]> {
|
|
143
|
+
let candidateIds: Set<string> | null = null;
|
|
144
|
+
|
|
145
|
+
if (filter.types && filter.types.length > 0) {
|
|
146
|
+
const typeIds = new Set<string>();
|
|
147
|
+
for (const type of filter.types) {
|
|
148
|
+
const ids = this.indexByType.get(type);
|
|
149
|
+
if (ids) {
|
|
150
|
+
for (const id of ids) {
|
|
151
|
+
typeIds.add(id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
candidateIds = typeIds;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
159
|
+
const tagIds = new Set<string>();
|
|
160
|
+
for (const tag of filter.tags) {
|
|
161
|
+
const ids = this.indexByTag.get(tag);
|
|
162
|
+
if (ids) {
|
|
163
|
+
for (const id of ids) {
|
|
164
|
+
tagIds.add(id);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
candidateIds = candidateIds
|
|
169
|
+
? new Set([...candidateIds].filter(id => tagIds.has(id)))
|
|
170
|
+
: tagIds;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (filter.userId) {
|
|
174
|
+
const userIds = this.indexByUser.get(filter.userId);
|
|
175
|
+
if (userIds) {
|
|
176
|
+
candidateIds = candidateIds
|
|
177
|
+
? new Set([...candidateIds].filter(id => userIds.has(id)))
|
|
178
|
+
: new Set(userIds);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (filter.sessionId) {
|
|
183
|
+
const sessionIds = this.indexBySession.get(filter.sessionId);
|
|
184
|
+
if (sessionIds) {
|
|
185
|
+
candidateIds = candidateIds
|
|
186
|
+
? new Set([...candidateIds].filter(id => sessionIds.has(id)))
|
|
187
|
+
: new Set(sessionIds);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const idsToSearch = candidateIds || new Set(this.memories.keys());
|
|
192
|
+
const results: Memory[] = [];
|
|
193
|
+
|
|
194
|
+
for (const id of idsToSearch) {
|
|
195
|
+
const memory = this.memories.get(id);
|
|
196
|
+
if (!memory) continue;
|
|
197
|
+
|
|
198
|
+
if (filter.minImportance !== undefined && memory.metadata.importance < filter.minImportance) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (filter.maxImportance !== undefined && memory.metadata.importance > filter.maxImportance) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (filter.startDate !== undefined && memory.timestamp < filter.startDate) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (filter.endDate !== undefined && memory.timestamp > filter.endDate) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (filter.metadata) {
|
|
215
|
+
const matches = Object.entries(filter.metadata).every(
|
|
216
|
+
([key, value]) => memory.metadata[key] === value
|
|
217
|
+
);
|
|
218
|
+
if (!matches) continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
results.push({ ...memory });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
results.sort((a, b) => b.timestamp - a.timestamp);
|
|
225
|
+
return results.slice(0, limit);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async getByTimeRange(startDate: number, endDate: number): Promise<Memory[]> {
|
|
229
|
+
const results: Memory[] = [];
|
|
230
|
+
|
|
231
|
+
for (const id of this.indexByTime) {
|
|
232
|
+
const memory = this.memories.get(id);
|
|
233
|
+
if (!memory) continue;
|
|
234
|
+
|
|
235
|
+
if (memory.timestamp >= startDate && memory.timestamp <= endDate) {
|
|
236
|
+
results.push({ ...memory });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async getRecent(limit: number = 10): Promise<Memory[]> {
|
|
244
|
+
const recentIds = this.indexByTime.slice(-limit).reverse();
|
|
245
|
+
return recentIds.map(id => ({ ...this.memories.get(id)! })).filter(Boolean);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async getStats(): Promise<MemoryStats> {
|
|
249
|
+
const memories = Array.from(this.memories.values());
|
|
250
|
+
|
|
251
|
+
const memoriesByType: Record<MemoryType, number> = {
|
|
252
|
+
conversation: 0,
|
|
253
|
+
fact: 0,
|
|
254
|
+
preference: 0,
|
|
255
|
+
instruction: 0,
|
|
256
|
+
context: 0,
|
|
257
|
+
custom: 0,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const memoriesByTag: Record<string, number> = {};
|
|
261
|
+
|
|
262
|
+
let oldestMemory = Date.now();
|
|
263
|
+
let newestMemory = 0;
|
|
264
|
+
|
|
265
|
+
for (const memory of memories) {
|
|
266
|
+
memoriesByType[memory.metadata.type]++;
|
|
267
|
+
|
|
268
|
+
const tags = memory.metadata.tags || [];
|
|
269
|
+
for (const tag of tags) {
|
|
270
|
+
memoriesByTag[tag] = (memoriesByTag[tag] || 0) + 1;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (memory.timestamp < oldestMemory) oldestMemory = memory.timestamp;
|
|
274
|
+
if (memory.timestamp > newestMemory) newestMemory = memory.timestamp;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
totalMemories: memories.length,
|
|
279
|
+
memoriesByType,
|
|
280
|
+
memoriesByTag,
|
|
281
|
+
oldestMemory: memories.length > 0 ? oldestMemory : Date.now(),
|
|
282
|
+
newestMemory: memories.length > 0 ? newestMemory : Date.now(),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async clear(): Promise<void> {
|
|
287
|
+
this.memories.clear();
|
|
288
|
+
this.indexByTag.clear();
|
|
289
|
+
this.indexByUser.clear();
|
|
290
|
+
this.indexBySession.clear();
|
|
291
|
+
this.indexByTime.length = 0;
|
|
292
|
+
this.initializeIndexes();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async size(): Promise<number> {
|
|
296
|
+
return this.memories.size;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { RateLimiter, RateLimitResult, DEFAULT_RATE_LIMITS } from './rateLimiter.js';
|
|
2
|
+
import { UsageTracker, UsageLimits, UsageCheckResult, DEFAULT_USAGE_LIMITS } from './usageTracker.js';
|
|
3
|
+
import { EmbeddingFunction, Memory, MemoryMetadata, SearchResult, ContextResult, MemoryStats } from './types.js';
|
|
4
|
+
import { InMemoryVectorStore } from './vectorStore.js';
|
|
5
|
+
import { MetadataStore } from './metadataStore.js';
|
|
6
|
+
import { AIMemoryConfig, validateConfig } from './config.js';
|
|
7
|
+
|
|
8
|
+
export type Plan = 'free' | 'starter' | 'pro' | 'enterprise';
|
|
9
|
+
export type ExtensionPlan = 'free' | 'starter' | 'plus' | 'premium';
|
|
10
|
+
export type TeamPlan = 'free' | 'team' | 'business' | 'enterprise';
|
|
11
|
+
export type ProductPlan = Plan | ExtensionPlan | TeamPlan;
|
|
12
|
+
|
|
13
|
+
export type ProductType = 'npm' | 'extension' | 'team';
|
|
14
|
+
|
|
15
|
+
export interface PlanConfig {
|
|
16
|
+
name: ProductPlan;
|
|
17
|
+
price: number;
|
|
18
|
+
billingPeriod: 'monthly' | 'yearly';
|
|
19
|
+
productType: ProductType;
|
|
20
|
+
features: PlanFeatures;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PlanFeatures {
|
|
24
|
+
maxMemories: number;
|
|
25
|
+
maxSearchPerDay: number;
|
|
26
|
+
maxContextPerDay: number;
|
|
27
|
+
maxApiCallsPerDay: number;
|
|
28
|
+
maxStorageMB: number;
|
|
29
|
+
embeddingDimension: number;
|
|
30
|
+
persistence: 'memory' | 'redis' | 'postgres';
|
|
31
|
+
support: 'community' | 'email' | 'priority';
|
|
32
|
+
customIntegrations: boolean;
|
|
33
|
+
apiKeys: number;
|
|
34
|
+
teamMembers: number;
|
|
35
|
+
cloudSync: boolean;
|
|
36
|
+
advancedSearch: boolean;
|
|
37
|
+
teamSharing: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PlanLimits {
|
|
41
|
+
rateLimit: {
|
|
42
|
+
windowMs: number;
|
|
43
|
+
maxRequests: number;
|
|
44
|
+
};
|
|
45
|
+
usage: UsageLimits;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const PLANS: Record<ProductPlan, PlanConfig> = {
|
|
49
|
+
// NPM Plans
|
|
50
|
+
free: {
|
|
51
|
+
name: 'free',
|
|
52
|
+
price: 0,
|
|
53
|
+
billingPeriod: 'monthly',
|
|
54
|
+
productType: 'npm',
|
|
55
|
+
features: {
|
|
56
|
+
maxMemories: 5000,
|
|
57
|
+
maxSearchPerDay: 500,
|
|
58
|
+
maxContextPerDay: 200,
|
|
59
|
+
maxApiCallsPerDay: 5000,
|
|
60
|
+
maxStorageMB: 50,
|
|
61
|
+
embeddingDimension: 1536,
|
|
62
|
+
persistence: 'memory',
|
|
63
|
+
support: 'community',
|
|
64
|
+
customIntegrations: false,
|
|
65
|
+
apiKeys: 0,
|
|
66
|
+
teamMembers: 1,
|
|
67
|
+
cloudSync: false,
|
|
68
|
+
advancedSearch: false,
|
|
69
|
+
teamSharing: false,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
starter: {
|
|
73
|
+
name: 'starter',
|
|
74
|
+
price: 3,
|
|
75
|
+
billingPeriod: 'monthly',
|
|
76
|
+
productType: 'extension',
|
|
77
|
+
features: {
|
|
78
|
+
maxMemories: 30000,
|
|
79
|
+
maxSearchPerDay: 1000,
|
|
80
|
+
maxContextPerDay: 500,
|
|
81
|
+
maxApiCallsPerDay: 10000,
|
|
82
|
+
maxStorageMB: 100,
|
|
83
|
+
embeddingDimension: 1536,
|
|
84
|
+
persistence: 'memory',
|
|
85
|
+
support: 'community',
|
|
86
|
+
customIntegrations: false,
|
|
87
|
+
apiKeys: 1,
|
|
88
|
+
teamMembers: 1,
|
|
89
|
+
cloudSync: true,
|
|
90
|
+
advancedSearch: false,
|
|
91
|
+
teamSharing: false,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
pro: {
|
|
95
|
+
name: 'pro',
|
|
96
|
+
price: 29,
|
|
97
|
+
billingPeriod: 'monthly',
|
|
98
|
+
productType: 'npm',
|
|
99
|
+
features: {
|
|
100
|
+
maxMemories: 50000,
|
|
101
|
+
maxSearchPerDay: 10000,
|
|
102
|
+
maxContextPerDay: 5000,
|
|
103
|
+
maxApiCallsPerDay: 100000,
|
|
104
|
+
maxStorageMB: 500,
|
|
105
|
+
embeddingDimension: 1536,
|
|
106
|
+
persistence: 'redis',
|
|
107
|
+
support: 'email',
|
|
108
|
+
customIntegrations: false,
|
|
109
|
+
apiKeys: 5,
|
|
110
|
+
teamMembers: 5,
|
|
111
|
+
cloudSync: true,
|
|
112
|
+
advancedSearch: true,
|
|
113
|
+
teamSharing: false,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
plus: {
|
|
117
|
+
name: 'plus',
|
|
118
|
+
price: 9,
|
|
119
|
+
billingPeriod: 'monthly',
|
|
120
|
+
productType: 'extension',
|
|
121
|
+
features: {
|
|
122
|
+
maxMemories: 150000,
|
|
123
|
+
maxSearchPerDay: 5000,
|
|
124
|
+
maxContextPerDay: 2000,
|
|
125
|
+
maxApiCallsPerDay: 50000,
|
|
126
|
+
maxStorageMB: 250,
|
|
127
|
+
embeddingDimension: 1536,
|
|
128
|
+
persistence: 'memory',
|
|
129
|
+
support: 'email',
|
|
130
|
+
customIntegrations: false,
|
|
131
|
+
apiKeys: 2,
|
|
132
|
+
teamMembers: 2,
|
|
133
|
+
cloudSync: true,
|
|
134
|
+
advancedSearch: true,
|
|
135
|
+
teamSharing: false,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
premium: {
|
|
139
|
+
name: 'premium',
|
|
140
|
+
price: 29,
|
|
141
|
+
billingPeriod: 'monthly',
|
|
142
|
+
productType: 'extension',
|
|
143
|
+
features: {
|
|
144
|
+
maxMemories: -1,
|
|
145
|
+
maxSearchPerDay: -1,
|
|
146
|
+
maxContextPerDay: -1,
|
|
147
|
+
maxApiCallsPerDay: -1,
|
|
148
|
+
maxStorageMB: -1,
|
|
149
|
+
embeddingDimension: 3072,
|
|
150
|
+
persistence: 'redis',
|
|
151
|
+
support: 'priority',
|
|
152
|
+
customIntegrations: true,
|
|
153
|
+
apiKeys: 10,
|
|
154
|
+
teamMembers: 10,
|
|
155
|
+
cloudSync: true,
|
|
156
|
+
advancedSearch: true,
|
|
157
|
+
teamSharing: true,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
// Team Plans
|
|
161
|
+
team: {
|
|
162
|
+
name: 'team',
|
|
163
|
+
price: 149,
|
|
164
|
+
billingPeriod: 'monthly',
|
|
165
|
+
productType: 'team',
|
|
166
|
+
features: {
|
|
167
|
+
maxMemories: 500000,
|
|
168
|
+
maxSearchPerDay: 100000,
|
|
169
|
+
maxContextPerDay: 50000,
|
|
170
|
+
maxApiCallsPerDay: 1000000,
|
|
171
|
+
maxStorageMB: 5000,
|
|
172
|
+
embeddingDimension: 1536,
|
|
173
|
+
persistence: 'redis',
|
|
174
|
+
support: 'email',
|
|
175
|
+
customIntegrations: true,
|
|
176
|
+
apiKeys: 50,
|
|
177
|
+
teamMembers: 10,
|
|
178
|
+
cloudSync: true,
|
|
179
|
+
advancedSearch: true,
|
|
180
|
+
teamSharing: true,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
business: {
|
|
184
|
+
name: 'business',
|
|
185
|
+
price: 399,
|
|
186
|
+
billingPeriod: 'monthly',
|
|
187
|
+
productType: 'team',
|
|
188
|
+
features: {
|
|
189
|
+
maxMemories: 2000000,
|
|
190
|
+
maxSearchPerDay: 500000,
|
|
191
|
+
maxContextPerDay: 200000,
|
|
192
|
+
maxApiCallsPerDay: 5000000,
|
|
193
|
+
maxStorageMB: 20000,
|
|
194
|
+
embeddingDimension: 3072,
|
|
195
|
+
persistence: 'postgres',
|
|
196
|
+
support: 'priority',
|
|
197
|
+
customIntegrations: true,
|
|
198
|
+
apiKeys: 200,
|
|
199
|
+
teamMembers: 50,
|
|
200
|
+
cloudSync: true,
|
|
201
|
+
advancedSearch: true,
|
|
202
|
+
teamSharing: true,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
enterprise: {
|
|
206
|
+
name: 'enterprise',
|
|
207
|
+
price: 999,
|
|
208
|
+
billingPeriod: 'monthly',
|
|
209
|
+
productType: 'team',
|
|
210
|
+
features: {
|
|
211
|
+
maxMemories: -1,
|
|
212
|
+
maxSearchPerDay: -1,
|
|
213
|
+
maxContextPerDay: -1,
|
|
214
|
+
maxApiCallsPerDay: -1,
|
|
215
|
+
maxStorageMB: -1,
|
|
216
|
+
embeddingDimension: 3072,
|
|
217
|
+
persistence: 'postgres',
|
|
218
|
+
support: 'priority',
|
|
219
|
+
customIntegrations: true,
|
|
220
|
+
apiKeys: -1,
|
|
221
|
+
teamMembers: -1,
|
|
222
|
+
cloudSync: true,
|
|
223
|
+
advancedSearch: true,
|
|
224
|
+
teamSharing: true,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export function getPlanLimits(plan: ProductPlan): PlanLimits {
|
|
230
|
+
const limits = DEFAULT_RATE_LIMITS[plan] || DEFAULT_RATE_LIMITS.free;
|
|
231
|
+
const usage = DEFAULT_USAGE_LIMITS[plan] || DEFAULT_USAGE_LIMITS.free;
|
|
232
|
+
return {
|
|
233
|
+
rateLimit: limits,
|
|
234
|
+
usage: usage,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function getPlan(plan: ProductPlan): PlanConfig {
|
|
239
|
+
return PLANS[plan];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export class PlanManager {
|
|
243
|
+
private userPlans: Map<string, ProductPlan> = new Map();
|
|
244
|
+
private rateLimiters: Map<string, RateLimiter> = new Map();
|
|
245
|
+
private usageTrackers: Map<string, UsageTracker> = new Map();
|
|
246
|
+
private defaultPlan: ProductPlan = 'free';
|
|
247
|
+
|
|
248
|
+
setUserPlan(userId: string, plan: ProductPlan): void {
|
|
249
|
+
this.userPlans.set(userId, plan);
|
|
250
|
+
|
|
251
|
+
const limits = getPlanLimits(plan);
|
|
252
|
+
this.rateLimiters.set(userId, new RateLimiter(limits.rateLimit));
|
|
253
|
+
this.usageTrackers.set(userId, new UsageTracker(limits.usage));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getUserPlan(userId: string): ProductPlan {
|
|
257
|
+
return this.userPlans.get(userId) || this.defaultPlan;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getUserPlanConfig(userId: string): PlanConfig {
|
|
261
|
+
const plan = this.getUserPlan(userId);
|
|
262
|
+
return PLANS[plan];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
checkRateLimit(userId: string): Promise<RateLimitResult> {
|
|
266
|
+
let limiter = this.rateLimiters.get(userId);
|
|
267
|
+
|
|
268
|
+
if (!limiter) {
|
|
269
|
+
const plan = this.getUserPlan(userId);
|
|
270
|
+
const limits = getPlanLimits(plan);
|
|
271
|
+
limiter = new RateLimiter(limits.rateLimit);
|
|
272
|
+
this.rateLimiters.set(userId, limiter);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return limiter.check(userId);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async checkUsageLimit(userId: string, metric: 'memories_added' | 'memories_searched' | 'context_built' | 'api_calls'): Promise<UsageCheckResult> {
|
|
279
|
+
let tracker = this.usageTrackers.get(userId);
|
|
280
|
+
|
|
281
|
+
if (!tracker) {
|
|
282
|
+
const plan = this.getUserPlan(userId);
|
|
283
|
+
const limits = getPlanLimits(plan);
|
|
284
|
+
tracker = new UsageTracker(limits.usage);
|
|
285
|
+
this.usageTrackers.set(userId, tracker);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return tracker.checkLimit(userId, metric);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async recordUsage(userId: string, metric: 'memories_added' | 'memories_searched' | 'context_built' | 'api_calls'): Promise<UsageCheckResult> {
|
|
292
|
+
let tracker = this.usageTrackers.get(userId);
|
|
293
|
+
|
|
294
|
+
if (!tracker) {
|
|
295
|
+
const plan = this.getUserPlan(userId);
|
|
296
|
+
const limits = getPlanLimits(plan);
|
|
297
|
+
tracker = new UsageTracker(limits.usage);
|
|
298
|
+
this.usageTrackers.set(userId, tracker);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return tracker.recordUsage(userId, metric);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async getUsageSummary(userId: string, period: 'daily' | 'monthly' | 'yearly') {
|
|
305
|
+
let tracker = this.usageTrackers.get(userId);
|
|
306
|
+
|
|
307
|
+
if (!tracker) {
|
|
308
|
+
const plan = this.getUserPlan(userId);
|
|
309
|
+
const limits = getPlanLimits(plan);
|
|
310
|
+
tracker = new UsageTracker(limits.usage);
|
|
311
|
+
this.usageTrackers.set(userId, tracker);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return tracker.getUsageSummary(userId, period);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
removeUser(userId: string): void {
|
|
318
|
+
this.userPlans.delete(userId);
|
|
319
|
+
this.rateLimiters.delete(userId);
|
|
320
|
+
this.usageTrackers.delete(userId);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
setDefaultPlan(plan: ProductPlan): void {
|
|
324
|
+
this.defaultPlan = plan;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
getAllPlans(): PlanConfig[] {
|
|
328
|
+
return Object.values(PLANS);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
getPlansByType(type: ProductType): PlanConfig[] {
|
|
332
|
+
return Object.values(PLANS).filter(p => p.productType === type);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
getPlanByName(name: string): PlanConfig | null {
|
|
336
|
+
return PLANS[name as ProductPlan] || null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export class AIMemoryPro {
|
|
341
|
+
private planManager: PlanManager;
|
|
342
|
+
private config: AIMemoryConfig;
|
|
343
|
+
private embeddingFunction: EmbeddingFunction | null = null;
|
|
344
|
+
|
|
345
|
+
constructor(config?: Partial<AIMemoryConfig>, planManager?: PlanManager) {
|
|
346
|
+
this.config = validateConfig(config || {});
|
|
347
|
+
this.planManager = planManager || new PlanManager();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
setEmbeddingFunction(fn: EmbeddingFunction): void {
|
|
351
|
+
this.embeddingFunction = fn;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
setUserPlan(userId: string, plan: ProductPlan): void {
|
|
355
|
+
this.planManager.setUserPlan(userId, plan);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async checkAccess(userId: string): Promise<{ allowed: boolean; reason?: string }> {
|
|
359
|
+
const rateResult = await this.planManager.checkRateLimit(userId);
|
|
360
|
+
|
|
361
|
+
if (!rateResult.allowed) {
|
|
362
|
+
return {
|
|
363
|
+
allowed: false,
|
|
364
|
+
reason: `Rate limit exceeded. Retry after ${rateResult.retryAfter} seconds.`,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const usageResult = await this.planManager.checkUsageLimit(userId, 'api_calls');
|
|
369
|
+
|
|
370
|
+
if (!usageResult.allowed) {
|
|
371
|
+
return {
|
|
372
|
+
allowed: false,
|
|
373
|
+
reason: `API limit exceeded. Upgrade to Pro for more calls.`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return { allowed: true };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async addMemory(
|
|
381
|
+
userId: string,
|
|
382
|
+
content: string,
|
|
383
|
+
metadata?: Partial<MemoryMetadata>
|
|
384
|
+
): Promise<Memory | { error: string }> {
|
|
385
|
+
const access = await this.checkAccess(userId);
|
|
386
|
+
|
|
387
|
+
if (!access.allowed) {
|
|
388
|
+
return { error: access.reason! };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const usageCheck = await this.planManager.checkUsageLimit(userId, 'memories_added');
|
|
392
|
+
|
|
393
|
+
if (!usageCheck.allowed) {
|
|
394
|
+
return { error: 'Memory limit reached. Upgrade your plan.' };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
await this.planManager.recordUsage(userId, 'api_calls');
|
|
398
|
+
|
|
399
|
+
return { error: 'Not implemented - requires memory manager' };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
getPlanManager(): PlanManager {
|
|
403
|
+
return this.planManager;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
getPlanConfig(userId: string) {
|
|
407
|
+
return this.planManager.getUserPlanConfig(userId);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function createPlanManager(): PlanManager {
|
|
412
|
+
return new PlanManager();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function getAvailablePlans(): PlanConfig[] {
|
|
416
|
+
return Object.values(PLANS);
|
|
417
|
+
}
|