@arkheia/mcp-server 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +219 -0
- package/dist/memory.js +187 -0
- package/dist/providers.js +320 -0
- package/dist/proxy-client.js +219 -0
- package/dist/tool-registry.js +90 -0
- package/package.json +43 -57
- package/bin/arkheia-mcp.js +0 -240
- package/python/mcp_server/__init__.py +0 -0
- package/python/requirements.txt +0 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
38
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
39
|
+
const zod_1 = require("zod");
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const proxy_client_js_1 = require("./proxy-client.js");
|
|
44
|
+
const tool_registry_js_1 = require("./tool-registry.js");
|
|
45
|
+
const providers_js_1 = require("./providers.js");
|
|
46
|
+
const memory_js_1 = require("./memory.js");
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Config Loading
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
function loadConfig() {
|
|
51
|
+
const configPath = path.join(os.homedir(), '.arkheia', 'config.json');
|
|
52
|
+
if (!fs.existsSync(configPath))
|
|
53
|
+
return;
|
|
54
|
+
try {
|
|
55
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
56
|
+
if (config.api_key && !process.env.ARKHEIA_API_KEY) {
|
|
57
|
+
process.env.ARKHEIA_API_KEY = config.api_key;
|
|
58
|
+
process.stderr.write(`[arkheia] API key loaded from ${configPath}\n`);
|
|
59
|
+
}
|
|
60
|
+
if (config.proxy_url && !process.env.ARKHEIA_HOSTED_URL) {
|
|
61
|
+
process.env.ARKHEIA_HOSTED_URL = config.proxy_url;
|
|
62
|
+
process.stderr.write(`[arkheia] Hosted URL: ${config.proxy_url}\n`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
process.stderr.write(`[arkheia] Warning: Could not read ${configPath}\n`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function checkCRLF() {
|
|
70
|
+
for (const k of ['ARKHEIA_API_KEY', 'ARKHEIA_PROXY_URL', 'ARKHEIA_HOSTED_URL']) {
|
|
71
|
+
const v = process.env[k];
|
|
72
|
+
if (v && /[\r\n]/.test(v)) {
|
|
73
|
+
process.stderr.write(`[arkheia] WARNING: ${k} contains whitespace/newline characters. Run 'dos2unix' on your env file.\n`);
|
|
74
|
+
process.env[k] = v.trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Helpers
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
function wrapResult(result) {
|
|
82
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
83
|
+
}
|
|
84
|
+
function handleError(toolName, e) {
|
|
85
|
+
if (e instanceof tool_registry_js_1.PolicyViolation) {
|
|
86
|
+
return wrapResult({ error: e.reason, risk_level: "UNKNOWN" });
|
|
87
|
+
}
|
|
88
|
+
process.stderr.write(`[arkheia] ${toolName} error: ${e.message}\n`);
|
|
89
|
+
return wrapResult({ error: e.message, risk_level: "UNKNOWN" });
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Main
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
async function main() {
|
|
95
|
+
loadConfig();
|
|
96
|
+
checkCRLF();
|
|
97
|
+
const ARKHEIA_PROXY_URL = process.env.ARKHEIA_PROXY_URL || "http://localhost:8098";
|
|
98
|
+
const ARKHEIA_HOSTED_URL = process.env.ARKHEIA_HOSTED_URL || "https://arkheia-proxy-production.up.railway.app";
|
|
99
|
+
const ARKHEIA_API_KEY = process.env.ARKHEIA_API_KEY;
|
|
100
|
+
if (!ARKHEIA_API_KEY) {
|
|
101
|
+
process.stderr.write("[arkheia] Warning: ARKHEIA_API_KEY not set.\n" +
|
|
102
|
+
"Get a free API key at https://arkheia.ai/mcp\n" +
|
|
103
|
+
"Then set: export ARKHEIA_API_KEY=ak_live_...\n\n");
|
|
104
|
+
}
|
|
105
|
+
const proxy = new proxy_client_js_1.ProxyClient(ARKHEIA_PROXY_URL, 10.0, ARKHEIA_HOSTED_URL, ARKHEIA_API_KEY);
|
|
106
|
+
const server = new mcp_js_1.McpServer({ name: "arkheia-trust", version: "0.1.6" });
|
|
107
|
+
// ── Detection & audit ──────────────────────────────────────────
|
|
108
|
+
server.tool("arkheia_verify", "Verify whether an AI response shows signs of fabrication. Call this on EVERY model response before acting on it. Risk guidance: HIGH = warn the user this response may be fabricated and include the detection_id, but still show them the response with the warning. MEDIUM = show with a brief confidence note. LOW = show normally. Never hide or suppress a response — always surface it to the user with the appropriate risk context.", { prompt: zod_1.z.string(), response: zod_1.z.string(), model: zod_1.z.string() }, async ({ prompt, response, model }) => {
|
|
109
|
+
try {
|
|
110
|
+
(0, tool_registry_js_1.check)("arkheia_verify");
|
|
111
|
+
const result = await proxy.verify(prompt, response, model);
|
|
112
|
+
return wrapResult(result);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
return handleError("arkheia_verify", e);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
server.tool("arkheia_audit_log", "Retrieve structured audit evidence for compliance review.", { session_id: zod_1.z.string().optional(), limit: zod_1.z.number().int().min(1).max(500).default(50) }, async ({ session_id, limit }) => {
|
|
119
|
+
try {
|
|
120
|
+
(0, tool_registry_js_1.check)("arkheia_audit_log");
|
|
121
|
+
const result = await proxy.get_audit_log(session_id, Math.min(limit, 500));
|
|
122
|
+
return wrapResult(result);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return handleError("arkheia_audit_log", e);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// ── Provider wrappers ──────────────────────────────────────────
|
|
129
|
+
server.tool("run_grok", "Call xAI Grok and screen the response through Arkheia for fabrication.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("grok-4-fast-non-reasoning") }, async ({ prompt, model }) => {
|
|
130
|
+
try {
|
|
131
|
+
(0, tool_registry_js_1.check)("run_grok");
|
|
132
|
+
const pr = await (0, providers_js_1.call_grok)(prompt, model);
|
|
133
|
+
if (pr.error)
|
|
134
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
135
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
136
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return handleError("run_grok", e);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
server.tool("run_gemini", "Call Google Gemini and screen the response through Arkheia for fabrication.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("gemini-2.5-flash") }, async ({ prompt, model }) => {
|
|
143
|
+
try {
|
|
144
|
+
(0, tool_registry_js_1.check)("run_gemini");
|
|
145
|
+
const pr = await (0, providers_js_1.call_gemini)(prompt, model);
|
|
146
|
+
if (pr.error)
|
|
147
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
148
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
149
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
return handleError("run_gemini", e);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
server.tool("run_ollama", "Call a local Ollama model and screen the response through Arkheia. No network egress.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("phi4:14b") }, async ({ prompt, model }) => {
|
|
156
|
+
try {
|
|
157
|
+
(0, tool_registry_js_1.check)("run_ollama");
|
|
158
|
+
const pr = await (0, providers_js_1.call_ollama)(prompt, model);
|
|
159
|
+
if (pr.error)
|
|
160
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
161
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
162
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return handleError("run_ollama", e);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
server.tool("run_together", "Call Together AI (Kimi K2.5, DeepSeek, etc.) and screen the response through Arkheia.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("moonshotai/Kimi-K2.5") }, async ({ prompt, model }) => {
|
|
169
|
+
try {
|
|
170
|
+
(0, tool_registry_js_1.check)("run_together");
|
|
171
|
+
const pr = await (0, providers_js_1.call_together)(prompt, model);
|
|
172
|
+
if (pr.error)
|
|
173
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
174
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
175
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
return handleError("run_together", e);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
// ── Memory / Knowledge Graph ───────────────────────────────────
|
|
182
|
+
server.tool("memory_store", "Store an entity and its observations in the persistent knowledge graph. Entities are upserted by name+type.", { name: zod_1.z.string(), entity_type: zod_1.z.string(), observations: zod_1.z.array(zod_1.z.string()) }, async ({ name, entity_type, observations }) => {
|
|
183
|
+
try {
|
|
184
|
+
(0, tool_registry_js_1.check)("memory_store");
|
|
185
|
+
const result = await (0, memory_js_1.store_entity)(name, entity_type, observations);
|
|
186
|
+
return wrapResult(result);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
return handleError("memory_store", e);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
server.tool("memory_retrieve", "Search entities in the persistent knowledge graph by name (case-insensitive).", { query: zod_1.z.string(), entity_type: zod_1.z.string().optional(), limit: zod_1.z.number().int().min(1).max(50).default(10) }, async ({ query, entity_type, limit }) => {
|
|
193
|
+
try {
|
|
194
|
+
(0, tool_registry_js_1.check)("memory_retrieve");
|
|
195
|
+
const result = await (0, memory_js_1.retrieve_entities)(query, entity_type, Math.min(limit, 50));
|
|
196
|
+
return wrapResult(result);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
return handleError("memory_retrieve", e);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
server.tool("memory_relate", "Store a named relationship between two entities in the knowledge graph.", { from_entity: zod_1.z.string(), relation_type: zod_1.z.string(), to_entity: zod_1.z.string() }, async ({ from_entity, relation_type, to_entity }) => {
|
|
203
|
+
try {
|
|
204
|
+
(0, tool_registry_js_1.check)("memory_relate");
|
|
205
|
+
const result = await (0, memory_js_1.store_relation)(from_entity, relation_type, to_entity);
|
|
206
|
+
return wrapResult(result);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
return handleError("memory_relate", e);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
// ── Start ──────────────────────────────────────────────────────
|
|
213
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
214
|
+
await server.connect(transport);
|
|
215
|
+
}
|
|
216
|
+
main().catch((err) => {
|
|
217
|
+
process.stderr.write(`[arkheia] Fatal error: ${err}\n`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
});
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.store_entity = store_entity;
|
|
40
|
+
exports.retrieve_entities = retrieve_entities;
|
|
41
|
+
exports.store_relation = store_relation;
|
|
42
|
+
const sql_js_1 = __importDefault(require("sql.js"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const crypto = __importStar(require("crypto"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const logger = console;
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// DB setup — sql.js (pure JS, no native module)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
function _db_path() {
|
|
52
|
+
return process.env.MEMORY_DB_PATH || path.join(os.homedir(), '.arkheia', 'memory.db');
|
|
53
|
+
}
|
|
54
|
+
let _db = null;
|
|
55
|
+
let _dbReady = null;
|
|
56
|
+
function _getDb() {
|
|
57
|
+
if (_dbReady)
|
|
58
|
+
return _dbReady;
|
|
59
|
+
_dbReady = (async () => {
|
|
60
|
+
const SQL = await (0, sql_js_1.default)();
|
|
61
|
+
const dbPath = _db_path();
|
|
62
|
+
const dir = path.dirname(dbPath);
|
|
63
|
+
if (!fs.existsSync(dir)) {
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
// Load existing DB or create new
|
|
67
|
+
if (fs.existsSync(dbPath)) {
|
|
68
|
+
const buffer = fs.readFileSync(dbPath);
|
|
69
|
+
_db = new SQL.Database(buffer);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
_db = new SQL.Database();
|
|
73
|
+
}
|
|
74
|
+
// Init schema
|
|
75
|
+
_db.run(`
|
|
76
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
77
|
+
entity_id TEXT PRIMARY KEY,
|
|
78
|
+
name TEXT NOT NULL,
|
|
79
|
+
entity_type TEXT NOT NULL,
|
|
80
|
+
created_at TEXT NOT NULL
|
|
81
|
+
);
|
|
82
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
83
|
+
obs_id TEXT PRIMARY KEY,
|
|
84
|
+
entity_id TEXT NOT NULL REFERENCES entities(entity_id),
|
|
85
|
+
content TEXT NOT NULL,
|
|
86
|
+
created_at TEXT NOT NULL
|
|
87
|
+
);
|
|
88
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
89
|
+
rel_id TEXT PRIMARY KEY,
|
|
90
|
+
from_entity TEXT NOT NULL,
|
|
91
|
+
relation_type TEXT NOT NULL,
|
|
92
|
+
to_entity TEXT NOT NULL,
|
|
93
|
+
created_at TEXT NOT NULL
|
|
94
|
+
);
|
|
95
|
+
`);
|
|
96
|
+
_save(_db);
|
|
97
|
+
return _db;
|
|
98
|
+
})();
|
|
99
|
+
return _dbReady;
|
|
100
|
+
}
|
|
101
|
+
function _save(db) {
|
|
102
|
+
const data = db.export();
|
|
103
|
+
const buffer = Buffer.from(data);
|
|
104
|
+
fs.writeFileSync(_db_path(), buffer);
|
|
105
|
+
}
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Public functions
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
async function store_entity(name, entity_type, observations) {
|
|
110
|
+
const db = await _getDb();
|
|
111
|
+
const now = new Date().toISOString();
|
|
112
|
+
// Upsert entity — look up by name+type
|
|
113
|
+
let entity_id;
|
|
114
|
+
const existing = db.exec("SELECT entity_id FROM entities WHERE name = ? AND entity_type = ?", [name, entity_type]);
|
|
115
|
+
if (existing.length > 0 && existing[0].values.length > 0) {
|
|
116
|
+
entity_id = existing[0].values[0][0];
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
entity_id = crypto.randomUUID();
|
|
120
|
+
db.run("INSERT INTO entities (entity_id, name, entity_type, created_at) VALUES (?, ?, ?, ?)", [entity_id, name, entity_type, now]);
|
|
121
|
+
}
|
|
122
|
+
// Fetch existing observation contents to deduplicate
|
|
123
|
+
const existingObs = db.exec("SELECT content FROM observations WHERE entity_id = ?", [entity_id]);
|
|
124
|
+
const existingContentSet = new Set();
|
|
125
|
+
if (existingObs.length > 0) {
|
|
126
|
+
for (const row of existingObs[0].values) {
|
|
127
|
+
existingContentSet.add(row[0]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
let added = 0;
|
|
131
|
+
for (const content of observations) {
|
|
132
|
+
if (!existingContentSet.has(content)) {
|
|
133
|
+
db.run("INSERT INTO observations (obs_id, entity_id, content, created_at) VALUES (?, ?, ?, ?)", [crypto.randomUUID(), entity_id, content, now]);
|
|
134
|
+
existingContentSet.add(content);
|
|
135
|
+
added++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const countResult = db.exec("SELECT COUNT(*) AS n FROM observations WHERE entity_id = ?", [entity_id]);
|
|
139
|
+
const totalObservations = countResult.length > 0 ? countResult[0].values[0][0] : 0;
|
|
140
|
+
_save(db);
|
|
141
|
+
return {
|
|
142
|
+
entity_id,
|
|
143
|
+
name,
|
|
144
|
+
entity_type,
|
|
145
|
+
observations_added: added,
|
|
146
|
+
total_observations: totalObservations,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function retrieve_entities(query, entity_type = undefined, limit = 10) {
|
|
150
|
+
const db = await _getDb();
|
|
151
|
+
const pattern = `%${query}%`;
|
|
152
|
+
let rows;
|
|
153
|
+
if (entity_type) {
|
|
154
|
+
const result = db.exec("SELECT entity_id, name, entity_type, created_at FROM entities WHERE name LIKE ? AND entity_type = ?", [pattern, entity_type]);
|
|
155
|
+
rows = result.length > 0 ? result[0].values.map(r => ({ entity_id: r[0], name: r[1], entity_type: r[2], created_at: r[3] })) : [];
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const result = db.exec("SELECT entity_id, name, entity_type, created_at FROM entities WHERE name LIKE ?", [pattern]);
|
|
159
|
+
rows = result.length > 0 ? result[0].values.map(r => ({ entity_id: r[0], name: r[1], entity_type: r[2], created_at: r[3] })) : [];
|
|
160
|
+
}
|
|
161
|
+
const total = rows.length;
|
|
162
|
+
rows = rows.slice(0, Math.min(limit, 50));
|
|
163
|
+
const entities = [];
|
|
164
|
+
for (const row of rows) {
|
|
165
|
+
const obsResult = db.exec("SELECT content, created_at FROM observations WHERE entity_id = ? ORDER BY created_at", [row.entity_id]);
|
|
166
|
+
const obs = obsResult.length > 0 ? obsResult[0].values.map(o => ({ content: o[0], created_at: o[1] })) : [];
|
|
167
|
+
const relResult = db.exec("SELECT relation_type, to_entity FROM relations WHERE from_entity = ? ORDER BY created_at", [row.name]);
|
|
168
|
+
const rels = relResult.length > 0 ? relResult[0].values.map(r => ({ relation_type: r[0], to_entity: r[1] })) : [];
|
|
169
|
+
entities.push({
|
|
170
|
+
entity_id: row.entity_id,
|
|
171
|
+
name: row.name,
|
|
172
|
+
entity_type: row.entity_type,
|
|
173
|
+
created_at: row.created_at,
|
|
174
|
+
observations: obs,
|
|
175
|
+
relations: rels,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return { entities, total };
|
|
179
|
+
}
|
|
180
|
+
async function store_relation(from_entity, relation_type, to_entity) {
|
|
181
|
+
const db = await _getDb();
|
|
182
|
+
const rel_id = crypto.randomUUID();
|
|
183
|
+
const now = new Date().toISOString();
|
|
184
|
+
db.run("INSERT INTO relations (rel_id, from_entity, relation_type, to_entity, created_at) VALUES (?, ?, ?, ?, ?)", [rel_id, from_entity, relation_type, to_entity, now]);
|
|
185
|
+
_save(db);
|
|
186
|
+
return { rel_id, from_entity, relation_type, to_entity };
|
|
187
|
+
}
|