@tpsdev-ai/flair 0.3.19 → 0.4.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/README.md +19 -6
- package/config.yaml +4 -2
- package/dist/cli.js +567 -60
- package/dist/resources/Memory.js +52 -3
- package/dist/resources/MemoryBootstrap.js +7 -1
- package/dist/resources/SemanticSearch.js +129 -56
- package/dist/resources/auth-middleware.js +37 -10
- package/dist/resources/content-safety.js +62 -0
- package/dist/resources/embeddings-provider.js +86 -7
- package/dist/resources/rate-limiter.js +118 -0
- package/package.json +2 -2
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rate-limiter.ts
|
|
3
|
+
*
|
|
4
|
+
* In-memory sliding window rate limiter for Flair endpoints.
|
|
5
|
+
* Configurable via environment variables:
|
|
6
|
+
*
|
|
7
|
+
* FLAIR_RATE_LIMIT_RPM — requests per minute per agent (default: 120)
|
|
8
|
+
* FLAIR_RATE_LIMIT_EMBED — embedding requests per minute per agent (default: 30)
|
|
9
|
+
* FLAIR_RATE_LIMIT_STORAGE — max memories per agent (default: 10000)
|
|
10
|
+
* FLAIR_RATE_LIMIT_ENABLED — "true" to enable (default: disabled for local, enabled when FLAIR_PUBLIC=true)
|
|
11
|
+
*/
|
|
12
|
+
const windows = new Map();
|
|
13
|
+
const WINDOW_MS = 60_000; // 1 minute sliding window
|
|
14
|
+
const CLEANUP_INTERVAL_MS = 300_000; // Clean stale entries every 5 minutes
|
|
15
|
+
// Default limits (generous for local use)
|
|
16
|
+
function getLimit(envVar, defaultVal) {
|
|
17
|
+
const val = process.env[envVar];
|
|
18
|
+
return val ? Number(val) : defaultVal;
|
|
19
|
+
}
|
|
20
|
+
function isEnabled() {
|
|
21
|
+
if (process.env.FLAIR_RATE_LIMIT_ENABLED === "true")
|
|
22
|
+
return true;
|
|
23
|
+
if (process.env.FLAIR_PUBLIC === "true")
|
|
24
|
+
return true;
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if an agent has exceeded their rate limit for a given bucket.
|
|
29
|
+
* Returns { allowed: true } or { allowed: false, retryAfterMs }.
|
|
30
|
+
*/
|
|
31
|
+
export function checkRateLimit(agentId, bucket = "general") {
|
|
32
|
+
if (!isEnabled())
|
|
33
|
+
return { allowed: true };
|
|
34
|
+
if (!agentId)
|
|
35
|
+
return { allowed: true }; // admin/internal calls
|
|
36
|
+
const limit = bucket === "embedding"
|
|
37
|
+
? getLimit("FLAIR_RATE_LIMIT_EMBED", 30)
|
|
38
|
+
: getLimit("FLAIR_RATE_LIMIT_RPM", 120);
|
|
39
|
+
const key = `${agentId}:${bucket}`;
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
const cutoff = now - WINDOW_MS;
|
|
42
|
+
let entry = windows.get(key);
|
|
43
|
+
if (!entry) {
|
|
44
|
+
entry = { timestamps: [] };
|
|
45
|
+
windows.set(key, entry);
|
|
46
|
+
}
|
|
47
|
+
// Prune old timestamps
|
|
48
|
+
entry.timestamps = entry.timestamps.filter(t => t > cutoff);
|
|
49
|
+
if (entry.timestamps.length >= limit) {
|
|
50
|
+
// Find when the oldest will expire
|
|
51
|
+
const oldest = entry.timestamps[0];
|
|
52
|
+
const retryAfterMs = oldest + WINDOW_MS - now;
|
|
53
|
+
return {
|
|
54
|
+
allowed: false,
|
|
55
|
+
retryAfterMs: Math.max(retryAfterMs, 1000),
|
|
56
|
+
remaining: 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
entry.timestamps.push(now);
|
|
60
|
+
return {
|
|
61
|
+
allowed: true,
|
|
62
|
+
remaining: limit - entry.timestamps.length,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if an agent has exceeded their storage quota.
|
|
67
|
+
* Returns { allowed: true } or { allowed: false }.
|
|
68
|
+
*/
|
|
69
|
+
export function checkStorageQuota(currentCount) {
|
|
70
|
+
if (!isEnabled())
|
|
71
|
+
return { allowed: true, limit: Infinity };
|
|
72
|
+
const limit = getLimit("FLAIR_RATE_LIMIT_STORAGE", 10000);
|
|
73
|
+
return {
|
|
74
|
+
allowed: currentCount < limit,
|
|
75
|
+
limit,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build a 429 Too Many Requests response.
|
|
80
|
+
*/
|
|
81
|
+
export function rateLimitResponse(retryAfterMs, bucket) {
|
|
82
|
+
const retryAfterSec = Math.ceil(retryAfterMs / 1000);
|
|
83
|
+
return new Response(JSON.stringify({
|
|
84
|
+
error: "rate_limit_exceeded",
|
|
85
|
+
message: `Rate limit exceeded for ${bucket} requests. Retry after ${retryAfterSec}s.`,
|
|
86
|
+
retryAfterMs,
|
|
87
|
+
}), {
|
|
88
|
+
status: 429,
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
"Retry-After": String(retryAfterSec),
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build a 413 Payload Too Large response for storage quota.
|
|
97
|
+
*/
|
|
98
|
+
export function storageQuotaResponse(limit) {
|
|
99
|
+
return new Response(JSON.stringify({
|
|
100
|
+
error: "storage_quota_exceeded",
|
|
101
|
+
message: `Storage quota exceeded. Maximum ${limit} memories per agent.`,
|
|
102
|
+
limit,
|
|
103
|
+
}), {
|
|
104
|
+
status: 413,
|
|
105
|
+
headers: { "Content-Type": "application/json" },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Periodic cleanup of stale window entries
|
|
109
|
+
if (typeof setInterval !== "undefined") {
|
|
110
|
+
setInterval(() => {
|
|
111
|
+
const cutoff = Date.now() - WINDOW_MS * 2;
|
|
112
|
+
for (const [key, entry] of windows) {
|
|
113
|
+
entry.timestamps = entry.timestamps.filter(t => t > cutoff);
|
|
114
|
+
if (entry.timestamps.length === 0)
|
|
115
|
+
windows.delete(key);
|
|
116
|
+
}
|
|
117
|
+
}, CLEANUP_INTERVAL_MS).unref?.();
|
|
118
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpsdev-ai/flair",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=22"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@harperfast/harper": "5.0.0-beta.
|
|
51
|
+
"@harperfast/harper": "5.0.0-beta.7",
|
|
52
52
|
"commander": "14.0.3",
|
|
53
53
|
"harper-fabric-embeddings": "^0.2.2",
|
|
54
54
|
"tweetnacl": "1.0.3"
|