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,186 @@
|
|
|
1
|
+
export interface RateLimitConfig {
|
|
2
|
+
windowMs: number;
|
|
3
|
+
maxRequests: number;
|
|
4
|
+
keyPrefix?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RateLimitResult {
|
|
8
|
+
allowed: boolean;
|
|
9
|
+
remaining: number;
|
|
10
|
+
resetTime: number;
|
|
11
|
+
retryAfter?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RateLimitStore {
|
|
15
|
+
get(key: string): Promise<{ count: number; resetTime: number } | null>;
|
|
16
|
+
set(key: string, count: number, windowMs: number, resetTime: number): Promise<void>;
|
|
17
|
+
increment(key: string): Promise<{ count: number; resetTime: number }>;
|
|
18
|
+
delete(key: string): Promise<void>;
|
|
19
|
+
reset(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class InMemoryRateLimitStore implements RateLimitStore {
|
|
23
|
+
private store: Map<string, { count: number; resetTime: number }> = new Map();
|
|
24
|
+
|
|
25
|
+
async get(key: string): Promise<{ count: number; resetTime: number } | null> {
|
|
26
|
+
const entry = this.store.get(key);
|
|
27
|
+
if (!entry) return null;
|
|
28
|
+
|
|
29
|
+
if (Date.now() > entry.resetTime) {
|
|
30
|
+
this.store.delete(key);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async set(key: string, count: number, _windowMs: number, resetTime: number): Promise<void> {
|
|
38
|
+
this.store.set(key, { count, resetTime });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async increment(key: string): Promise<{ count: number; resetTime: number }> {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const entry = this.store.get(key);
|
|
44
|
+
|
|
45
|
+
if (!entry || now > entry.resetTime) {
|
|
46
|
+
const resetTime = now + 60000;
|
|
47
|
+
this.store.set(key, { count: 1, resetTime });
|
|
48
|
+
return { count: 1, resetTime };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const newCount = entry.count + 1;
|
|
52
|
+
this.store.set(key, { count: newCount, resetTime: entry.resetTime });
|
|
53
|
+
return { count: newCount, resetTime: entry.resetTime };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async delete(key: string): Promise<void> {
|
|
57
|
+
this.store.delete(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async reset(): Promise<void> {
|
|
61
|
+
this.store.clear();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class RateLimiter {
|
|
66
|
+
private store: RateLimitStore;
|
|
67
|
+
private config: RateLimitConfig;
|
|
68
|
+
|
|
69
|
+
constructor(config: RateLimitConfig, store?: RateLimitStore) {
|
|
70
|
+
this.config = config;
|
|
71
|
+
this.store = store || new InMemoryRateLimitStore();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async check(identifier: string): Promise<RateLimitResult> {
|
|
75
|
+
const key = `${this.config.keyPrefix || 'ratelimit'}:${identifier}`;
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
|
|
78
|
+
const entry = await this.store.get(key);
|
|
79
|
+
|
|
80
|
+
if (!entry) {
|
|
81
|
+
const resetTime = now + this.config.windowMs;
|
|
82
|
+
await this.store.set(key, 1, this.config.windowMs, resetTime);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
allowed: true,
|
|
86
|
+
remaining: this.config.maxRequests - 1,
|
|
87
|
+
resetTime,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (now >= entry.resetTime) {
|
|
92
|
+
const resetTime = now + this.config.windowMs;
|
|
93
|
+
await this.store.set(key, 1, this.config.windowMs, resetTime);
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
allowed: true,
|
|
97
|
+
remaining: this.config.maxRequests - 1,
|
|
98
|
+
resetTime,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (entry.count >= this.config.maxRequests) {
|
|
103
|
+
const retryAfter = Math.ceil((entry.resetTime - now) / 1000);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
allowed: false,
|
|
107
|
+
remaining: 0,
|
|
108
|
+
resetTime: entry.resetTime,
|
|
109
|
+
retryAfter,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const newEntry = await this.store.increment(key);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
allowed: true,
|
|
117
|
+
remaining: this.config.maxRequests - newEntry.count,
|
|
118
|
+
resetTime: newEntry.resetTime,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async reset(identifier: string): Promise<void> {
|
|
123
|
+
const key = `${this.config.keyPrefix || 'ratelimit'}:${identifier}`;
|
|
124
|
+
await this.store.delete(key);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
updateConfig(config: Partial<RateLimitConfig>): void {
|
|
128
|
+
this.config = { ...this.config, ...config };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const DEFAULT_RATE_LIMITS: Record<string, RateLimitConfig> = {
|
|
133
|
+
// NPM & Extension Free
|
|
134
|
+
free: {
|
|
135
|
+
windowMs: 60 * 1000,
|
|
136
|
+
maxRequests: 100,
|
|
137
|
+
keyPrefix: 'free',
|
|
138
|
+
},
|
|
139
|
+
// Extension Starter
|
|
140
|
+
starter: {
|
|
141
|
+
windowMs: 60 * 1000,
|
|
142
|
+
maxRequests: 200,
|
|
143
|
+
keyPrefix: 'starter',
|
|
144
|
+
},
|
|
145
|
+
// NPM Pro
|
|
146
|
+
pro: {
|
|
147
|
+
windowMs: 60 * 1000,
|
|
148
|
+
maxRequests: 500,
|
|
149
|
+
keyPrefix: 'pro',
|
|
150
|
+
},
|
|
151
|
+
// Extension Plus
|
|
152
|
+
plus: {
|
|
153
|
+
windowMs: 60 * 1000,
|
|
154
|
+
maxRequests: 300,
|
|
155
|
+
keyPrefix: 'plus',
|
|
156
|
+
},
|
|
157
|
+
// Extension Premium
|
|
158
|
+
premium: {
|
|
159
|
+
windowMs: 60 * 1000,
|
|
160
|
+
maxRequests: 1000,
|
|
161
|
+
keyPrefix: 'premium',
|
|
162
|
+
},
|
|
163
|
+
// Team
|
|
164
|
+
team: {
|
|
165
|
+
windowMs: 60 * 1000,
|
|
166
|
+
maxRequests: 2000,
|
|
167
|
+
keyPrefix: 'team',
|
|
168
|
+
},
|
|
169
|
+
// Business
|
|
170
|
+
business: {
|
|
171
|
+
windowMs: 60 * 1000,
|
|
172
|
+
maxRequests: 5000,
|
|
173
|
+
keyPrefix: 'business',
|
|
174
|
+
},
|
|
175
|
+
// Enterprise
|
|
176
|
+
enterprise: {
|
|
177
|
+
windowMs: 60 * 1000,
|
|
178
|
+
maxRequests: 10000,
|
|
179
|
+
keyPrefix: 'enterprise',
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export function createRateLimiter(plan: string = 'free', store?: RateLimitStore): RateLimiter {
|
|
184
|
+
const config = DEFAULT_RATE_LIMITS[plan] || DEFAULT_RATE_LIMITS.free;
|
|
185
|
+
return new RateLimiter(config, store);
|
|
186
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { UsageStore, UsageRecord, UsageMetric } from '../usageTracker';
|
|
2
|
+
|
|
3
|
+
export interface PostgresConfig {
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
database?: string;
|
|
7
|
+
user?: string;
|
|
8
|
+
password?: string;
|
|
9
|
+
connectionString?: string;
|
|
10
|
+
max?: number;
|
|
11
|
+
idleTimeoutMillis?: number;
|
|
12
|
+
connectionTimeoutMillis?: number;
|
|
13
|
+
ssl?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class PostgresUsageStore implements UsageStore {
|
|
17
|
+
private pool: any;
|
|
18
|
+
private initialized: boolean = false;
|
|
19
|
+
|
|
20
|
+
constructor(private config: PostgresConfig) {}
|
|
21
|
+
|
|
22
|
+
private async getPool(): Promise<any> {
|
|
23
|
+
if (!this.initialized) {
|
|
24
|
+
try {
|
|
25
|
+
const { default: pg } = await import('pg');
|
|
26
|
+
const { Pool } = pg;
|
|
27
|
+
|
|
28
|
+
this.pool = new Pool({
|
|
29
|
+
host: this.config.host || 'localhost',
|
|
30
|
+
port: this.config.port || 5432,
|
|
31
|
+
database: this.config.database || 'ai_brain',
|
|
32
|
+
user: this.config.user || 'postgres',
|
|
33
|
+
password: this.config.password,
|
|
34
|
+
connectionString: this.config.connectionString,
|
|
35
|
+
max: this.config.max || 20,
|
|
36
|
+
idleTimeoutMillis: this.config.idleTimeoutMillis || 30000,
|
|
37
|
+
connectionTimeoutMillis: this.config.connectionTimeoutMillis || 5000,
|
|
38
|
+
ssl: this.config.ssl ? { rejectUnauthorized: false } : false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await this.initialize();
|
|
42
|
+
this.initialized = true;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Failed to initialize PostgreSQL:', error);
|
|
45
|
+
throw new Error('PostgreSQL not available');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return this.pool;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async initialize(): Promise<void> {
|
|
52
|
+
const pool = this.pool;
|
|
53
|
+
|
|
54
|
+
await pool.query(`
|
|
55
|
+
CREATE TABLE IF NOT EXISTS usage_records (
|
|
56
|
+
id SERIAL PRIMARY KEY,
|
|
57
|
+
user_id VARCHAR(255) NOT NULL,
|
|
58
|
+
date DATE NOT NULL,
|
|
59
|
+
memories_added INTEGER DEFAULT 0,
|
|
60
|
+
memories_searched INTEGER DEFAULT 0,
|
|
61
|
+
context_built INTEGER DEFAULT 0,
|
|
62
|
+
api_calls INTEGER DEFAULT 0,
|
|
63
|
+
storage_used INTEGER DEFAULT 0,
|
|
64
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
65
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
66
|
+
UNIQUE(user_id, date)
|
|
67
|
+
);
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
await pool.query(`
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_usage_user_date
|
|
72
|
+
ON usage_records(user_id, date);
|
|
73
|
+
`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async get(userId: string, date: string): Promise<UsageRecord | null> {
|
|
77
|
+
try {
|
|
78
|
+
const pool = await this.getPool();
|
|
79
|
+
const result = await pool.query(
|
|
80
|
+
'SELECT * FROM usage_records WHERE user_id = $1 AND date = $2',
|
|
81
|
+
[userId, date]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (result.rows.length === 0) return null;
|
|
85
|
+
|
|
86
|
+
const row = result.rows[0];
|
|
87
|
+
return {
|
|
88
|
+
userId: row.user_id,
|
|
89
|
+
date: row.date.toISOString().split('T')[0],
|
|
90
|
+
metrics: {
|
|
91
|
+
memories_added: row.memories_added,
|
|
92
|
+
memories_searched: row.memories_searched,
|
|
93
|
+
context_built: row.context_built,
|
|
94
|
+
api_calls: row.api_calls,
|
|
95
|
+
storage_used: row.storage_used,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async set(userId: string, date: string, record: UsageRecord): Promise<void> {
|
|
104
|
+
try {
|
|
105
|
+
const pool = await this.getPool();
|
|
106
|
+
await pool.query(
|
|
107
|
+
`INSERT INTO usage_records (user_id, date, memories_added, memories_searched, context_built, api_calls, storage_used)
|
|
108
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
109
|
+
ON CONFLICT (user_id, date) DO UPDATE SET
|
|
110
|
+
memories_added = EXCLUDED.memories_added,
|
|
111
|
+
memories_searched = EXCLUDED.memories_searched,
|
|
112
|
+
context_built = EXCLUDED.context_built,
|
|
113
|
+
api_calls = EXCLUDED.api_calls,
|
|
114
|
+
storage_used = EXCLUDED.storage_used,
|
|
115
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
116
|
+
[
|
|
117
|
+
userId,
|
|
118
|
+
date,
|
|
119
|
+
record.metrics.memories_added,
|
|
120
|
+
record.metrics.memories_searched,
|
|
121
|
+
record.metrics.context_built,
|
|
122
|
+
record.metrics.api_calls,
|
|
123
|
+
record.metrics.storage_used,
|
|
124
|
+
]
|
|
125
|
+
);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('PostgreSQL set error:', error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async increment(userId: string, metric: UsageMetric, amount: number = 1): Promise<void> {
|
|
132
|
+
const today = new Date().toISOString().split('T')[0];
|
|
133
|
+
const metricColumn = metric;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const pool = await this.getPool();
|
|
137
|
+
await pool.query(
|
|
138
|
+
`INSERT INTO usage_records (user_id, date, ${metricColumn})
|
|
139
|
+
VALUES ($1, $2, $3)
|
|
140
|
+
ON CONFLICT (user_id, date) DO UPDATE SET
|
|
141
|
+
${metricColumn} = usage_records.${metricColumn} + EXCLUDED.${metricColumn},
|
|
142
|
+
updated_at = CURRENT_TIMESTAMP`,
|
|
143
|
+
[userId, today, amount]
|
|
144
|
+
);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('PostgreSQL increment error:', error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async getRange(userId: string, startDate: string, endDate: string): Promise<UsageRecord[]> {
|
|
151
|
+
try {
|
|
152
|
+
const pool = await this.getPool();
|
|
153
|
+
const result = await pool.query(
|
|
154
|
+
'SELECT * FROM usage_records WHERE user_id = $1 AND date >= $2 AND date <= $3 ORDER BY date',
|
|
155
|
+
[userId, startDate, endDate]
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return result.rows.map((row: any) => ({
|
|
159
|
+
userId: row.user_id,
|
|
160
|
+
date: row.date.toISOString().split('T')[0],
|
|
161
|
+
metrics: {
|
|
162
|
+
memories_added: row.memories_added,
|
|
163
|
+
memories_searched: row.memories_searched,
|
|
164
|
+
context_built: row.context_built,
|
|
165
|
+
api_calls: row.api_calls,
|
|
166
|
+
storage_used: row.storage_used,
|
|
167
|
+
},
|
|
168
|
+
}));
|
|
169
|
+
} catch {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async reset(userId: string, date: string): Promise<void> {
|
|
175
|
+
try {
|
|
176
|
+
const pool = await this.getPool();
|
|
177
|
+
await pool.query(
|
|
178
|
+
'DELETE FROM usage_records WHERE user_id = $1 AND date = $2',
|
|
179
|
+
[userId, date]
|
|
180
|
+
);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error('PostgreSQL reset error:', error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async deleteUser(userId: string): Promise<void> {
|
|
187
|
+
try {
|
|
188
|
+
const pool = await this.getPool();
|
|
189
|
+
await pool.query('DELETE FROM usage_records WHERE user_id = $1', [userId]);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error('PostgreSQL deleteUser error:', error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async disconnect(): Promise<void> {
|
|
196
|
+
if (this.pool) {
|
|
197
|
+
await this.pool.end();
|
|
198
|
+
this.initialized = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
isInitialized(): boolean {
|
|
203
|
+
return this.initialized;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createPostgresUsageStore(config: PostgresConfig): PostgresUsageStore {
|
|
208
|
+
return new PostgresUsageStore(config);
|
|
209
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { RateLimitStore } from '../rateLimiter';
|
|
2
|
+
|
|
3
|
+
export interface RedisConfig {
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
url?: string;
|
|
7
|
+
password?: string;
|
|
8
|
+
db?: number;
|
|
9
|
+
keyPrefix?: string;
|
|
10
|
+
connectionTimeout?: number;
|
|
11
|
+
maxRetriesPerRequest?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class RedisRateLimitStore implements RateLimitStore {
|
|
15
|
+
private redis: any;
|
|
16
|
+
private keyPrefix: string;
|
|
17
|
+
private connected: boolean = false;
|
|
18
|
+
|
|
19
|
+
constructor(private config: RedisConfig) {
|
|
20
|
+
this.keyPrefix = config.keyPrefix || 'ai-memory:ratelimit:';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private async getClient(): Promise<any> {
|
|
24
|
+
if (!this.connected) {
|
|
25
|
+
try {
|
|
26
|
+
const Redis = (await import('ioredis')).default;
|
|
27
|
+
this.redis = new Redis({
|
|
28
|
+
host: this.config.host || 'localhost',
|
|
29
|
+
port: this.config.port || 6379,
|
|
30
|
+
password: this.config.password,
|
|
31
|
+
db: this.config.db || 0,
|
|
32
|
+
lazyConnect: true,
|
|
33
|
+
});
|
|
34
|
+
await this.redis.connect();
|
|
35
|
+
this.connected = true;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Failed to connect to Redis:', error);
|
|
38
|
+
throw new Error('Redis not available');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return this.redis;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async get(key: string): Promise<{ count: number; resetTime: number } | null> {
|
|
45
|
+
try {
|
|
46
|
+
const client = await this.getClient();
|
|
47
|
+
const data = await client.get(this.keyPrefix + key);
|
|
48
|
+
if (!data) return null;
|
|
49
|
+
return JSON.parse(data);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async set(key: string, count: number, windowMs: number, resetTime: number): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
const client = await this.getClient();
|
|
58
|
+
const data = JSON.stringify({ count, resetTime });
|
|
59
|
+
const ttlSeconds = Math.ceil(windowMs / 1000);
|
|
60
|
+
await client.setex(this.keyPrefix + key, ttlSeconds, data);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Redis set error:', error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async increment(key: string): Promise<{ count: number; resetTime: number }> {
|
|
67
|
+
const client = await this.getClient();
|
|
68
|
+
const fullKey = this.keyPrefix + key;
|
|
69
|
+
|
|
70
|
+
const existing = await this.get(key);
|
|
71
|
+
if (!existing) {
|
|
72
|
+
const resetTime = Date.now() + 60000;
|
|
73
|
+
await this.set(key, 1, 60000, resetTime);
|
|
74
|
+
return { count: 1, resetTime };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const newCount = existing.count + 1;
|
|
78
|
+
await this.set(key, newCount, 60000, existing.resetTime);
|
|
79
|
+
return { count: newCount, resetTime: existing.resetTime };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async delete(key: string): Promise<void> {
|
|
83
|
+
try {
|
|
84
|
+
const client = await this.getClient();
|
|
85
|
+
await client.del(this.keyPrefix + key);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Redis delete error:', error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async reset(): Promise<void> {
|
|
92
|
+
try {
|
|
93
|
+
const client = await this.getClient();
|
|
94
|
+
const keys = await client.keys(this.keyPrefix + '*');
|
|
95
|
+
if (keys.length > 0) {
|
|
96
|
+
await client.del(...keys);
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Redis reset error:', error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async disconnect(): Promise<void> {
|
|
104
|
+
if (this.redis && this.connected) {
|
|
105
|
+
await this.redis.quit();
|
|
106
|
+
this.connected = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
isConnected(): boolean {
|
|
111
|
+
return this.connected;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createRedisRateLimiter(config: RedisConfig): RedisRateLimitStore {
|
|
116
|
+
return new RedisRateLimitStore(config);
|
|
117
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export interface Memory {
|
|
2
|
+
id: string;
|
|
3
|
+
content: string;
|
|
4
|
+
embedding?: number[];
|
|
5
|
+
timestamp: number;
|
|
6
|
+
metadata: MemoryMetadata;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MemoryMetadata {
|
|
10
|
+
type: MemoryType;
|
|
11
|
+
importance: number;
|
|
12
|
+
tags: string[];
|
|
13
|
+
userId?: string;
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
source?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type MemoryType =
|
|
20
|
+
| 'conversation'
|
|
21
|
+
| 'fact'
|
|
22
|
+
| 'preference'
|
|
23
|
+
| 'instruction'
|
|
24
|
+
| 'context'
|
|
25
|
+
| 'custom';
|
|
26
|
+
|
|
27
|
+
export interface SearchResult {
|
|
28
|
+
memory: Memory;
|
|
29
|
+
score: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MemoryQuery {
|
|
33
|
+
text?: string;
|
|
34
|
+
embedding?: number[];
|
|
35
|
+
filter?: MemoryFilter;
|
|
36
|
+
limit?: number;
|
|
37
|
+
threshold?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MemoryFilter {
|
|
41
|
+
types?: MemoryType[];
|
|
42
|
+
tags?: string[];
|
|
43
|
+
userId?: string;
|
|
44
|
+
sessionId?: string;
|
|
45
|
+
startDate?: number;
|
|
46
|
+
endDate?: number;
|
|
47
|
+
minImportance?: number;
|
|
48
|
+
maxImportance?: number;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MemoryStats {
|
|
53
|
+
totalMemories: number;
|
|
54
|
+
memoriesByType: Record<MemoryType, number>;
|
|
55
|
+
memoriesByTag: Record<string, number>;
|
|
56
|
+
oldestMemory: number;
|
|
57
|
+
newestMemory: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ContextConfig {
|
|
61
|
+
maxTokens: number;
|
|
62
|
+
includeSystemPrompt: boolean;
|
|
63
|
+
systemPrompt?: string;
|
|
64
|
+
relevanceThreshold: number;
|
|
65
|
+
includeMetadata: boolean;
|
|
66
|
+
memoryTypes: MemoryType[];
|
|
67
|
+
maxMemories: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ContextMessage {
|
|
71
|
+
role: 'system' | 'user' | 'assistant';
|
|
72
|
+
content: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ContextResult {
|
|
76
|
+
messages: ContextMessage[];
|
|
77
|
+
memories: Memory[];
|
|
78
|
+
stats: {
|
|
79
|
+
memoriesUsed: number;
|
|
80
|
+
tokensEstimated: number;
|
|
81
|
+
truncationApplied: boolean;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface EmbeddingFunction {
|
|
86
|
+
(text: string): Promise<number[]>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface StorageAdapter {
|
|
90
|
+
save(key: string, data: unknown): Promise<void>;
|
|
91
|
+
load(key: string): Promise<unknown | null>;
|
|
92
|
+
delete(key: string): Promise<void>;
|
|
93
|
+
exists(key: string): Promise<boolean>;
|
|
94
|
+
list(prefix: string): Promise<string[]>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface VectorStoreAdapter {
|
|
98
|
+
add(vectors: VectorEntry[]): Promise<void>;
|
|
99
|
+
search(query: number[], options: SearchOptions): Promise<SearchResult[]>;
|
|
100
|
+
delete(ids: string[]): Promise<void>;
|
|
101
|
+
getById(id: string): Promise<VectorEntry | null>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface VectorEntry {
|
|
105
|
+
id: string;
|
|
106
|
+
vector: number[];
|
|
107
|
+
metadata: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface SearchOptions {
|
|
111
|
+
limit: number;
|
|
112
|
+
threshold?: number;
|
|
113
|
+
filter?: Record<string, unknown>;
|
|
114
|
+
}
|