prepia 1.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/LICENSE +21 -0
- package/README.md +312 -0
- package/bin/prepia.mjs +119 -0
- package/package.json +53 -0
- package/skill/SKILL.md +148 -0
- package/skill/config.json +29 -0
- package/src/analytics/dashboard.mjs +84 -0
- package/src/analytics/tracker.mjs +131 -0
- package/src/api/middleware.mjs +219 -0
- package/src/api/routes.mjs +142 -0
- package/src/api/server.mjs +150 -0
- package/src/cache/disk-store.mjs +199 -0
- package/src/cache/manager.mjs +142 -0
- package/src/cache/memory-store.mjs +205 -0
- package/src/chain/dag.mjs +209 -0
- package/src/chain/executor.mjs +103 -0
- package/src/chain/scheduler.mjs +89 -0
- package/src/client/adapters.mjs +483 -0
- package/src/client/connector.mjs +391 -0
- package/src/client/index.mjs +483 -0
- package/src/client/websocket.mjs +353 -0
- package/src/core/context-packager.mjs +169 -0
- package/src/core/engine.mjs +338 -0
- package/src/core/event-bus.mjs +84 -0
- package/src/core/prepimshot.mjs +120 -0
- package/src/core/task-decomposer.mjs +158 -0
- package/src/edge/lite.mjs +90 -0
- package/src/guard/checker.mjs +123 -0
- package/src/guard/fact-checker.mjs +105 -0
- package/src/guard/hallucination.mjs +108 -0
- package/src/index.mjs +67 -0
- package/src/models/local-model.mjs +171 -0
- package/src/models/provider.mjs +192 -0
- package/src/models/router.mjs +156 -0
- package/src/morph/optimizer.mjs +142 -0
- package/src/network/p2p.mjs +146 -0
- package/src/persona/detector.mjs +118 -0
- package/src/plugins/loader.mjs +120 -0
- package/src/plugins/registry.mjs +164 -0
- package/src/plugins/sandbox.mjs +79 -0
- package/src/rate/limiter.mjs +145 -0
- package/src/rate/shield.mjs +150 -0
- package/src/script/executor.mjs +164 -0
- package/src/script/parser.mjs +134 -0
- package/src/security/privacy.mjs +108 -0
- package/src/security/sanitizer.mjs +133 -0
- package/src/shadow/daemon.mjs +128 -0
- package/src/stream/handler.mjs +204 -0
- package/src/tools/calculator.mjs +312 -0
- package/src/tools/file-ops.mjs +138 -0
- package/src/tools/http-client.mjs +127 -0
- package/src/tools/orchestrator.mjs +205 -0
- package/src/tools/web-scraper.mjs +159 -0
- package/src/tools/web-search.mjs +129 -0
- package/src/vault/knowledge-base.mjs +207 -0
- package/src/vault/pattern-learner.mjs +192 -0
- package/workflows/analyze.json +32 -0
- package/workflows/automate.json +32 -0
- package/workflows/research.json +37 -0
- package/workflows/summarize.json +32 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepia Universal Connector
|
|
3
|
+
* Auto-detects and connects any AI agent to Prepia
|
|
4
|
+
* Supports: HTTP REST, WebSocket, CLI, Webhook, stdio
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
import http from 'http';
|
|
9
|
+
import { PrepiClient, prepiConnect } from './index.mjs';
|
|
10
|
+
import { WebSocketServer } from './websocket.mjs';
|
|
11
|
+
import { createAdapter, listAdapters } from './adapters.mjs';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ConnectorConfig
|
|
15
|
+
* @property {number} [port=4710] - Server port
|
|
16
|
+
* @property {string} [host='0.0.0.0'] - Bind host
|
|
17
|
+
* @property {string} [apiKey] - Required API key (null = no auth)
|
|
18
|
+
* @property {boolean} [cors=true] - Enable CORS
|
|
19
|
+
* @property {number} [maxConnections=100] - Max concurrent WebSocket connections
|
|
20
|
+
* @property {boolean} [websocket=true] - Enable WebSocket support
|
|
21
|
+
* @property {Object} [rateLimit] - Per-client rate limiting
|
|
22
|
+
* @property {number} [rateLimit.windowMs=60000] - Rate limit window
|
|
23
|
+
* @property {number} [rateLimit.maxRequests=100] - Max requests per window
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Prepia Universal Connector Server
|
|
28
|
+
* Accepts connections from ANY AI agent via REST, WebSocket, or CLI
|
|
29
|
+
*/
|
|
30
|
+
export class PrepiaConnector extends EventEmitter {
|
|
31
|
+
/**
|
|
32
|
+
* @param {ConnectorConfig} config
|
|
33
|
+
* @param {import('../core/engine.mjs').PrepiaEngine} engine - Prepia engine instance
|
|
34
|
+
*/
|
|
35
|
+
constructor(config = {}, engine = null) {
|
|
36
|
+
super();
|
|
37
|
+
this.config = {
|
|
38
|
+
port: config.port != null ? config.port : 4710,
|
|
39
|
+
host: config.host || '0.0.0.0',
|
|
40
|
+
apiKey: config.apiKey || null,
|
|
41
|
+
cors: config.cors !== false,
|
|
42
|
+
maxConnections: config.maxConnections || 100,
|
|
43
|
+
websocket: config.websocket !== false,
|
|
44
|
+
rateLimit: config.rateLimit || { windowMs: 60000, maxRequests: 100 },
|
|
45
|
+
};
|
|
46
|
+
this.engine = engine;
|
|
47
|
+
this.server = null;
|
|
48
|
+
this.wsServer = null;
|
|
49
|
+
/** @type {Map<string, { count: number, resetAt: number }>} */
|
|
50
|
+
this.rateLimits = new Map();
|
|
51
|
+
this.running = false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Start the connector server
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
async start() {
|
|
59
|
+
if (this.running) return;
|
|
60
|
+
|
|
61
|
+
this.server = http.createServer((req, res) => this._handleRequest(req, res));
|
|
62
|
+
|
|
63
|
+
if (this.config.websocket) {
|
|
64
|
+
this.wsServer = new WebSocketServer();
|
|
65
|
+
this.server.on('upgrade', (req, socket, head) => {
|
|
66
|
+
if (this.wsServer.clients.size >= this.config.maxConnections) {
|
|
67
|
+
socket.destroy();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!this._authenticate(req)) {
|
|
71
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
72
|
+
socket.destroy();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.wsServer.handleUpgrade(req, socket, head);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
this.wsServer.on('task:submit', async (conn, data, msg) => {
|
|
79
|
+
try {
|
|
80
|
+
conn.send({ event: 'task:started', taskId: msg.taskId, timestamp: Date.now() });
|
|
81
|
+
const result = await this._executeTask(data, (phase) => {
|
|
82
|
+
conn.send({ event: 'task:progress', taskId: msg.taskId, data: phase });
|
|
83
|
+
});
|
|
84
|
+
conn.send({ event: 'task:complete', taskId: msg.taskId, data: result });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
conn.send({ event: 'task:error', taskId: msg.taskId, data: { error: err.message } });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
93
|
+
this.running = true;
|
|
94
|
+
this.emit('started', { host: this.config.host, port: this.config.port });
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Stop the connector server
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
async stop() {
|
|
105
|
+
if (!this.running) return;
|
|
106
|
+
if (this.wsServer) this.wsServer.closeAll();
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
this.server.close(() => {
|
|
109
|
+
this.running = false;
|
|
110
|
+
this.emit('stopped');
|
|
111
|
+
resolve();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Handle incoming HTTP request
|
|
118
|
+
* @param {http.IncomingMessage} req
|
|
119
|
+
* @param {http.ServerResponse} res
|
|
120
|
+
*/
|
|
121
|
+
async _handleRequest(req, res) {
|
|
122
|
+
// CORS
|
|
123
|
+
if (this.config.cors) {
|
|
124
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
125
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
126
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Prepia-Agent-Id, X-Prepia-Agent-Type');
|
|
127
|
+
if (req.method === 'OPTIONS') {
|
|
128
|
+
res.writeHead(204);
|
|
129
|
+
res.end();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Auth
|
|
135
|
+
if (!this._authenticate(req)) {
|
|
136
|
+
this._json(res, 401, { error: 'Unauthorized', message: 'Invalid or missing API key' });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Rate limit
|
|
141
|
+
const clientId = this._getClientId(req);
|
|
142
|
+
if (!this._checkRateLimit(clientId)) {
|
|
143
|
+
this._json(res, 429, { error: 'Rate limited', message: 'Too many requests', retryAfter: this.config.rateLimit.windowMs / 1000 });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
148
|
+
const path = url.pathname;
|
|
149
|
+
const method = req.method;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
// Parse body for POST/PUT
|
|
153
|
+
let body = null;
|
|
154
|
+
if (method === 'POST' || method === 'PUT') {
|
|
155
|
+
body = await this._parseBody(req);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Route request
|
|
159
|
+
const result = await this._route(method, path, body, req, url.searchParams);
|
|
160
|
+
this._json(res, result.status || 200, result.data || result);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
const status = err.status || 500;
|
|
163
|
+
this._json(res, status, { error: err.message, code: err.code });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Route request to appropriate handler
|
|
169
|
+
*/
|
|
170
|
+
async _route(method, path, body, req, params) {
|
|
171
|
+
// Health
|
|
172
|
+
if (path === '/api/health' && method === 'GET') {
|
|
173
|
+
return {
|
|
174
|
+
data: {
|
|
175
|
+
status: 'ok',
|
|
176
|
+
version: '1.0.0',
|
|
177
|
+
uptime: process.uptime(),
|
|
178
|
+
connections: this.wsServer?.clientCount || 0,
|
|
179
|
+
adapters: listAdapters(),
|
|
180
|
+
features: {
|
|
181
|
+
websocket: this.config.websocket,
|
|
182
|
+
caching: true,
|
|
183
|
+
analytics: true,
|
|
184
|
+
plugins: true,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Task submission
|
|
191
|
+
if (path === '/api/task' && method === 'POST') {
|
|
192
|
+
if (!body?.description) {
|
|
193
|
+
throw Object.assign(new Error('Missing required field: description'), { status: 400 });
|
|
194
|
+
}
|
|
195
|
+
const onProgress = (phase) => this.emit('task:progress', phase);
|
|
196
|
+
const result = await this._executeTask(body, onProgress);
|
|
197
|
+
return { data: result };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Analytics
|
|
201
|
+
if (path === '/api/analytics' && method === 'GET') {
|
|
202
|
+
if (this.engine?.analytics) {
|
|
203
|
+
return { data: this.engine.analytics.getReport() };
|
|
204
|
+
}
|
|
205
|
+
return { data: { message: 'Analytics not available' } };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Cache
|
|
209
|
+
if (path === '/api/cache' && method === 'DELETE') {
|
|
210
|
+
if (this.engine?.cache) {
|
|
211
|
+
this.engine.cache.clear();
|
|
212
|
+
return { data: { message: 'Cache cleared' } };
|
|
213
|
+
}
|
|
214
|
+
return { data: { message: 'Cache not available' } };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (path.startsWith('/api/cache/') && method === 'DELETE') {
|
|
218
|
+
const key = decodeURIComponent(path.slice('/api/cache/'.length));
|
|
219
|
+
if (this.engine?.cache) {
|
|
220
|
+
this.engine.cache.delete(key);
|
|
221
|
+
return { data: { message: `Cache key '${key}' deleted` } };
|
|
222
|
+
}
|
|
223
|
+
return { data: { message: 'Cache not available' } };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Plugins
|
|
227
|
+
if (path === '/api/plugins' && method === 'GET') {
|
|
228
|
+
if (this.engine?.plugins) {
|
|
229
|
+
return { data: this.engine.plugins.list() };
|
|
230
|
+
}
|
|
231
|
+
return { data: [] };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Config
|
|
235
|
+
if (path === '/api/config' && method === 'GET') {
|
|
236
|
+
return { data: this.engine?.getConfig?.() || {} };
|
|
237
|
+
}
|
|
238
|
+
if (path === '/api/config' && method === 'POST') {
|
|
239
|
+
if (this.engine?.updateConfig) {
|
|
240
|
+
this.engine.updateConfig(body);
|
|
241
|
+
return { data: { message: 'Config updated' } };
|
|
242
|
+
}
|
|
243
|
+
return { data: { message: 'Config update not available' } };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Script execution
|
|
247
|
+
if (path === '/api/script' && method === 'POST') {
|
|
248
|
+
if (!body?.script) {
|
|
249
|
+
throw Object.assign(new Error('Missing required field: script'), { status: 400 });
|
|
250
|
+
}
|
|
251
|
+
if (this.engine?.script) {
|
|
252
|
+
const result = await this.engine.script.execute(body.script);
|
|
253
|
+
return { data: result };
|
|
254
|
+
}
|
|
255
|
+
return { data: { message: 'Script engine not available' } };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Knowledge base
|
|
259
|
+
if (path === '/api/knowledge' && method === 'GET') {
|
|
260
|
+
const query = params.get('q');
|
|
261
|
+
if (this.engine?.vault) {
|
|
262
|
+
const results = this.engine.vault.search(query || '');
|
|
263
|
+
return { data: results };
|
|
264
|
+
}
|
|
265
|
+
return { data: [] };
|
|
266
|
+
}
|
|
267
|
+
if (path === '/api/knowledge' && method === 'POST') {
|
|
268
|
+
if (this.engine?.vault && body?.key) {
|
|
269
|
+
this.engine.vault.store(body.key, body.value, body);
|
|
270
|
+
return { data: { message: 'Stored' } };
|
|
271
|
+
}
|
|
272
|
+
throw Object.assign(new Error('Missing key'), { status: 400 });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Adapters info
|
|
276
|
+
if (path === '/api/adapters' && method === 'GET') {
|
|
277
|
+
return { data: { adapters: listAdapters() } };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Python client generation
|
|
281
|
+
if (path === '/api/client/python' && method === 'GET') {
|
|
282
|
+
const { PythonBridgeAdapter } = await import('./adapters.mjs');
|
|
283
|
+
const adapter = new PythonBridgeAdapter({ host: this.config.host, port: this.config.port });
|
|
284
|
+
return { data: { code: adapter.generatePythonClient() } };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// OpenAPI spec
|
|
288
|
+
if (path === '/api/spec' && method === 'GET') {
|
|
289
|
+
const { RestAdapter } = await import('./adapters.mjs');
|
|
290
|
+
const adapter = new RestAdapter({ host: this.config.host, port: this.config.port });
|
|
291
|
+
return { data: adapter.getOpenAPISpec() };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
throw Object.assign(new Error(`Not found: ${method} ${path}`), { status: 404 });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Execute a task through the engine
|
|
299
|
+
*/
|
|
300
|
+
async _executeTask(taskData, onProgress) {
|
|
301
|
+
if (this.engine) {
|
|
302
|
+
return this.engine.process(taskData, onProgress);
|
|
303
|
+
}
|
|
304
|
+
// Fallback if no engine attached
|
|
305
|
+
return {
|
|
306
|
+
status: 'no_engine',
|
|
307
|
+
message: 'Prepia engine not attached. Connect an engine to process tasks.',
|
|
308
|
+
input: taskData,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Authenticate request
|
|
314
|
+
*/
|
|
315
|
+
_authenticate(req) {
|
|
316
|
+
if (!this.config.apiKey) return true;
|
|
317
|
+
const auth = req.headers.authorization;
|
|
318
|
+
if (!auth) return false;
|
|
319
|
+
const token = auth.replace(/^Bearer\s+/i, '');
|
|
320
|
+
return token === this.config.apiKey;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get client identifier for rate limiting
|
|
325
|
+
*/
|
|
326
|
+
_getClientId(req) {
|
|
327
|
+
return req.headers['x-prepia-agent-id'] || req.socket.remoteAddress || 'unknown';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Check rate limit for a client
|
|
332
|
+
*/
|
|
333
|
+
_checkRateLimit(clientId) {
|
|
334
|
+
const now = Date.now();
|
|
335
|
+
const limit = this.rateLimits.get(clientId);
|
|
336
|
+
|
|
337
|
+
if (!limit || now > limit.resetAt) {
|
|
338
|
+
this.rateLimits.set(clientId, { count: 1, resetAt: now + this.config.rateLimit.windowMs });
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (limit.count >= this.config.rateLimit.maxRequests) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
limit.count++;
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Parse JSON request body
|
|
352
|
+
*/
|
|
353
|
+
_parseBody(req) {
|
|
354
|
+
return new Promise((resolve, reject) => {
|
|
355
|
+
const chunks = [];
|
|
356
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
357
|
+
req.on('end', () => {
|
|
358
|
+
const raw = Buffer.concat(chunks).toString();
|
|
359
|
+
if (!raw) return resolve(null);
|
|
360
|
+
try {
|
|
361
|
+
resolve(JSON.parse(raw));
|
|
362
|
+
} catch {
|
|
363
|
+
reject(Object.assign(new Error('Invalid JSON body'), { status: 400 }));
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
req.on('error', reject);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Send JSON response
|
|
372
|
+
*/
|
|
373
|
+
_json(res, status, data) {
|
|
374
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
375
|
+
res.end(JSON.stringify(data));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Create and start a Prepia connector
|
|
381
|
+
* @param {ConnectorConfig} config
|
|
382
|
+
* @param {import('../core/engine.mjs').PrepiaEngine} engine
|
|
383
|
+
* @returns {Promise<PrepiaConnector>}
|
|
384
|
+
*/
|
|
385
|
+
export async function startConnector(config = {}, engine = null) {
|
|
386
|
+
const connector = new PrepiaConnector(config, engine);
|
|
387
|
+
await connector.start();
|
|
388
|
+
return connector;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export default PrepiaConnector;
|