ai-memory-layer 2.0.0
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/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/bin/memory-server.mjs +157 -0
- package/dist/adapters/memory/embeddings.d.ts +4 -0
- package/dist/adapters/memory/embeddings.d.ts.map +1 -0
- package/dist/adapters/memory/embeddings.js +53 -0
- package/dist/adapters/memory/embeddings.js.map +1 -0
- package/dist/adapters/memory/index.d.ts +7 -0
- package/dist/adapters/memory/index.d.ts.map +1 -0
- package/dist/adapters/memory/index.js +650 -0
- package/dist/adapters/memory/index.js.map +1 -0
- package/dist/adapters/postgres/index.d.ts +38 -0
- package/dist/adapters/postgres/index.d.ts.map +1 -0
- package/dist/adapters/postgres/index.js +982 -0
- package/dist/adapters/postgres/index.js.map +1 -0
- package/dist/adapters/sqlite/embeddings.d.ts +5 -0
- package/dist/adapters/sqlite/embeddings.d.ts.map +1 -0
- package/dist/adapters/sqlite/embeddings.js +122 -0
- package/dist/adapters/sqlite/embeddings.js.map +1 -0
- package/dist/adapters/sqlite/index.d.ts +8 -0
- package/dist/adapters/sqlite/index.d.ts.map +1 -0
- package/dist/adapters/sqlite/index.js +839 -0
- package/dist/adapters/sqlite/index.js.map +1 -0
- package/dist/adapters/sqlite/mappers.d.ts +40 -0
- package/dist/adapters/sqlite/mappers.d.ts.map +1 -0
- package/dist/adapters/sqlite/mappers.js +95 -0
- package/dist/adapters/sqlite/mappers.js.map +1 -0
- package/dist/adapters/sqlite/schema.d.ts +4 -0
- package/dist/adapters/sqlite/schema.d.ts.map +1 -0
- package/dist/adapters/sqlite/schema.js +394 -0
- package/dist/adapters/sqlite/schema.js.map +1 -0
- package/dist/adapters/sync-to-async.d.ts +15 -0
- package/dist/adapters/sync-to-async.d.ts.map +1 -0
- package/dist/adapters/sync-to-async.js +95 -0
- package/dist/adapters/sync-to-async.js.map +1 -0
- package/dist/cli/inspect.d.ts +34 -0
- package/dist/cli/inspect.d.ts.map +1 -0
- package/dist/cli/inspect.js +190 -0
- package/dist/cli/inspect.js.map +1 -0
- package/dist/contracts/async-storage.d.ts +86 -0
- package/dist/contracts/async-storage.d.ts.map +1 -0
- package/dist/contracts/async-storage.js +2 -0
- package/dist/contracts/async-storage.js.map +1 -0
- package/dist/contracts/embedding.d.ts +22 -0
- package/dist/contracts/embedding.d.ts.map +1 -0
- package/dist/contracts/embedding.js +2 -0
- package/dist/contracts/embedding.js.map +1 -0
- package/dist/contracts/identity.d.ts +29 -0
- package/dist/contracts/identity.d.ts.map +1 -0
- package/dist/contracts/identity.js +34 -0
- package/dist/contracts/identity.js.map +1 -0
- package/dist/contracts/observability.d.ts +18 -0
- package/dist/contracts/observability.d.ts.map +1 -0
- package/dist/contracts/observability.js +7 -0
- package/dist/contracts/observability.js.map +1 -0
- package/dist/contracts/policy.d.ts +108 -0
- package/dist/contracts/policy.d.ts.map +1 -0
- package/dist/contracts/policy.js +107 -0
- package/dist/contracts/policy.js.map +1 -0
- package/dist/contracts/storage.d.ts +78 -0
- package/dist/contracts/storage.d.ts.map +1 -0
- package/dist/contracts/storage.js +2 -0
- package/dist/contracts/storage.js.map +1 -0
- package/dist/contracts/types.d.ts +381 -0
- package/dist/contracts/types.d.ts.map +1 -0
- package/dist/contracts/types.js +94 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/core/circuit-breaker.d.ts +11 -0
- package/dist/core/circuit-breaker.d.ts.map +1 -0
- package/dist/core/circuit-breaker.js +38 -0
- package/dist/core/circuit-breaker.js.map +1 -0
- package/dist/core/context.d.ts +56 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +345 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/events.d.ts +8 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +25 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/extractor.d.ts +37 -0
- package/dist/core/extractor.d.ts.map +1 -0
- package/dist/core/extractor.js +448 -0
- package/dist/core/extractor.js.map +1 -0
- package/dist/core/formatter.d.ts +25 -0
- package/dist/core/formatter.d.ts.map +1 -0
- package/dist/core/formatter.js +97 -0
- package/dist/core/formatter.js.map +1 -0
- package/dist/core/knowledge-lifecycle.d.ts +15 -0
- package/dist/core/knowledge-lifecycle.d.ts.map +1 -0
- package/dist/core/knowledge-lifecycle.js +103 -0
- package/dist/core/knowledge-lifecycle.js.map +1 -0
- package/dist/core/maintenance.d.ts +13 -0
- package/dist/core/maintenance.d.ts.map +1 -0
- package/dist/core/maintenance.js +102 -0
- package/dist/core/maintenance.js.map +1 -0
- package/dist/core/manager.d.ts +110 -0
- package/dist/core/manager.d.ts.map +1 -0
- package/dist/core/manager.js +640 -0
- package/dist/core/manager.js.map +1 -0
- package/dist/core/monitor.d.ts +73 -0
- package/dist/core/monitor.d.ts.map +1 -0
- package/dist/core/monitor.js +395 -0
- package/dist/core/monitor.js.map +1 -0
- package/dist/core/orchestrator.d.ts +64 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +916 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/presets.d.ts +15 -0
- package/dist/core/presets.d.ts.map +1 -0
- package/dist/core/presets.js +99 -0
- package/dist/core/presets.js.map +1 -0
- package/dist/core/provider-managers.d.ts +47 -0
- package/dist/core/provider-managers.d.ts.map +1 -0
- package/dist/core/provider-managers.js +112 -0
- package/dist/core/provider-managers.js.map +1 -0
- package/dist/core/quick.d.ts +62 -0
- package/dist/core/quick.d.ts.map +1 -0
- package/dist/core/quick.js +300 -0
- package/dist/core/quick.js.map +1 -0
- package/dist/core/retrieval.d.ts +29 -0
- package/dist/core/retrieval.d.ts.map +1 -0
- package/dist/core/retrieval.js +150 -0
- package/dist/core/retrieval.js.map +1 -0
- package/dist/core/runtime.d.ts +67 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +84 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/streaming.d.ts +37 -0
- package/dist/core/streaming.d.ts.map +1 -0
- package/dist/core/streaming.js +51 -0
- package/dist/core/streaming.js.map +1 -0
- package/dist/core/sync.d.ts +13 -0
- package/dist/core/sync.d.ts.map +1 -0
- package/dist/core/sync.js +46 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/core/telemetry.d.ts +8 -0
- package/dist/core/telemetry.d.ts.map +1 -0
- package/dist/core/telemetry.js +14 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/core/tokens.d.ts +8 -0
- package/dist/core/tokens.d.ts.map +1 -0
- package/dist/core/tokens.js +59 -0
- package/dist/core/tokens.js.map +1 -0
- package/dist/core/trust.d.ts +23 -0
- package/dist/core/trust.d.ts.map +1 -0
- package/dist/core/trust.js +164 -0
- package/dist/core/trust.js.map +1 -0
- package/dist/core/validation.d.ts +36 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +185 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/embeddings/local.d.ts +5 -0
- package/dist/embeddings/local.d.ts.map +1 -0
- package/dist/embeddings/local.js +128 -0
- package/dist/embeddings/local.js.map +1 -0
- package/dist/embeddings/openai.d.ts +26 -0
- package/dist/embeddings/openai.d.ts.map +1 -0
- package/dist/embeddings/openai.js +48 -0
- package/dist/embeddings/openai.js.map +1 -0
- package/dist/embeddings/resilience.d.ts +5 -0
- package/dist/embeddings/resilience.d.ts.map +1 -0
- package/dist/embeddings/resilience.js +53 -0
- package/dist/embeddings/resilience.js.map +1 -0
- package/dist/embeddings/voyage.d.ts +30 -0
- package/dist/embeddings/voyage.d.ts.map +1 -0
- package/dist/embeddings/voyage.js +53 -0
- package/dist/embeddings/voyage.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/claude-agent.d.ts +21 -0
- package/dist/integrations/claude-agent.d.ts.map +1 -0
- package/dist/integrations/claude-agent.js +44 -0
- package/dist/integrations/claude-agent.js.map +1 -0
- package/dist/integrations/claude-tools.d.ts +18 -0
- package/dist/integrations/claude-tools.d.ts.map +1 -0
- package/dist/integrations/claude-tools.js +60 -0
- package/dist/integrations/claude-tools.js.map +1 -0
- package/dist/integrations/langchain.d.ts +24 -0
- package/dist/integrations/langchain.d.ts.map +1 -0
- package/dist/integrations/langchain.js +48 -0
- package/dist/integrations/langchain.js.map +1 -0
- package/dist/integrations/mcp.d.ts +23 -0
- package/dist/integrations/mcp.d.ts.map +1 -0
- package/dist/integrations/mcp.js +60 -0
- package/dist/integrations/mcp.js.map +1 -0
- package/dist/integrations/middleware.d.ts +15 -0
- package/dist/integrations/middleware.d.ts.map +1 -0
- package/dist/integrations/middleware.js +27 -0
- package/dist/integrations/middleware.js.map +1 -0
- package/dist/integrations/openai-tools.d.ts +21 -0
- package/dist/integrations/openai-tools.d.ts.map +1 -0
- package/dist/integrations/openai-tools.js +69 -0
- package/dist/integrations/openai-tools.js.map +1 -0
- package/dist/integrations/vercel-ai.d.ts +19 -0
- package/dist/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/integrations/vercel-ai.js +41 -0
- package/dist/integrations/vercel-ai.js.map +1 -0
- package/dist/server/http-server.d.ts +61 -0
- package/dist/server/http-server.d.ts.map +1 -0
- package/dist/server/http-server.js +684 -0
- package/dist/server/http-server.js.map +1 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/mcp-server.d.ts +61 -0
- package/dist/server/mcp-server.d.ts.map +1 -0
- package/dist/server/mcp-server.js +465 -0
- package/dist/server/mcp-server.js.map +1 -0
- package/dist/summarizers/claude.d.ts +11 -0
- package/dist/summarizers/claude.d.ts.map +1 -0
- package/dist/summarizers/claude.js +39 -0
- package/dist/summarizers/claude.js.map +1 -0
- package/dist/summarizers/client.d.ts +23 -0
- package/dist/summarizers/client.d.ts.map +1 -0
- package/dist/summarizers/client.js +24 -0
- package/dist/summarizers/client.js.map +1 -0
- package/dist/summarizers/extractive.d.ts +6 -0
- package/dist/summarizers/extractive.d.ts.map +1 -0
- package/dist/summarizers/extractive.js +204 -0
- package/dist/summarizers/extractive.js.map +1 -0
- package/dist/summarizers/extractor.d.ts +12 -0
- package/dist/summarizers/extractor.d.ts.map +1 -0
- package/dist/summarizers/extractor.js +75 -0
- package/dist/summarizers/extractor.js.map +1 -0
- package/dist/summarizers/openai.d.ts +11 -0
- package/dist/summarizers/openai.d.ts.map +1 -0
- package/dist/summarizers/openai.js +41 -0
- package/dist/summarizers/openai.js.map +1 -0
- package/dist/summarizers/prompts.d.ts +11 -0
- package/dist/summarizers/prompts.d.ts.map +1 -0
- package/dist/summarizers/prompts.js +104 -0
- package/dist/summarizers/prompts.js.map +1 -0
- package/docs/DEPLOYMENT.md +84 -0
- package/docs/INTEGRATIONS.md +64 -0
- package/docs/MEMORY_QUALITY_BASELINE.md +55 -0
- package/docs/MEMORY_QUALITY_RELEASE_GATE.md +63 -0
- package/docs/MEMORY_QUALITY_RUBRIC.md +249 -0
- package/docs/OPERATIONS.md +49 -0
- package/docs/SECURITY.md +25 -0
- package/openapi.yaml +843 -0
- package/package.json +157 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { createMemoryWithAsyncAdapter, } from '../core/quick.js';
|
|
3
|
+
import { normalizeScope } from '../contracts/identity.js';
|
|
4
|
+
import { createSQLiteAdapterWithEmbeddings } from '../adapters/sqlite/index.js';
|
|
5
|
+
import { wrapSyncAdapter } from '../adapters/sync-to-async.js';
|
|
6
|
+
class HttpRequestError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
constructor(status, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function writeJson(res, status, data) {
|
|
14
|
+
const body = JSON.stringify(data);
|
|
15
|
+
res.writeHead(status, {
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
'Content-Length': Buffer.byteLength(body),
|
|
18
|
+
});
|
|
19
|
+
res.end(body);
|
|
20
|
+
}
|
|
21
|
+
function writeError(res, status, message) {
|
|
22
|
+
writeJson(res, status, { error: message });
|
|
23
|
+
}
|
|
24
|
+
async function readBody(req, limitBytes = 1_048_576) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const chunks = [];
|
|
27
|
+
let totalBytes = 0;
|
|
28
|
+
let tooLarge = false;
|
|
29
|
+
req.on('data', (chunk) => {
|
|
30
|
+
if (!tooLarge) {
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
}
|
|
33
|
+
totalBytes += chunk.length;
|
|
34
|
+
if (totalBytes > limitBytes) {
|
|
35
|
+
tooLarge = true;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
req.on('end', () => {
|
|
39
|
+
try {
|
|
40
|
+
if (tooLarge) {
|
|
41
|
+
reject(new HttpRequestError(413, 'Request body too large'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const text = Buffer.concat(chunks).toString('utf-8');
|
|
45
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
46
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
47
|
+
reject(new HttpRequestError(400, 'JSON body must be an object'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
resolve(parsed);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
reject(new HttpRequestError(400, 'Invalid JSON body'));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
req.on('error', reject);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function parseQuery(url) {
|
|
60
|
+
const idx = url.indexOf('?');
|
|
61
|
+
if (idx === -1)
|
|
62
|
+
return {};
|
|
63
|
+
const params = {};
|
|
64
|
+
const search = url.slice(idx + 1);
|
|
65
|
+
for (const pair of search.split('&')) {
|
|
66
|
+
try {
|
|
67
|
+
const [key, value] = pair.split('=');
|
|
68
|
+
if (key)
|
|
69
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value ?? '');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new HttpRequestError(400, 'Invalid query string');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return params;
|
|
76
|
+
}
|
|
77
|
+
function parseOptionalInteger(value) {
|
|
78
|
+
if (value == null || value === '')
|
|
79
|
+
return undefined;
|
|
80
|
+
const parsed = Number(value);
|
|
81
|
+
return Number.isInteger(parsed) ? parsed : undefined;
|
|
82
|
+
}
|
|
83
|
+
function normalizePath(path) {
|
|
84
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
85
|
+
return path.slice(0, -1);
|
|
86
|
+
}
|
|
87
|
+
return path;
|
|
88
|
+
}
|
|
89
|
+
function isRecord(value) {
|
|
90
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
91
|
+
}
|
|
92
|
+
function requireString(value, name) {
|
|
93
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
94
|
+
throw new HttpRequestError(400, `Missing or invalid field: ${name}`);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function optionalString(value, name) {
|
|
99
|
+
if (value == null)
|
|
100
|
+
return undefined;
|
|
101
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
102
|
+
throw new HttpRequestError(400, `Invalid field: ${name}`);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function requireEnum(value, allowed, name) {
|
|
107
|
+
if (typeof value !== 'string' || !allowed.includes(value)) {
|
|
108
|
+
throw new HttpRequestError(400, `Invalid field: ${name}`);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function parseLimit(value) {
|
|
113
|
+
const parsed = parseOptionalInteger(value);
|
|
114
|
+
if (value != null && parsed == null) {
|
|
115
|
+
throw new HttpRequestError(400, 'Invalid limit parameter');
|
|
116
|
+
}
|
|
117
|
+
return parsed;
|
|
118
|
+
}
|
|
119
|
+
function parseScopeLevel(value, name, allowed = ['scope', 'workspace', 'system', 'tenant']) {
|
|
120
|
+
if (value == null || value === '')
|
|
121
|
+
return undefined;
|
|
122
|
+
return requireEnum(value, allowed, name);
|
|
123
|
+
}
|
|
124
|
+
function resolvePartialScope(source, labels) {
|
|
125
|
+
const requiredValues = [source.tenant_id, source.system_id, source.scope_id];
|
|
126
|
+
const provided = requiredValues.filter((value) => value != null && value !== '').length;
|
|
127
|
+
if (provided === 0) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
if (provided !== 3) {
|
|
131
|
+
throw new HttpRequestError(400, `Incomplete scope override: ${labels.join(', ')}`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
tenant_id: requireString(source.tenant_id, labels[0]),
|
|
135
|
+
system_id: requireString(source.system_id, labels[1]),
|
|
136
|
+
workspace_id: optionalString(source.workspace_id, 'workspace_id'),
|
|
137
|
+
collaboration_id: optionalString(source.collaboration_id, 'collaboration_id'),
|
|
138
|
+
scope_id: requireString(source.scope_id, labels[2]),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function parseEventTypes(value) {
|
|
142
|
+
if (!value)
|
|
143
|
+
return undefined;
|
|
144
|
+
const allowed = [
|
|
145
|
+
'manager',
|
|
146
|
+
'search',
|
|
147
|
+
'compaction',
|
|
148
|
+
'extraction',
|
|
149
|
+
'promotion',
|
|
150
|
+
'knowledge_change',
|
|
151
|
+
'context_assembly',
|
|
152
|
+
'semantic_search',
|
|
153
|
+
];
|
|
154
|
+
return new Set(value
|
|
155
|
+
.split(',')
|
|
156
|
+
.map((entry) => entry.trim())
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.map((entry) => requireEnum(entry, allowed, 'event_types')));
|
|
159
|
+
}
|
|
160
|
+
function resolveRequestScope(fallbackScope, req, query, body) {
|
|
161
|
+
const bodyScope = body?.scope;
|
|
162
|
+
if (bodyScope) {
|
|
163
|
+
if (!isRecord(bodyScope)) {
|
|
164
|
+
throw new HttpRequestError(400, 'Invalid scope override');
|
|
165
|
+
}
|
|
166
|
+
normalizeScope(bodyScope);
|
|
167
|
+
return bodyScope;
|
|
168
|
+
}
|
|
169
|
+
const headerScope = resolvePartialScope({
|
|
170
|
+
tenant_id: req.headers['x-memory-tenant'],
|
|
171
|
+
system_id: req.headers['x-memory-system'],
|
|
172
|
+
workspace_id: req.headers['x-memory-workspace'],
|
|
173
|
+
collaboration_id: req.headers['x-memory-collaboration'],
|
|
174
|
+
scope_id: req.headers['x-memory-scope'],
|
|
175
|
+
}, ['x-memory-tenant', 'x-memory-system', 'x-memory-scope']);
|
|
176
|
+
if (headerScope) {
|
|
177
|
+
normalizeScope(headerScope);
|
|
178
|
+
return headerScope;
|
|
179
|
+
}
|
|
180
|
+
const queryScope = resolvePartialScope({
|
|
181
|
+
tenant_id: query.tenant_id,
|
|
182
|
+
system_id: query.system_id,
|
|
183
|
+
workspace_id: query.workspace_id,
|
|
184
|
+
collaboration_id: query.collaboration_id,
|
|
185
|
+
scope_id: query.scope_id,
|
|
186
|
+
}, ['tenant_id', 'system_id', 'scope_id']);
|
|
187
|
+
if (queryScope) {
|
|
188
|
+
normalizeScope(queryScope);
|
|
189
|
+
return queryScope;
|
|
190
|
+
}
|
|
191
|
+
return fallbackScope ?? 'default';
|
|
192
|
+
}
|
|
193
|
+
function matchesEventScope(event, scope, level) {
|
|
194
|
+
const left = normalizeScope(event.scope);
|
|
195
|
+
const right = normalizeScope(scope);
|
|
196
|
+
if (left.tenant_id !== right.tenant_id)
|
|
197
|
+
return false;
|
|
198
|
+
if (level === 'tenant')
|
|
199
|
+
return true;
|
|
200
|
+
if (level === 'workspace' && left.collaboration_id && right.collaboration_id) {
|
|
201
|
+
return left.collaboration_id === right.collaboration_id;
|
|
202
|
+
}
|
|
203
|
+
if (left.system_id !== right.system_id)
|
|
204
|
+
return false;
|
|
205
|
+
if (level === 'system')
|
|
206
|
+
return true;
|
|
207
|
+
if (level === 'workspace') {
|
|
208
|
+
return left.workspace_id === right.workspace_id;
|
|
209
|
+
}
|
|
210
|
+
return left.workspace_id === right.workspace_id && left.scope_id === right.scope_id;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Creates and starts an HTTP server exposing memory operations as a REST API.
|
|
214
|
+
*
|
|
215
|
+
* Endpoints:
|
|
216
|
+
* - POST /v1/turns - Store a turn
|
|
217
|
+
* - POST /v1/exchanges - Store a user+assistant exchange
|
|
218
|
+
* - GET /v1/context - Get assembled context
|
|
219
|
+
* - GET /v1/search - Search turns and knowledge
|
|
220
|
+
* - POST /v1/facts - Learn a fact
|
|
221
|
+
* - POST /v1/work - Track a work item
|
|
222
|
+
* - POST /v1/compact - Force compaction
|
|
223
|
+
* - GET /v1/health - Get health report
|
|
224
|
+
* - POST /v1/maintenance - Run maintenance
|
|
225
|
+
* - GET /v1/inspect/* - Inspect knowledge, audits, monitor, and compactions
|
|
226
|
+
* - POST /v1/reverification - Run reverification workflows
|
|
227
|
+
* - GET /v1/events - SSE stream of memory events
|
|
228
|
+
*/
|
|
229
|
+
export async function startHttpServer(config = {}) {
|
|
230
|
+
const port = config.port ?? 3100;
|
|
231
|
+
const host = config.host ?? '127.0.0.1';
|
|
232
|
+
const apiKey = config.apiKey ?? process.env.MEMORY_API_KEY;
|
|
233
|
+
const adminApiKey = config.adminApiKey ?? process.env.MEMORY_ADMIN_API_KEY;
|
|
234
|
+
const enableCors = config.cors ?? true;
|
|
235
|
+
const bodyLimitBytes = config.bodyLimitBytes ?? 1_048_576;
|
|
236
|
+
const managers = new Map();
|
|
237
|
+
const databaseUrl = config.databaseUrl ?? process.env.MEMORY_DATABASE_URL;
|
|
238
|
+
const adapterResources = databaseUrl
|
|
239
|
+
? await (async () => {
|
|
240
|
+
const moduleName = 'pg';
|
|
241
|
+
const pgModule = await import(moduleName).catch(() => {
|
|
242
|
+
throw new Error('memory-layer: hosted Postgres mode requires the "pg" package. Install it with: npm install pg');
|
|
243
|
+
});
|
|
244
|
+
const { createPostgresAdapter, createPostgresEmbeddingAdapter } = await import('../adapters/postgres/index.js');
|
|
245
|
+
const Pool = pgModule.Pool ?? pgModule.default?.Pool;
|
|
246
|
+
const pool = new Pool({ connectionString: databaseUrl });
|
|
247
|
+
const asyncAdapter = createPostgresAdapter(pool);
|
|
248
|
+
return {
|
|
249
|
+
asyncAdapter,
|
|
250
|
+
embeddingAdapter: createPostgresEmbeddingAdapter(pool),
|
|
251
|
+
close: async () => {
|
|
252
|
+
await pool.end();
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
})()
|
|
256
|
+
: (() => {
|
|
257
|
+
const sqlite = createSQLiteAdapterWithEmbeddings(config.dbPath ?? ':memory:');
|
|
258
|
+
return {
|
|
259
|
+
asyncAdapter: wrapSyncAdapter(sqlite),
|
|
260
|
+
embeddingAdapter: sqlite.embeddings,
|
|
261
|
+
close: async () => {
|
|
262
|
+
sqlite.close();
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
})();
|
|
266
|
+
const sseClients = new Set();
|
|
267
|
+
function createHostedManager(scopeInput) {
|
|
268
|
+
const baseOptions = {
|
|
269
|
+
adapter: 'sqlite',
|
|
270
|
+
path: config.dbPath ?? ':memory:',
|
|
271
|
+
scope: scopeInput,
|
|
272
|
+
summarizer: config.summarizer ?? 'extractive',
|
|
273
|
+
extractor: config.extractor ?? 'regex',
|
|
274
|
+
preset: config.preset,
|
|
275
|
+
redactText: config.redactText,
|
|
276
|
+
qualityMode: config.qualityMode,
|
|
277
|
+
qualityTier: config.qualityTier,
|
|
278
|
+
crossScopeLevel: config.crossScopeLevel,
|
|
279
|
+
onEvent: (event) => {
|
|
280
|
+
if (sseClients.size === 0)
|
|
281
|
+
return;
|
|
282
|
+
const data = `data: ${JSON.stringify(event)}\n\n`;
|
|
283
|
+
for (const client of sseClients) {
|
|
284
|
+
if (client.eventTypes && !client.eventTypes.has(event.type))
|
|
285
|
+
continue;
|
|
286
|
+
if (client.scope && client.scopeLevel) {
|
|
287
|
+
if (!matchesEventScope(event, client.scope, client.scopeLevel))
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
client.response.write(data);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
return createMemoryWithAsyncAdapter({
|
|
295
|
+
...baseOptions,
|
|
296
|
+
asyncAdapter: adapterResources.asyncAdapter,
|
|
297
|
+
embeddingAdapter: adapterResources.embeddingAdapter,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function getManager(scopeInput) {
|
|
301
|
+
const key = typeof scopeInput === 'string'
|
|
302
|
+
? `scope:${scopeInput}`
|
|
303
|
+
: JSON.stringify(normalizeScope(scopeInput));
|
|
304
|
+
const existing = managers.get(key);
|
|
305
|
+
if (existing) {
|
|
306
|
+
return existing;
|
|
307
|
+
}
|
|
308
|
+
const manager = createHostedManager(scopeInput);
|
|
309
|
+
managers.set(key, manager);
|
|
310
|
+
return manager;
|
|
311
|
+
}
|
|
312
|
+
const manager = getManager(config.scope ?? 'default');
|
|
313
|
+
const server = createServer(async (req, res) => {
|
|
314
|
+
// CORS
|
|
315
|
+
if (enableCors) {
|
|
316
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
317
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
318
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-admin-key, x-memory-tenant, x-memory-system, x-memory-workspace, x-memory-collaboration, x-memory-scope, Last-Event-ID');
|
|
319
|
+
}
|
|
320
|
+
if (req.method === 'OPTIONS') {
|
|
321
|
+
res.writeHead(204);
|
|
322
|
+
res.end();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
// Auth
|
|
326
|
+
if (apiKey) {
|
|
327
|
+
const auth = req.headers.authorization;
|
|
328
|
+
if (!auth || auth !== `Bearer ${apiKey}`) {
|
|
329
|
+
writeError(res, 401, 'Unauthorized');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const url = req.url ?? '/';
|
|
335
|
+
const path = normalizePath(url.split('?')[0]);
|
|
336
|
+
const query = parseQuery(url);
|
|
337
|
+
// POST /v1/turns
|
|
338
|
+
if (path === '/v1/turns' && req.method === 'POST') {
|
|
339
|
+
const body = await readBody(req, bodyLimitBytes);
|
|
340
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query, body));
|
|
341
|
+
const turn = await requestManager.processTurn(requireEnum(body.role, ['user', 'assistant', 'system'], 'role'), requireString(body.content, 'content'), optionalString(body.actor, 'actor'));
|
|
342
|
+
writeJson(res, 201, { turnId: turn.id, role: turn.role });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// POST /v1/exchanges
|
|
346
|
+
if (path === '/v1/exchanges' && req.method === 'POST') {
|
|
347
|
+
const body = await readBody(req, bodyLimitBytes);
|
|
348
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query, body));
|
|
349
|
+
const exchange = await requestManager.processExchange(requireString(body.userContent, 'userContent'), requireString(body.assistantContent, 'assistantContent'));
|
|
350
|
+
writeJson(res, 201, {
|
|
351
|
+
userTurnId: exchange.userTurn.id,
|
|
352
|
+
assistantTurnId: exchange.assistantTurn.id,
|
|
353
|
+
compacted: exchange.compactionResult !== null,
|
|
354
|
+
});
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// GET /v1/context
|
|
358
|
+
if (path === '/v1/context' && req.method === 'GET') {
|
|
359
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
360
|
+
const context = await requestManager.getContext(query.query || undefined);
|
|
361
|
+
writeJson(res, 200, {
|
|
362
|
+
currentObjective: context.currentObjective,
|
|
363
|
+
activeTurnCount: context.activeTurns.length,
|
|
364
|
+
workingMemory: context.workingMemory
|
|
365
|
+
? {
|
|
366
|
+
summary: context.workingMemory.summary,
|
|
367
|
+
key_entities: context.workingMemory.key_entities,
|
|
368
|
+
topic_tags: context.workingMemory.topic_tags,
|
|
369
|
+
}
|
|
370
|
+
: null,
|
|
371
|
+
relevantKnowledge: context.relevantKnowledge.map((k) => ({
|
|
372
|
+
id: k.id,
|
|
373
|
+
fact: k.fact,
|
|
374
|
+
fact_type: k.fact_type,
|
|
375
|
+
confidence: k.confidence,
|
|
376
|
+
})),
|
|
377
|
+
activeObjectives: context.activeObjectives.map((o) => ({
|
|
378
|
+
title: o.title,
|
|
379
|
+
status: o.status,
|
|
380
|
+
})),
|
|
381
|
+
unresolvedWork: context.unresolvedWork,
|
|
382
|
+
tokenEstimate: context.tokenEstimate,
|
|
383
|
+
});
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// GET /v1/search
|
|
387
|
+
if (path === '/v1/search' && req.method === 'GET') {
|
|
388
|
+
if (!query.q) {
|
|
389
|
+
writeError(res, 400, 'Missing required query parameter: q');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
393
|
+
const results = await requestManager.search(query.q, query.limit ? { limit: parseLimit(query.limit) } : undefined);
|
|
394
|
+
writeJson(res, 200, {
|
|
395
|
+
turns: results.turns.map((r) => ({
|
|
396
|
+
id: r.item.id,
|
|
397
|
+
role: r.item.role,
|
|
398
|
+
content: r.item.content,
|
|
399
|
+
rank: r.rank,
|
|
400
|
+
})),
|
|
401
|
+
knowledge: results.knowledge.map((r) => ({
|
|
402
|
+
id: r.item.id,
|
|
403
|
+
fact: r.item.fact,
|
|
404
|
+
fact_type: r.item.fact_type,
|
|
405
|
+
rank: r.rank,
|
|
406
|
+
})),
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// GET /v1/search/cross-scope
|
|
411
|
+
if (path === '/v1/search/cross-scope' && req.method === 'GET') {
|
|
412
|
+
if (!query.q) {
|
|
413
|
+
writeError(res, 400, 'Missing required query parameter: q');
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
417
|
+
const scopeLevel = parseScopeLevel(query.scope_level, 'scope_level', [
|
|
418
|
+
'workspace',
|
|
419
|
+
'system',
|
|
420
|
+
'tenant',
|
|
421
|
+
]) ?? 'workspace';
|
|
422
|
+
const results = await requestManager.searchCrossScope(query.q, scopeLevel, query.limit ? { limit: parseLimit(query.limit) } : undefined);
|
|
423
|
+
writeJson(res, 200, {
|
|
424
|
+
knowledge: results.knowledge.map((r) => ({
|
|
425
|
+
id: r.item.id,
|
|
426
|
+
fact: r.item.fact,
|
|
427
|
+
fact_type: r.item.fact_type,
|
|
428
|
+
scope_id: r.item.scope_id,
|
|
429
|
+
collaboration_id: r.item.collaboration_id,
|
|
430
|
+
rank: r.rank,
|
|
431
|
+
})),
|
|
432
|
+
});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
// GET /v1/inspect/knowledge
|
|
436
|
+
if (path === '/v1/inspect/knowledge' && req.method === 'GET') {
|
|
437
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
438
|
+
const limit = parseOptionalInteger(query.limit);
|
|
439
|
+
const cursor = parseOptionalInteger(query.cursor);
|
|
440
|
+
if ((query.limit && limit == null) || (query.cursor && cursor == null)) {
|
|
441
|
+
writeError(res, 400, 'Invalid pagination parameters');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const knowledge = await requestManager.listKnowledge({
|
|
445
|
+
limit,
|
|
446
|
+
cursor,
|
|
447
|
+
});
|
|
448
|
+
writeJson(res, 200, knowledge);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const knowledgeInspectMatch = path.match(/^\/v1\/inspect\/knowledge\/(\d+)$/);
|
|
452
|
+
if (knowledgeInspectMatch && req.method === 'GET') {
|
|
453
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
454
|
+
const detail = await requestManager.inspectKnowledge(Number(knowledgeInspectMatch[1]));
|
|
455
|
+
if (!detail.knowledge) {
|
|
456
|
+
writeError(res, 404, 'Knowledge not found');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
writeJson(res, 200, detail);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// GET /v1/inspect/audits
|
|
463
|
+
if (path === '/v1/inspect/audits' && req.method === 'GET') {
|
|
464
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
465
|
+
const knowledgeId = parseOptionalInteger(query.knowledge_id);
|
|
466
|
+
const limit = parseOptionalInteger(query.limit);
|
|
467
|
+
if ((query.knowledge_id && knowledgeId == null) || (query.limit && limit == null)) {
|
|
468
|
+
writeError(res, 400, 'Invalid audit inspection parameters');
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const audits = await requestManager.getKnowledgeAudits({
|
|
472
|
+
knowledgeId,
|
|
473
|
+
limit,
|
|
474
|
+
});
|
|
475
|
+
writeJson(res, 200, { audits });
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// GET /v1/inspect/monitor
|
|
479
|
+
if (path === '/v1/inspect/monitor' && req.method === 'GET') {
|
|
480
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
481
|
+
const monitor = await requestManager.getContextMonitor();
|
|
482
|
+
writeJson(res, 200, { monitor });
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
// GET /v1/inspect/compactions
|
|
486
|
+
if (path === '/v1/inspect/compactions' && req.method === 'GET') {
|
|
487
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
488
|
+
const limit = parseOptionalInteger(query.limit);
|
|
489
|
+
if (query.limit && limit == null) {
|
|
490
|
+
writeError(res, 400, 'Invalid compaction inspection parameters');
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const logs = await requestManager.getRecentCompactionLogs(limit);
|
|
494
|
+
writeJson(res, 200, { logs });
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
// GET /v1/inspect/reverification
|
|
498
|
+
if (path === '/v1/inspect/reverification' && req.method === 'GET') {
|
|
499
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
500
|
+
const limit = parseOptionalInteger(query.limit);
|
|
501
|
+
if (query.limit && limit == null) {
|
|
502
|
+
writeError(res, 400, 'Invalid reverification inspection parameters');
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const due = await requestManager.getDueReverification({ limit });
|
|
506
|
+
writeJson(res, 200, { due });
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
// POST /v1/facts
|
|
510
|
+
if (path === '/v1/facts' && req.method === 'POST') {
|
|
511
|
+
const body = await readBody(req, bodyLimitBytes);
|
|
512
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query, body));
|
|
513
|
+
const fact = await requestManager.learnFact(requireString(body.fact, 'fact'), requireEnum(body.factType, ['preference', 'entity', 'decision', 'constraint', 'reference'], 'factType'), (body.confidence == null
|
|
514
|
+
? 'high'
|
|
515
|
+
: requireEnum(body.confidence, ['high', 'medium', 'low'], 'confidence')));
|
|
516
|
+
writeJson(res, 201, { knowledgeId: fact.id });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
// POST /v1/work
|
|
520
|
+
if (path === '/v1/work' && req.method === 'POST') {
|
|
521
|
+
const body = await readBody(req, bodyLimitBytes);
|
|
522
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query, body));
|
|
523
|
+
const item = await requestManager.trackWorkItem(requireString(body.title, 'title'), requireEnum(body.kind ?? 'objective', ['objective', 'unresolved_work', 'constraint'], 'kind'), requireEnum(body.status ?? 'open', ['open', 'in_progress', 'blocked', 'done'], 'status'), optionalString(body.detail, 'detail'));
|
|
524
|
+
writeJson(res, 201, { workItemId: item.id });
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
// POST /v1/compact
|
|
528
|
+
if (path === '/v1/compact' && req.method === 'POST') {
|
|
529
|
+
if (adminApiKey && req.headers['x-admin-key'] !== adminApiKey) {
|
|
530
|
+
writeError(res, 403, 'Admin key required');
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const body = await readBody(req, bodyLimitBytes);
|
|
534
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query, body));
|
|
535
|
+
const result = await requestManager.forceCompact();
|
|
536
|
+
writeJson(res, 200, {
|
|
537
|
+
compacted: result !== null,
|
|
538
|
+
archivedTurnCount: result?.archivedTurnIds.length ?? 0,
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// GET /v1/health
|
|
543
|
+
if (path === '/v1/health' && req.method === 'GET') {
|
|
544
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
545
|
+
const context = await requestManager.getContext();
|
|
546
|
+
writeJson(res, 200, {
|
|
547
|
+
activeTurnCount: context.activeTurns.length,
|
|
548
|
+
tokenEstimate: context.tokenEstimate,
|
|
549
|
+
knowledgeCount: context.relevantKnowledge.length,
|
|
550
|
+
objectiveCount: context.activeObjectives.length,
|
|
551
|
+
unresolvedWorkCount: context.unresolvedWork.length,
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if ((path === '/healthz' || path === '/readyz') && req.method === 'GET') {
|
|
556
|
+
writeJson(res, 200, { ok: true, scopes: managers.size });
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
// POST /v1/maintenance
|
|
560
|
+
if (path === '/v1/maintenance' && req.method === 'POST') {
|
|
561
|
+
if (adminApiKey && req.headers['x-admin-key'] !== adminApiKey) {
|
|
562
|
+
writeError(res, 403, 'Admin key required');
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const body = await readBody(req, bodyLimitBytes);
|
|
566
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query, body));
|
|
567
|
+
const report = await requestManager.runMaintenance();
|
|
568
|
+
writeJson(res, 200, {
|
|
569
|
+
expiredWorkingMemory: report.expiredWorkingMemoryIds.length,
|
|
570
|
+
retiredKnowledge: report.retiredKnowledgeIds.length,
|
|
571
|
+
deletedWorkItems: report.deletedWorkItemIds.length,
|
|
572
|
+
});
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const reverificationMatch = path.match(/^\/v1\/reverification\/(\d+)$/);
|
|
576
|
+
if (reverificationMatch && req.method === 'POST') {
|
|
577
|
+
if (adminApiKey && req.headers['x-admin-key'] !== adminApiKey) {
|
|
578
|
+
writeError(res, 403, 'Admin key required');
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
582
|
+
const result = await requestManager.reverifyKnowledge(Number(reverificationMatch[1]));
|
|
583
|
+
writeJson(res, 200, result);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
// POST /v1/reverification/run
|
|
587
|
+
if (path === '/v1/reverification/run' && req.method === 'POST') {
|
|
588
|
+
if (adminApiKey && req.headers['x-admin-key'] !== adminApiKey) {
|
|
589
|
+
writeError(res, 403, 'Admin key required');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const body = await readBody(req, bodyLimitBytes);
|
|
593
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query, body));
|
|
594
|
+
let limit;
|
|
595
|
+
if (body.limit != null) {
|
|
596
|
+
if (typeof body.limit !== 'number' || !Number.isInteger(body.limit)) {
|
|
597
|
+
throw new HttpRequestError(400, 'Invalid field: limit');
|
|
598
|
+
}
|
|
599
|
+
limit = body.limit;
|
|
600
|
+
}
|
|
601
|
+
const report = await requestManager.runReverification({ limit });
|
|
602
|
+
writeJson(res, 200, report);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
// GET /v1/changes
|
|
606
|
+
if (path === '/v1/changes' && req.method === 'GET') {
|
|
607
|
+
const requestManager = getManager(resolveRequestScope(config.scope, req, query));
|
|
608
|
+
const sinceValue = query.since ? new Date(query.since) : new Date(0);
|
|
609
|
+
if (Number.isNaN(sinceValue.valueOf())) {
|
|
610
|
+
writeError(res, 400, 'Invalid since parameter');
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const changes = await requestManager.pollForChanges(sinceValue, {
|
|
614
|
+
scopeLevel: parseScopeLevel(query.scope_level, 'scope_level') ?? 'scope',
|
|
615
|
+
});
|
|
616
|
+
writeJson(res, 200, {
|
|
617
|
+
changes: changes.map((knowledge) => ({
|
|
618
|
+
id: knowledge.id,
|
|
619
|
+
fact: knowledge.fact,
|
|
620
|
+
fact_type: knowledge.fact_type,
|
|
621
|
+
knowledge_state: knowledge.knowledge_state,
|
|
622
|
+
scope_id: knowledge.scope_id,
|
|
623
|
+
collaboration_id: knowledge.collaboration_id,
|
|
624
|
+
created_at: knowledge.created_at,
|
|
625
|
+
})),
|
|
626
|
+
});
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
// GET /v1/events (SSE)
|
|
630
|
+
if (path === '/v1/events' && req.method === 'GET') {
|
|
631
|
+
res.writeHead(200, {
|
|
632
|
+
'Content-Type': 'text/event-stream',
|
|
633
|
+
'Cache-Control': 'no-cache',
|
|
634
|
+
Connection: 'keep-alive',
|
|
635
|
+
});
|
|
636
|
+
res.write('data: {"type":"connected"}\n\n');
|
|
637
|
+
const scope = resolveRequestScope(config.scope, req, query);
|
|
638
|
+
sseClients.add({
|
|
639
|
+
response: res,
|
|
640
|
+
scope: typeof scope === 'string' ? undefined : scope,
|
|
641
|
+
scopeLevel: parseScopeLevel(query.scope_level, 'scope_level') ?? 'scope',
|
|
642
|
+
eventTypes: parseEventTypes(query.event_types),
|
|
643
|
+
});
|
|
644
|
+
req.on('close', () => {
|
|
645
|
+
for (const client of sseClients) {
|
|
646
|
+
if (client.response === res) {
|
|
647
|
+
sseClients.delete(client);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
writeError(res, 404, `Not found: ${req.method} ${path}`);
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
if (error instanceof HttpRequestError) {
|
|
657
|
+
writeError(res, error.status, error.message);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
writeError(res, 500, error instanceof Error ? error.message : String(error));
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
return new Promise((resolve) => {
|
|
664
|
+
server.listen(port, host, () => {
|
|
665
|
+
resolve({
|
|
666
|
+
server,
|
|
667
|
+
manager,
|
|
668
|
+
async close() {
|
|
669
|
+
for (const client of sseClients) {
|
|
670
|
+
client.response.end();
|
|
671
|
+
}
|
|
672
|
+
sseClients.clear();
|
|
673
|
+
server.close();
|
|
674
|
+
for (const cachedManager of managers.values()) {
|
|
675
|
+
await cachedManager.close();
|
|
676
|
+
}
|
|
677
|
+
managers.clear();
|
|
678
|
+
await adapterResources.close();
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
//# sourceMappingURL=http-server.js.map
|