@devilsdev/rag-pipeline-utils 2.4.5 → 2.6.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/.ragrc.schema.json +83 -192
- package/CHANGELOG.md +431 -0
- package/README.md +418 -117
- package/SECURITY.md +5 -5
- package/dist/experimental.cjs +2 -0
- package/dist/experimental.d.ts +1 -0
- package/dist/experimental.mjs +5 -0
- package/dist/index.cjs +6442 -1355
- package/dist/index.d.ts +129 -2
- package/dist/index.mjs +1 -1
- package/dist/testing.cjs +2 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.mjs +5 -0
- package/dist/types/adapters/ai-sdk.d.ts +53 -0
- package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
- package/dist/types/agentic/iterative-retriever.d.ts +85 -0
- package/dist/types/agentic/query-planner.d.ts +145 -0
- package/dist/types/agentic/self-critique.d.ts +45 -0
- package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
- package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
- package/dist/types/ai/multimodal-processing.d.ts +32 -0
- package/dist/types/ai/multimodal.d.ts +39 -0
- package/dist/types/ai/retrieval-engine.d.ts +37 -0
- package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
- package/dist/types/cache/component-keys.d.ts +7 -0
- package/dist/types/cache/memory-cache.d.ts +54 -0
- package/dist/types/cache/pipeline-cache.d.ts +66 -0
- package/dist/types/cache/redis-cache.d.ts +46 -0
- package/dist/types/chunking/chunking-engine.d.ts +70 -0
- package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
- package/dist/types/chunking/strategies/recursive.d.ts +20 -0
- package/dist/types/chunking/strategies/semantic.d.ts +19 -0
- package/dist/types/chunking/strategies/sentence.d.ts +13 -0
- package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
- package/dist/types/citation/citation-tracker.d.ts +64 -0
- package/dist/types/citation/hallucination-detector.d.ts +40 -0
- package/dist/types/citation/source-mapper.d.ts +37 -0
- package/dist/types/connectors/anthropic-connector.d.ts +97 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +94 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +95 -0
- package/dist/types/connectors/memory-retriever.d.ts +99 -0
- package/dist/types/connectors/ollama-connector.d.ts +115 -0
- package/dist/types/connectors/openai-connector.d.ts +129 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -0
- package/dist/types/core/create-pipeline.d.ts +8 -0
- package/dist/types/core/plugin-registry.d.ts +136 -0
- package/dist/types/core/stage-contracts.d.ts +36 -0
- package/dist/types/core/validate-plugin.d.ts +45 -0
- package/dist/types/cost/cost-calculator.d.ts +152 -0
- package/dist/types/cost/cost-tracker.d.ts +126 -0
- package/dist/types/cost/token-budget.d.ts +110 -0
- package/dist/types/dag/core/error-context.d.ts +239 -0
- package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
- package/dist/types/dag/core/topology-validation.d.ts +226 -0
- package/dist/types/dag/dag-engine.d.ts +156 -0
- package/dist/types/dag/dag-node.d.ts +37 -0
- package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
- package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
- package/dist/types/debug/execution-tracer.d.ts +57 -0
- package/dist/types/debug/trace-inspector.d.ts +31 -0
- package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
- package/dist/types/enterprise/data-governance.d.ts +203 -0
- package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
- package/dist/types/enterprise/sso-integration.d.ts +420 -0
- package/dist/types/evaluate/context-metrics.d.ts +55 -0
- package/dist/types/evaluate/faithfulness.d.ts +41 -0
- package/dist/types/evaluate/groundedness.d.ts +60 -0
- package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
- package/dist/types/evaluate/relevance.d.ts +21 -0
- package/dist/types/evaluate/scoring.d.ts +123 -0
- package/dist/types/evaluate/semantic-similarity.d.ts +14 -0
- package/dist/types/experimental.d.ts +15 -0
- package/dist/types/graph/entity-extractor.d.ts +84 -0
- package/dist/types/graph/graph-index.d.ts +49 -0
- package/dist/types/graph/graph-retriever.d.ts +81 -0
- package/dist/types/graph/knowledge-graph.d.ts +129 -0
- package/dist/types/grounding/check-answer.d.ts +101 -0
- package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
- package/dist/types/guardrails/injection-detector.d.ts +47 -0
- package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
- package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
- package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
- package/dist/types/mcp/mcp-server.d.ts +85 -0
- package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
- package/dist/types/reranker/cascade-reranker.d.ts +40 -0
- package/dist/types/reranker/embedding-reranker.d.ts +77 -0
- package/dist/types/reranker/scoring-reranker.d.ts +41 -0
- package/dist/types/retrieval/bm25-search.d.ts +43 -0
- package/dist/types/retrieval/file-vector-store.d.ts +99 -0
- package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
- package/dist/types/retrieval/rank-fusion.d.ts +10 -0
- package/dist/types/security/jwt-config.d.ts +80 -0
- package/dist/types/security/jwt-validator.d.ts +195 -0
- package/dist/types/security/plugin-sandbox.d.ts +158 -0
- package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
- package/dist/types/security/validator.d.ts +5 -0
- package/dist/types/streaming/embedding-stream.d.ts +65 -0
- package/dist/types/streaming/sse-adapter.d.ts +59 -0
- package/dist/types/streaming/stream-router.d.ts +88 -0
- package/dist/types/streaming/streaming-embedder.d.ts +99 -0
- package/dist/types/streaming/websocket-adapter.d.ts +71 -0
- package/dist/types/templates/scaffold.d.ts +55 -0
- package/dist/types/templates/template-registry.d.ts +63 -0
- package/dist/types/testing.d.ts +168 -0
- package/dist/types/utils/embed-query.d.ts +20 -0
- package/dist/types/utils/logger.d.ts +50 -0
- package/dist/types/utils/rate-limiter.d.ts +66 -0
- package/dist/types/utils/retry.d.ts +27 -0
- package/dist/types/utils/run-abort.d.ts +34 -0
- package/dist/types/utils/secure-logging.d.ts +51 -0
- package/dist/types/utils/structured-logger.d.ts +126 -0
- package/package.json +72 -33
- package/src/adapters/ai-sdk.js +215 -0
- package/src/agentic/agentic-pipeline.js +75 -7
- package/src/agentic/iterative-retriever.js +47 -10
- package/src/agentic/query-planner.js +329 -78
- package/src/cache/component-keys.js +38 -0
- package/src/cache/memory-cache.js +87 -0
- package/src/cache/pipeline-cache.js +260 -0
- package/src/cache/redis-cache.js +83 -0
- package/src/citation/citation-tracker.js +6 -4
- package/src/citation/hallucination-detector.js +2 -2
- package/src/citation/source-mapper.js +15 -28
- package/src/cli/commands/ai.js +106 -0
- package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
- package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
- package/src/cli/commands/plugin-hub/local-commands.js +143 -0
- package/src/cli/commands/plugin-hub.js +114 -49
- package/src/cli/diagnostics/config-checker.js +76 -63
- package/src/cli/diagnostics/environment-checker.js +62 -31
- package/src/cli/diagnostics/plugin-checker.js +36 -89
- package/src/cli/doctor-command.js +39 -5
- package/src/cli/enhanced-cli-commands.js +104 -233
- package/src/cli/handlers/ai-handler.js +364 -0
- package/src/cli/handlers/diagnostics-handler.js +78 -31
- package/src/cli/handlers/ingest-handler.js +28 -22
- package/src/cli/handlers/init-handler.js +148 -25
- package/src/cli/handlers/query-handler.js +203 -25
- package/src/cli/handlers/run-handler.js +16 -167
- package/src/cli/handlers/serve-handler.js +60 -0
- package/src/cli/services/bench-stats.js +54 -0
- package/src/cli/services/benchmark.js +161 -0
- package/src/cli/services/feedback.js +155 -0
- package/src/cli/services/instrument.js +83 -0
- package/src/cli/services/rag-service.js +361 -0
- package/src/cli/shared/cli-utils.js +24 -7
- package/src/cli/shared/config-context.js +21 -0
- package/src/cli/wizard/config-builder.js +81 -0
- package/src/cli/wizard/init-wizard.js +602 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/enhanced-ragrc-schema.js +0 -2
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/config/validate-schema.js +4 -1
- package/src/connectors/anthropic-connector.js +115 -57
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +108 -34
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +63 -5
- package/src/connectors/memory-retriever.js +125 -14
- package/src/connectors/ollama-connector.js +112 -51
- package/src/connectors/openai-connector.js +153 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +307 -61
- package/src/core/performance/parallel-processor.js +62 -1
- package/src/core/plugin-registry.js +125 -26
- package/src/core/stage-contracts.js +36 -0
- package/src/core/validate-plugin.js +85 -0
- package/src/cost/cost-calculator.js +205 -32
- package/src/debug/execution-tracer.js +14 -4
- package/src/dev/dev-server.js +173 -11
- package/src/dev/hot-reload.js +44 -4
- package/src/ecosystem/plugin-checks.js +181 -0
- package/src/ecosystem/plugin-hub.js +50 -125
- package/src/ecosystem/plugin-installer.js +298 -0
- package/src/ecosystem/plugin-probe.js +127 -0
- package/src/ecosystem/plugin-scanner.js +185 -0
- package/src/ecosystem/tar-extract.js +114 -0
- package/src/enterprise/audit-logging.js +158 -29
- package/src/enterprise/sso-integration.js +716 -228
- package/src/evaluate/context-metrics.js +28 -16
- package/src/evaluate/evaluator.js +3 -3
- package/src/evaluate/faithfulness.js +18 -14
- package/src/evaluate/groundedness.js +43 -12
- package/src/evaluate/pipeline-evaluator.js +35 -8
- package/src/evaluate/scoring.js +251 -3
- package/src/evaluate/semantic-similarity.js +79 -0
- package/src/experimental.js +71 -0
- package/src/grounding/check-answer.js +173 -0
- package/src/guardrails/guardrails-pipeline.js +69 -7
- package/src/guardrails/injection-detector.js +283 -0
- package/src/guardrails/post-generation-guard.js +19 -0
- package/src/guardrails/pre-retrieval-guard.js +36 -36
- package/src/guardrails/retrieval-guard.js +58 -8
- package/src/index.js +58 -1
- package/src/loader/csv-loader.js +3 -2
- package/src/loader/document-reader.js +91 -0
- package/src/loader/html-loader.js +3 -1
- package/src/loader/markdown-loader.js +3 -2
- package/src/reranker/cascade-reranker.js +3 -0
- package/src/reranker/embedding-reranker.js +136 -21
- package/src/reranker/llm-reranker.js +79 -12
- package/src/reranker/scoring-reranker.js +3 -0
- package/src/retrieval/bm25-search.js +12 -6
- package/src/retrieval/file-vector-store.js +241 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +139 -34
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- package/src/testing.js +254 -0
- package/src/utils/deprecate.js +69 -0
- package/src/utils/embed-query.js +33 -0
- package/src/utils/input-sanitizer.js +19 -4
- package/src/utils/logger.js +34 -4
- package/src/utils/run-abort.js +102 -0
- package/src/ai/federated-learning.js +0 -211
- package/src/ai/federation.js +0 -71
- package/src/ai/index.js +0 -22
- package/src/ai/model-trainer.js +0 -217
- package/src/ai/model-training.js +0 -202
- package/src/ai/multimodal/multi-modal-processor.js +0 -345
- package/src/ai/orchestrator.js +0 -65
- package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
- package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
- package/src/cli/commands/ai-ml/federated-commands.js +0 -207
- package/src/cli/commands/ai-ml/index.js +0 -17
- package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
- package/src/cli/commands/ai-ml/shared.js +0 -51
- package/src/cli/commands/ai-ml/training-commands.js +0 -228
- package/src/cli/commands/ai-ml.js +0 -386
- package/src/cli/commands/docs.js +0 -228
- package/src/cli/commands/dx.js +0 -538
- package/src/cli/commands/wizard.js +0 -627
- package/src/cli/input-sanitization-middleware.js +0 -356
- package/src/cli/interactive-wizard.js +0 -373
- package/src/cli/templates/development.template.json +0 -64
- package/src/cli/templates/minimal.template.json +0 -33
- package/src/cli/templates/production.template.json +0 -104
- package/src/cli/templates/testing.template.json +0 -45
- package/src/cli/wizard/configuration.js +0 -148
- package/src/cli/wizard/observability.js +0 -79
- package/src/cli/wizard/performance.js +0 -76
- package/src/cli/wizard/plugin-selection.js +0 -297
- package/src/cli/wizard/preview-save.js +0 -172
- package/src/cli/wizard/project-setup.js +0 -66
- package/src/core/pipeline-factory.js +0 -59
- package/src/dag/enhanced-dag-engine.js +0 -894
- package/src/dx/index.js +0 -40
- package/src/dx/integration-templates.js +0 -662
- package/src/dx/performance-profiler.js +0 -968
- package/src/dx/realtime-debugger.js +0 -755
- package/src/dx/visual-pipeline-builder.js +0 -578
- package/src/ingest.js +0 -59
- package/src/pipeline/pipeline.js +0 -18
- package/src/query.js +0 -117
|
@@ -28,10 +28,17 @@ function generateTraceId() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* Snapshot a value for a trace: a JSON copy, or a truncated string when its
|
|
32
|
+
* JSON exceeds maxSize characters.
|
|
33
|
+
*
|
|
34
|
+
* Always a copy, never the live object: the traced run attaches the trace to
|
|
35
|
+
* its result (`result.trace`), so storing the result itself made the trace
|
|
36
|
+
* circular and exportTraces('json') threw. A copy also keeps later changes to
|
|
37
|
+
* the result out of the recorded trace.
|
|
38
|
+
*
|
|
39
|
+
* @param {*} value - Value to snapshot
|
|
33
40
|
* @param {number} maxSize - Maximum serialized size in characters
|
|
34
|
-
* @returns {*}
|
|
41
|
+
* @returns {*} JSON copy of the value, or a truncated string
|
|
35
42
|
*/
|
|
36
43
|
function truncatePayload(value, maxSize) {
|
|
37
44
|
if (value === undefined || value === null) {
|
|
@@ -43,8 +50,11 @@ function truncatePayload(value, maxSize) {
|
|
|
43
50
|
} catch {
|
|
44
51
|
return '[unserializable]';
|
|
45
52
|
}
|
|
53
|
+
if (serialized === undefined) {
|
|
54
|
+
return undefined; // functions and symbols have no JSON form
|
|
55
|
+
}
|
|
46
56
|
if (serialized.length <= maxSize) {
|
|
47
|
-
return
|
|
57
|
+
return JSON.parse(serialized);
|
|
48
58
|
}
|
|
49
59
|
return serialized.slice(0, maxSize) + '...[truncated]';
|
|
50
60
|
}
|
package/src/dev/dev-server.js
CHANGED
|
@@ -7,20 +7,25 @@
|
|
|
7
7
|
* for rapid plugin and configuration development.
|
|
8
8
|
*
|
|
9
9
|
* Features:
|
|
10
|
+
* - Local HTTP API over the configured pipeline:
|
|
11
|
+
* GET /health -> { status, uptime }
|
|
12
|
+
* POST /query -> { question, topK? } => { answer, results, citations }
|
|
13
|
+
* Binds to 127.0.0.1 by default, JSON only, no CORS headers, request
|
|
14
|
+
* bodies limited to 64 KB, no static file serving.
|
|
10
15
|
* - Automatic hot reloading of plugins and configuration
|
|
11
|
-
* - Live feedback on reload status
|
|
12
|
-
* - Error notifications
|
|
13
16
|
* - Statistics dashboard
|
|
14
|
-
* - WebSocket support for real-time updates (optional)
|
|
15
17
|
*
|
|
16
18
|
* @module dev/dev-server
|
|
17
19
|
* @since 2.4.0
|
|
18
20
|
*/
|
|
19
21
|
|
|
22
|
+
const http = require('http');
|
|
20
23
|
const { EventEmitter } = require('events');
|
|
21
24
|
const { createHotReloadManager } = require('./hot-reload');
|
|
22
25
|
const { logger } = require('../utils/logger');
|
|
23
26
|
|
|
27
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
|
|
28
|
+
|
|
24
29
|
/**
|
|
25
30
|
* Development Server
|
|
26
31
|
*
|
|
@@ -31,9 +36,12 @@ class DevServer extends EventEmitter {
|
|
|
31
36
|
super();
|
|
32
37
|
|
|
33
38
|
this.options = {
|
|
34
|
-
port: options.port
|
|
35
|
-
host: options.host || '
|
|
36
|
-
watchPaths: options.watchPaths || [
|
|
39
|
+
port: options.port ?? 3000, // 0 picks a free port
|
|
40
|
+
host: options.host || '127.0.0.1',
|
|
41
|
+
watchPaths: options.watchPaths || [],
|
|
42
|
+
maxBodyBytes: options.maxBodyBytes || 64 * 1024,
|
|
43
|
+
// Optional custom handler: async ({ question, topK }) => result
|
|
44
|
+
query: options.query || null,
|
|
37
45
|
configPath: options.configPath || '.ragrc.json',
|
|
38
46
|
verbose: options.verbose !== false,
|
|
39
47
|
autoReload: options.autoReload !== false,
|
|
@@ -147,12 +155,23 @@ class DevServer extends EventEmitter {
|
|
|
147
155
|
// Start hot reload
|
|
148
156
|
await this.hotReload.start();
|
|
149
157
|
|
|
158
|
+
this.server = http.createServer((req, res) => {
|
|
159
|
+
this._handleRequest(req, res).catch((error) => {
|
|
160
|
+
this._send(res, 500, { error: error.message });
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
await new Promise((resolve, reject) => {
|
|
164
|
+
this.server.once('error', reject);
|
|
165
|
+
this.server.listen(this.options.port, this.options.host, () => {
|
|
166
|
+
this.server.off('error', reject);
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
this.port = this.server.address().port;
|
|
171
|
+
|
|
150
172
|
this.isRunning = true;
|
|
151
173
|
|
|
152
|
-
this._log(
|
|
153
|
-
`Development server started on ${this.options.host}:${this.options.port}`,
|
|
154
|
-
'success',
|
|
155
|
-
);
|
|
174
|
+
this._log(`Development server listening on ${this.url}`, 'success');
|
|
156
175
|
this._log(`Watching: ${this.options.watchPaths.join(', ')}`, 'info');
|
|
157
176
|
this._log(
|
|
158
177
|
'Hot reload enabled - changes will be applied automatically',
|
|
@@ -178,6 +197,11 @@ class DevServer extends EventEmitter {
|
|
|
178
197
|
// Stop hot reload
|
|
179
198
|
await this.hotReload.stop();
|
|
180
199
|
|
|
200
|
+
if (this.server) {
|
|
201
|
+
await new Promise((resolve) => this.server.close(() => resolve()));
|
|
202
|
+
this.server = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
181
205
|
this.isRunning = false;
|
|
182
206
|
|
|
183
207
|
const uptime = this.startTime ? Date.now() - this.startTime : 0;
|
|
@@ -202,7 +226,8 @@ class DevServer extends EventEmitter {
|
|
|
202
226
|
isRunning: this.isRunning,
|
|
203
227
|
uptime,
|
|
204
228
|
host: this.options.host,
|
|
205
|
-
port: this.options.port,
|
|
229
|
+
port: this.port ?? this.options.port,
|
|
230
|
+
url: this.isRunning ? this.url : null,
|
|
206
231
|
watchPaths: this.options.watchPaths,
|
|
207
232
|
configPath: this.options.configPath,
|
|
208
233
|
hotReload: {
|
|
@@ -249,6 +274,143 @@ ${this.options.watchPaths.map((p) => `║ - ${p.padEnd(56)}║`).join('\n')}
|
|
|
249
274
|
* @param {string} type - Type ('plugin' or 'config')
|
|
250
275
|
* @returns {Promise<void>}
|
|
251
276
|
*/
|
|
277
|
+
/** @returns {string} Base URL of the running server */
|
|
278
|
+
get url() {
|
|
279
|
+
return `http://${this.options.host}:${this.port ?? this.options.port}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Route a request. Only GET /health and POST /query exist.
|
|
284
|
+
* @private
|
|
285
|
+
*/
|
|
286
|
+
async _handleRequest(req, res) {
|
|
287
|
+
// DNS rebinding: a web page can point its own domain at 127.0.0.1 and
|
|
288
|
+
// then call this server same-origin. When bound to loopback, only accept
|
|
289
|
+
// requests addressed to a loopback name.
|
|
290
|
+
if (
|
|
291
|
+
LOOPBACK_HOSTS.has(this.options.host) &&
|
|
292
|
+
!this._isLoopbackHostHeader(req)
|
|
293
|
+
) {
|
|
294
|
+
return this._send(res, 403, { error: 'Forbidden host' });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { pathname } = new URL(req.url, 'http://localhost');
|
|
298
|
+
|
|
299
|
+
if (pathname === '/health') {
|
|
300
|
+
if (req.method !== 'GET')
|
|
301
|
+
return this._send(res, 405, { error: 'Use GET' });
|
|
302
|
+
const uptime = this.startTime ? Date.now() - this.startTime : 0;
|
|
303
|
+
return this._send(res, 200, { status: 'ok', uptime });
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (pathname === '/query') {
|
|
307
|
+
if (req.method !== 'POST')
|
|
308
|
+
return this._send(res, 405, { error: 'Use POST' });
|
|
309
|
+
let body;
|
|
310
|
+
try {
|
|
311
|
+
body = await this._readJson(req);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return this._send(res, error.statusCode || 400, {
|
|
314
|
+
error: error.message,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const { question, topK } = body || {};
|
|
318
|
+
if (typeof question !== 'string' || !question.trim()) {
|
|
319
|
+
return this._send(res, 400, {
|
|
320
|
+
error: '"question" must be a non-empty string',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (
|
|
324
|
+
topK !== undefined &&
|
|
325
|
+
!(Number.isInteger(topK) && topK > 0 && topK <= 50)
|
|
326
|
+
) {
|
|
327
|
+
return this._send(res, 400, {
|
|
328
|
+
error: '"topK" must be an integer from 1 to 50',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const result = await this._runQuery({ question, topK: topK || 3 });
|
|
332
|
+
return this._send(res, 200, result);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return this._send(res, 404, { error: 'Not found' });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** @private */
|
|
339
|
+
_isLoopbackHostHeader(req) {
|
|
340
|
+
const header = req.headers.host || '';
|
|
341
|
+
const hostname = header.startsWith('[')
|
|
342
|
+
? header.slice(0, header.indexOf(']') + 1)
|
|
343
|
+
: header.split(':')[0];
|
|
344
|
+
return LOOPBACK_HOSTS.has(hostname.toLowerCase());
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Answer with the custom `query` option, or the pipeline described by the
|
|
349
|
+
* config file (reloaded per request, so config edits apply immediately).
|
|
350
|
+
* @private
|
|
351
|
+
*/
|
|
352
|
+
async _runQuery({ question, topK }) {
|
|
353
|
+
if (this.options.query) return this.options.query({ question, topK });
|
|
354
|
+
const { loadCliConfig } = require('../cli/shared/config-context');
|
|
355
|
+
const { query } = require('../cli/services/rag-service');
|
|
356
|
+
const { config, configDir } = loadCliConfig({
|
|
357
|
+
config: this.options.configPath,
|
|
358
|
+
});
|
|
359
|
+
// The HTTP response keeps its shape: answer, results, citations.
|
|
360
|
+
const { timings, durationMs, ...result } = await query({
|
|
361
|
+
config,
|
|
362
|
+
configDir,
|
|
363
|
+
question,
|
|
364
|
+
topK,
|
|
365
|
+
});
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** @private */
|
|
370
|
+
_readJson(req) {
|
|
371
|
+
return new Promise((resolve, reject) => {
|
|
372
|
+
const type = req.headers['content-type'] || '';
|
|
373
|
+
if (!type.includes('application/json')) {
|
|
374
|
+
const error = new Error('Content-Type must be application/json');
|
|
375
|
+
error.statusCode = 415;
|
|
376
|
+
return reject(error);
|
|
377
|
+
}
|
|
378
|
+
let size = 0;
|
|
379
|
+
const chunks = [];
|
|
380
|
+
req.on('data', (chunk) => {
|
|
381
|
+
size += chunk.length;
|
|
382
|
+
if (size > this.options.maxBodyBytes) {
|
|
383
|
+
const error = new Error('Request body too large');
|
|
384
|
+
error.statusCode = 413;
|
|
385
|
+
reject(error);
|
|
386
|
+
// Stop buffering but keep draining, so the 413 response is delivered
|
|
387
|
+
req.removeAllListeners('data');
|
|
388
|
+
req.resume();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
chunks.push(chunk);
|
|
392
|
+
});
|
|
393
|
+
req.on('end', () => {
|
|
394
|
+
try {
|
|
395
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}'));
|
|
396
|
+
} catch {
|
|
397
|
+
reject(new Error('Invalid JSON body'));
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
req.on('error', reject);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** @private */
|
|
405
|
+
_send(res, status, payload) {
|
|
406
|
+
if (res.headersSent) return;
|
|
407
|
+
res.writeHead(status, {
|
|
408
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
409
|
+
'X-Content-Type-Options': 'nosniff',
|
|
410
|
+
});
|
|
411
|
+
res.end(JSON.stringify(payload));
|
|
412
|
+
}
|
|
413
|
+
|
|
252
414
|
async triggerReload(filePath, type = 'plugin') {
|
|
253
415
|
this._log(`Manually triggering reload: ${filePath}`, 'info');
|
|
254
416
|
await this.hotReload.triggerReload(filePath, type);
|
package/src/dev/hot-reload.js
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const { EventEmitter } = require('events');
|
|
21
|
-
|
|
21
|
+
// chokidar loads when watching starts, not on import.
|
|
22
|
+
const loadChokidar = () => require('chokidar'); // eslint-disable-line global-require
|
|
22
23
|
const path = require('path');
|
|
23
24
|
const fs = require('fs');
|
|
24
25
|
const {
|
|
@@ -28,6 +29,42 @@ const {
|
|
|
28
29
|
} = require('../utils/error-formatter');
|
|
29
30
|
const { logger } = require('../utils/logger');
|
|
30
31
|
|
|
32
|
+
const RELOADABLE_EXTENSIONS = new Set(['.js', '.cjs', '.mjs']);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* chokidar 4 removed glob support, so glob strings in `ignored` were treated
|
|
36
|
+
* as literal paths and matched nothing. Convert them to one matcher function.
|
|
37
|
+
*
|
|
38
|
+
* @param {Array<string|RegExp|Function>} patterns
|
|
39
|
+
* @returns {(filePath: string) => boolean}
|
|
40
|
+
*/
|
|
41
|
+
function toIgnoreMatcher(patterns) {
|
|
42
|
+
const matchers = patterns.map((pattern) => {
|
|
43
|
+
if (typeof pattern === 'function') return pattern;
|
|
44
|
+
if (pattern instanceof RegExp) return (p) => pattern.test(p);
|
|
45
|
+
const regex = new RegExp(
|
|
46
|
+
'^' +
|
|
47
|
+
pattern
|
|
48
|
+
.split('**/')
|
|
49
|
+
.map((part) =>
|
|
50
|
+
part
|
|
51
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
52
|
+
.replace(/\*\*/g, '\u0000') // a remaining ** (e.g. trailing) spans directories
|
|
53
|
+
.replace(/\*/g, '[^/]*')
|
|
54
|
+
.replace(/\?/g, '[^/]')
|
|
55
|
+
.replace(/\u0000/g, '.*'),
|
|
56
|
+
)
|
|
57
|
+
.join('(?:.*/)?') +
|
|
58
|
+
'$',
|
|
59
|
+
);
|
|
60
|
+
return (p) => regex.test(p);
|
|
61
|
+
});
|
|
62
|
+
return (filePath) => {
|
|
63
|
+
const normalized = filePath.split(path.sep).join('/');
|
|
64
|
+
return matchers.some((match) => match(normalized));
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
31
68
|
/**
|
|
32
69
|
* Hot Reload Manager
|
|
33
70
|
*
|
|
@@ -134,8 +171,8 @@ class HotReloadManager extends EventEmitter {
|
|
|
134
171
|
* @returns {Promise<void>}
|
|
135
172
|
*/
|
|
136
173
|
async _watchPlugins() {
|
|
137
|
-
const watcher =
|
|
138
|
-
ignored: this.options.ignorePatterns,
|
|
174
|
+
const watcher = loadChokidar().watch(this.options.watchPaths, {
|
|
175
|
+
ignored: toIgnoreMatcher(this.options.ignorePatterns),
|
|
139
176
|
persistent: true,
|
|
140
177
|
ignoreInitial: true,
|
|
141
178
|
awaitWriteFinish: {
|
|
@@ -178,7 +215,7 @@ class HotReloadManager extends EventEmitter {
|
|
|
178
215
|
return;
|
|
179
216
|
}
|
|
180
217
|
|
|
181
|
-
const watcher =
|
|
218
|
+
const watcher = loadChokidar().watch(this.options.configPath, {
|
|
182
219
|
persistent: true,
|
|
183
220
|
ignoreInitial: true,
|
|
184
221
|
awaitWriteFinish: {
|
|
@@ -206,6 +243,8 @@ class HotReloadManager extends EventEmitter {
|
|
|
206
243
|
* @param {string} filePath - Changed file path
|
|
207
244
|
*/
|
|
208
245
|
_handlePluginChange(filePath) {
|
|
246
|
+
// Only JavaScript modules can be re-required; ignore data and docs.
|
|
247
|
+
if (!RELOADABLE_EXTENSIONS.has(path.extname(filePath))) return;
|
|
209
248
|
this._log(`Plugin changed: ${filePath}`);
|
|
210
249
|
|
|
211
250
|
// Debounce rapid changes
|
|
@@ -803,6 +842,7 @@ function createHotReloadManager(options = {}) {
|
|
|
803
842
|
}
|
|
804
843
|
|
|
805
844
|
module.exports = {
|
|
845
|
+
toIgnoreMatcher,
|
|
806
846
|
HotReloadManager,
|
|
807
847
|
createHotReloadManager,
|
|
808
848
|
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Automated certification checks for a local plugin. Every score is computed
|
|
5
|
+
* from the plugin's files: the stage contract (checked in a separate
|
|
6
|
+
* process), the static security scan, tests and documentation. Nothing is
|
|
7
|
+
* assumed; checks that cannot be computed score 0 with a reason.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { scanPlugin } = require('./plugin-scanner');
|
|
13
|
+
const { probeContract } = require('./plugin-probe');
|
|
14
|
+
const { relativeWithin } = require('./tar-extract');
|
|
15
|
+
|
|
16
|
+
/** Automated score needed for each level (manual review/audit are separate). */
|
|
17
|
+
const LEVELS = [
|
|
18
|
+
{
|
|
19
|
+
level: 'ENTERPRISE',
|
|
20
|
+
score: 95,
|
|
21
|
+
requires: 'manual review and security audit',
|
|
22
|
+
},
|
|
23
|
+
{ level: 'VERIFIED', score: 80, requires: 'manual review' },
|
|
24
|
+
{ level: 'BASIC', score: 60, requires: null },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const TEST_FILE = /(^|[\\/])(__tests__|tests?)[\\/]|\.(test|spec)\.[cm]?js$/;
|
|
28
|
+
|
|
29
|
+
function listFiles(dir) {
|
|
30
|
+
const out = [];
|
|
31
|
+
const walk = (current) => {
|
|
32
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
33
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
34
|
+
const full = path.join(current, entry.name);
|
|
35
|
+
if (entry.isDirectory()) walk(full);
|
|
36
|
+
else if (entry.isFile()) out.push(relativeWithin(dir, full));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
walk(dir);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function securityCheck(root) {
|
|
44
|
+
const scan = scanPlugin(root);
|
|
45
|
+
const { high, medium, low } = scan.counts;
|
|
46
|
+
const score = Math.max(0, 100 - 40 * high - 10 * medium - 2 * low);
|
|
47
|
+
return {
|
|
48
|
+
id: 'security',
|
|
49
|
+
label: 'Security scan',
|
|
50
|
+
weight: 30,
|
|
51
|
+
score,
|
|
52
|
+
details: scan.findings.map(
|
|
53
|
+
(f) =>
|
|
54
|
+
`${f.severity}: ${f.message} (${f.file}${f.line ? `:${f.line}` : ''})`,
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function testsCheck(files, manifest) {
|
|
60
|
+
const testFiles = files.filter((f) => TEST_FILE.test(f));
|
|
61
|
+
const script = manifest.scripts?.test;
|
|
62
|
+
const realScript = Boolean(script) && !/no test specified/.test(script);
|
|
63
|
+
let score = 0;
|
|
64
|
+
if (testFiles.length > 0) score = realScript ? 100 : 60;
|
|
65
|
+
return {
|
|
66
|
+
id: 'tests',
|
|
67
|
+
label: 'Tests',
|
|
68
|
+
weight: 15,
|
|
69
|
+
score,
|
|
70
|
+
details: [
|
|
71
|
+
`${testFiles.length} test file(s)`,
|
|
72
|
+
realScript
|
|
73
|
+
? `test script: ${script}`
|
|
74
|
+
: 'no "test" script in package.json',
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function docsCheck(root, files, manifest) {
|
|
80
|
+
const readme = files.find((f) => /^readme(\.md|\.txt)?$/i.test(f));
|
|
81
|
+
const readmeLength = readme
|
|
82
|
+
? fs.readFileSync(path.join(root, readme), 'utf8').trim().length
|
|
83
|
+
: 0;
|
|
84
|
+
const parts = [
|
|
85
|
+
[Boolean(readme), 50, readme ? `${readme} present` : 'no README'],
|
|
86
|
+
[
|
|
87
|
+
readmeLength >= 300,
|
|
88
|
+
20,
|
|
89
|
+
`README length ${readmeLength} chars (300+ expected)`,
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
Boolean(manifest.description),
|
|
93
|
+
15,
|
|
94
|
+
manifest.description
|
|
95
|
+
? 'package description present'
|
|
96
|
+
: 'no package description',
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
Boolean(manifest.license),
|
|
100
|
+
15,
|
|
101
|
+
manifest.license ? `license: ${manifest.license}` : 'no license field',
|
|
102
|
+
],
|
|
103
|
+
];
|
|
104
|
+
return {
|
|
105
|
+
id: 'documentation',
|
|
106
|
+
label: 'Documentation',
|
|
107
|
+
weight: 20,
|
|
108
|
+
score: parts.reduce((sum, [ok, points]) => sum + (ok ? points : 0), 0),
|
|
109
|
+
details: parts.map(([ok, , text]) => `${ok ? 'ok' : 'missing'}: ${text}`),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} pluginPath - Plugin directory or single .js file
|
|
115
|
+
* @param {object} options
|
|
116
|
+
* @param {string} [options.stage] - Overrides package.json "ragPipeline.stage"
|
|
117
|
+
* @returns {Promise<{plugin: string, stage: string, checks: object[], score: number, level: string|null, nextLevel: object|null}>}
|
|
118
|
+
*/
|
|
119
|
+
async function runPluginChecks(pluginPath, options = {}) {
|
|
120
|
+
const absolute = path.resolve(pluginPath);
|
|
121
|
+
if (!fs.existsSync(absolute))
|
|
122
|
+
throw new Error(`Plugin not found: ${pluginPath}`);
|
|
123
|
+
const isDir = fs.statSync(absolute).isDirectory();
|
|
124
|
+
const root = isDir ? absolute : path.dirname(absolute);
|
|
125
|
+
const manifestPath = path.join(root, 'package.json');
|
|
126
|
+
const manifest =
|
|
127
|
+
isDir && fs.existsSync(manifestPath)
|
|
128
|
+
? JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
|
129
|
+
: {};
|
|
130
|
+
const stage = options.stage || manifest.ragPipeline?.stage;
|
|
131
|
+
if (!stage) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
'Unknown stage. Pass --stage or set "ragPipeline.stage" in package.json.',
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const entry = isDir
|
|
137
|
+
? path.resolve(root, manifest.main || 'index.js')
|
|
138
|
+
: absolute;
|
|
139
|
+
const files = isDir ? listFiles(root) : [path.basename(absolute)];
|
|
140
|
+
|
|
141
|
+
const probe = await probeContract(entry, stage);
|
|
142
|
+
const contract = {
|
|
143
|
+
id: 'contract',
|
|
144
|
+
label: `${stage} contract`,
|
|
145
|
+
weight: 35,
|
|
146
|
+
score: probe.ok ? 100 : 0,
|
|
147
|
+
details: probe.ok
|
|
148
|
+
? [`methods: ${probe.methods.join(', ')}`]
|
|
149
|
+
: [probe.error || `missing: ${probe.missing.join(', ')}`],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const checks = [
|
|
153
|
+
contract,
|
|
154
|
+
isDir ? securityCheck(root) : securityCheck(absolute),
|
|
155
|
+
testsCheck(files, manifest),
|
|
156
|
+
docsCheck(root, isDir ? files : [], manifest),
|
|
157
|
+
];
|
|
158
|
+
const totalWeight = checks.reduce((sum, c) => sum + c.weight, 0);
|
|
159
|
+
const score = Math.round(
|
|
160
|
+
checks.reduce((sum, c) => sum + c.score * c.weight, 0) / totalWeight,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// A failed contract means the plugin cannot run, whatever else scores.
|
|
164
|
+
const eligible =
|
|
165
|
+
contract.score === 100 ? LEVELS.filter((l) => score >= l.score) : [];
|
|
166
|
+
const best = eligible[0] || null;
|
|
167
|
+
const nextLevel = [...LEVELS].reverse().find((l) => score < l.score) || null;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
plugin: manifest.name || path.basename(absolute),
|
|
171
|
+
version: manifest.version || null,
|
|
172
|
+
stage,
|
|
173
|
+
checks,
|
|
174
|
+
score,
|
|
175
|
+
level: best ? best.level : null,
|
|
176
|
+
pendingReview: best ? best.requires : null,
|
|
177
|
+
nextLevel: nextLevel && { level: nextLevel.level, score: nextLevel.score },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { runPluginChecks, LEVELS };
|