@devilsdev/rag-pipeline-utils 2.4.4 → 2.5.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 +345 -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 +4992 -1242
- package/dist/index.d.ts +80 -2
- package/dist/index.mjs +1 -1
- 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/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 +92 -0
- package/dist/types/connectors/base-connector.d.ts +87 -0
- package/dist/types/connectors/cohere-connector.d.ts +89 -0
- package/dist/types/connectors/connector-utils.d.ts +110 -0
- package/dist/types/connectors/local-embedder.d.ts +86 -0
- package/dist/types/connectors/memory-retriever.d.ts +91 -0
- package/dist/types/connectors/ollama-connector.d.ts +110 -0
- package/dist/types/connectors/openai-connector.d.ts +114 -0
- package/dist/types/connectors/rag-prompt.d.ts +40 -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/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/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 +87 -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/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 +53 -29
- 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/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 +101 -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 +96 -36
- 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 +72 -217
- 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 +41 -26
- 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/rag-service.js +322 -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 +617 -0
- package/src/config/component-factory.js +125 -0
- package/src/config/load-config.js +29 -10
- package/src/config/load-plugin-config.js +46 -2
- package/src/connectors/anthropic-connector.js +93 -55
- package/src/connectors/base-connector.js +74 -19
- package/src/connectors/cohere-connector.js +88 -32
- package/src/connectors/connector-utils.js +302 -0
- package/src/connectors/local-embedder.js +43 -5
- package/src/connectors/memory-retriever.js +108 -15
- package/src/connectors/ollama-connector.js +83 -49
- package/src/connectors/openai-connector.js +108 -44
- package/src/connectors/rag-prompt.js +88 -0
- package/src/core/create-pipeline.js +209 -60
- package/src/core/plugin-registry.js +87 -11
- package/src/cost/cost-calculator.js +205 -32
- package/src/dev/dev-server.js +166 -11
- package/src/dev/hot-reload.js +40 -1
- 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 +715 -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/experimental.js +71 -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 +36 -1
- package/src/loader/document-reader.js +91 -0
- 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 +212 -0
- package/src/retrieval/hybrid-retriever.js +22 -19
- package/src/security/jwt-config.js +13 -2
- package/src/security/jwt-validator.js +134 -30
- package/src/security/plugin-sandbox.js +18 -1
- package/src/security/plugin-signature-verifier.js +245 -70
- 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
|
@@ -145,6 +145,23 @@ class PluginHub extends EventEmitter {
|
|
|
145
145
|
/**
|
|
146
146
|
* Install plugin from the hub
|
|
147
147
|
*/
|
|
148
|
+
/**
|
|
149
|
+
* Download a plugin from the registry and install it into a project
|
|
150
|
+
* (see ecosystem/plugin-installer.js): sha256 verified against the
|
|
151
|
+
* registry, static security scan (high-risk findings block unless
|
|
152
|
+
* allowRisky), stage contract check, files kept inside
|
|
153
|
+
* <project>/.rag/plugins, and `plugins.<stage>.<name>` added to the config.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} pluginId - Registry plugin id
|
|
156
|
+
* @param {string} [version='latest']
|
|
157
|
+
* @param {object} [_options]
|
|
158
|
+
* @param {string} [_options.configPath='.ragrc.json'] - Config to register the plugin in
|
|
159
|
+
* @param {boolean} [_options.requireCertified] - Refuse plugins the registry marks uncertified
|
|
160
|
+
* @param {string} [_options.stage] - Overrides package.json ragPipeline.stage
|
|
161
|
+
* @param {string} [_options.name] - Overrides package.json ragPipeline.name
|
|
162
|
+
* @param {boolean} [_options.force] - Replace an installed plugin
|
|
163
|
+
* @param {boolean} [_options.allowRisky] - Install despite high-risk findings
|
|
164
|
+
*/
|
|
148
165
|
async installPlugin(pluginId, version = 'latest', _options = {}) {
|
|
149
166
|
const installId = crypto.randomUUID();
|
|
150
167
|
const startedAt = Date.now();
|
|
@@ -152,69 +169,43 @@ class PluginHub extends EventEmitter {
|
|
|
152
169
|
try {
|
|
153
170
|
this.emit('install_start', { pluginId, version, installId });
|
|
154
171
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Check certification if required
|
|
160
|
-
if (_options.requireCertified && !pluginInfo.certified) {
|
|
161
|
-
throw new Error(`Plugin ${pluginId} is not certified`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Security scan in sandbox
|
|
165
|
-
if (_options.securityScan !== false) {
|
|
166
|
-
this.emit('install_progress', { installId, stage: 'security_scan' });
|
|
167
|
-
await this.sandbox.scanPlugin(pluginInfo);
|
|
172
|
+
if (_options.requireCertified) {
|
|
173
|
+
const info = await this.getPluginInfo(pluginId);
|
|
174
|
+
if (!info.certified)
|
|
175
|
+
throw new Error(`Plugin ${pluginId} is not certified`);
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
timeout: _options.sandboxTimeout || 30000,
|
|
183
|
-
memoryLimit: _options.memoryLimit || '512MB',
|
|
184
|
-
networkAccess: _options.networkAccess || false,
|
|
178
|
+
const { installPlugin } = require('./plugin-installer');
|
|
179
|
+
const result = await installPlugin({
|
|
180
|
+
source: pluginId,
|
|
181
|
+
version,
|
|
182
|
+
hub: this,
|
|
183
|
+
configPath: _options.configPath || '.ragrc.json',
|
|
184
|
+
stage: _options.stage,
|
|
185
|
+
name: _options.name,
|
|
186
|
+
force: _options.force,
|
|
187
|
+
allowRisky: _options.allowRisky,
|
|
188
|
+
onProgress: (stage) =>
|
|
189
|
+
this.emit('install_progress', { installId, stage }),
|
|
185
190
|
});
|
|
186
191
|
|
|
187
|
-
if (!sandboxResult.success) {
|
|
188
|
-
throw new Error(`Sandbox installation failed: ${sandboxResult.error}`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Install to main system
|
|
192
|
-
this.emit('install_progress', { installId, stage: 'installing' });
|
|
193
|
-
const installPath = await this._installPluginToSystem(
|
|
194
|
-
pluginData,
|
|
195
|
-
pluginInfo,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
// Track installation analytics
|
|
199
192
|
this.analytics.trackInstallation(pluginId, version, {
|
|
200
193
|
success: true,
|
|
201
194
|
installTime: Date.now() - startedAt,
|
|
202
195
|
});
|
|
203
|
-
|
|
204
196
|
this.emit('install_complete', {
|
|
205
197
|
pluginId,
|
|
206
198
|
version,
|
|
207
199
|
installId,
|
|
208
|
-
installPath,
|
|
209
|
-
pluginInfo,
|
|
200
|
+
installPath: result.path,
|
|
210
201
|
});
|
|
211
202
|
|
|
212
203
|
return {
|
|
213
204
|
success: true,
|
|
214
205
|
pluginId,
|
|
215
206
|
version,
|
|
216
|
-
installPath,
|
|
217
|
-
|
|
207
|
+
installPath: result.path,
|
|
208
|
+
...result,
|
|
218
209
|
};
|
|
219
210
|
} catch (error) {
|
|
220
211
|
this.analytics.trackInstallation(pluginId, version, {
|
|
@@ -344,46 +335,16 @@ class PluginHub extends EventEmitter {
|
|
|
344
335
|
/**
|
|
345
336
|
* Get user's installed plugins
|
|
346
337
|
*/
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (entry.isDirectory()) {
|
|
356
|
-
const metadataPath = path.join(
|
|
357
|
-
pluginsDir,
|
|
358
|
-
entry.name,
|
|
359
|
-
'metadata.json',
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
try {
|
|
363
|
-
const metadata = JSON.parse(
|
|
364
|
-
await fs.readFile(metadataPath, 'utf8'),
|
|
365
|
-
);
|
|
366
|
-
installed.push({
|
|
367
|
-
...metadata,
|
|
368
|
-
installPath: path.join(pluginsDir, entry.name),
|
|
369
|
-
lastUsed: await this.analytics.getLastUsed(metadata.id),
|
|
370
|
-
});
|
|
371
|
-
} catch {
|
|
372
|
-
// Skip invalid plugins
|
|
373
|
-
continue; // eslint-disable-line no-continue
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return installed;
|
|
379
|
-
} catch {
|
|
380
|
-
return [];
|
|
381
|
-
}
|
|
338
|
+
/**
|
|
339
|
+
* Plugins registered in a project's config (see plugin-installer
|
|
340
|
+
* listInstalledPlugins).
|
|
341
|
+
* @param {string} [configPath='.ragrc.json']
|
|
342
|
+
*/
|
|
343
|
+
async getInstalledPlugins(configPath = '.ragrc.json') {
|
|
344
|
+
const { listInstalledPlugins } = require('./plugin-installer');
|
|
345
|
+
return listInstalledPlugins(configPath);
|
|
382
346
|
}
|
|
383
347
|
|
|
384
|
-
// ------------------------------
|
|
385
|
-
// Private methods
|
|
386
|
-
// ------------------------------
|
|
387
348
|
async _makeRequest(method, endpoint, params = {}) {
|
|
388
349
|
const doFetch = pickFetch(this._config.fetch);
|
|
389
350
|
if (!doFetch) throw new Error('fetch is not available in this environment');
|
|
@@ -540,35 +501,6 @@ class PluginHub extends EventEmitter {
|
|
|
540
501
|
}
|
|
541
502
|
}
|
|
542
503
|
|
|
543
|
-
async _installPluginToSystem(pluginData, pluginInfo) {
|
|
544
|
-
const installDir = path.join(
|
|
545
|
-
this._config.localCacheDir,
|
|
546
|
-
'plugins',
|
|
547
|
-
pluginInfo.id,
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
// Create installation directory
|
|
551
|
-
await fs.mkdir(installDir, { recursive: true });
|
|
552
|
-
|
|
553
|
-
// Simulated install: write metadata only
|
|
554
|
-
const metadataPath = path.join(installDir, 'metadata.json');
|
|
555
|
-
await fs.writeFile(
|
|
556
|
-
metadataPath,
|
|
557
|
-
JSON.stringify(
|
|
558
|
-
{
|
|
559
|
-
...pluginInfo,
|
|
560
|
-
installedAt: new Date().toISOString(),
|
|
561
|
-
installPath: installDir,
|
|
562
|
-
size: Buffer.isBuffer(pluginData) ? pluginData.length : 0,
|
|
563
|
-
},
|
|
564
|
-
null,
|
|
565
|
-
2,
|
|
566
|
-
),
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
return installDir;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
504
|
async _validatePluginForPublish(pluginPath) {
|
|
573
505
|
// Validate plugin structure and metadata
|
|
574
506
|
const packageJsonPath = path.join(pluginPath, 'package.json');
|
|
@@ -608,19 +540,12 @@ class PluginHub extends EventEmitter {
|
|
|
608
540
|
});
|
|
609
541
|
}
|
|
610
542
|
|
|
611
|
-
async _getInstalledVersion(pluginId) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
'metadata.json',
|
|
618
|
-
);
|
|
619
|
-
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
|
|
620
|
-
return metadata.version;
|
|
621
|
-
} catch {
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
543
|
+
async _getInstalledVersion(pluginId, configPath = '.ragrc.json') {
|
|
544
|
+
const { listInstalledPlugins } = require('./plugin-installer');
|
|
545
|
+
const match = listInstalledPlugins(configPath).find(
|
|
546
|
+
(p) => p.name === pluginId,
|
|
547
|
+
);
|
|
548
|
+
return match ? match.version : null;
|
|
624
549
|
}
|
|
625
550
|
}
|
|
626
551
|
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Install a plugin into a project and register it in .ragrc.json.
|
|
5
|
+
*
|
|
6
|
+
* Sources: a registry plugin id (needs a PluginHub; the download is verified
|
|
7
|
+
* against the registry's sha256), a local directory, a .tgz / .tar.gz
|
|
8
|
+
* package, a single .js file, or an npm package ("npm:<spec>").
|
|
9
|
+
*
|
|
10
|
+
* Steps: stage the files inside <project>/.rag/plugins, statically scan
|
|
11
|
+
* them (high-risk findings block the install unless allowRisky), check the
|
|
12
|
+
* entry module exposes the stage's required methods (in a separate process),
|
|
13
|
+
* move into place and add `plugins.<stage>.<name>` to the config. Install
|
|
14
|
+
* scripts are never run. Installed plugins run with your privileges when the
|
|
15
|
+
* pipeline uses them; the scan informs that decision, it does not sandbox.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const crypto = require('crypto');
|
|
22
|
+
const { execFile } = require('child_process');
|
|
23
|
+
const { promisify } = require('util');
|
|
24
|
+
const { extractTarGz, isWithin, relativeWithin } = require('./tar-extract');
|
|
25
|
+
const { scanPlugin } = require('./plugin-scanner');
|
|
26
|
+
const { probeContract, STAGE_METHODS } = require('./plugin-probe');
|
|
27
|
+
|
|
28
|
+
const NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
29
|
+
|
|
30
|
+
/** child is strictly inside parent (both resolved paths). */
|
|
31
|
+
function isInside(parent, child) {
|
|
32
|
+
return (
|
|
33
|
+
isWithin(parent, child) && path.resolve(parent) !== path.resolve(child)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Run npm without a shell (on Windows via node + npm-cli.js). */
|
|
38
|
+
async function runNpm(args, cwd) {
|
|
39
|
+
const run = promisify(execFile);
|
|
40
|
+
if (process.platform === 'win32') {
|
|
41
|
+
const npmCli = path.join(
|
|
42
|
+
path.dirname(process.execPath),
|
|
43
|
+
'node_modules',
|
|
44
|
+
'npm',
|
|
45
|
+
'bin',
|
|
46
|
+
'npm-cli.js',
|
|
47
|
+
);
|
|
48
|
+
return run(process.execPath, [npmCli, ...args], { cwd, windowsHide: true });
|
|
49
|
+
}
|
|
50
|
+
return run('npm', args, { cwd });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** If extraction produced a single top-level directory (e.g. "package/"), use it. */
|
|
54
|
+
function packageRoot(dir) {
|
|
55
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
56
|
+
if (entries.length === 1 && entries[0].isDirectory()) {
|
|
57
|
+
return path.join(dir, entries[0].name);
|
|
58
|
+
}
|
|
59
|
+
return dir;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function stageSource(source, stagingDir, options) {
|
|
63
|
+
const local = path.resolve(options.cwd || process.cwd(), source);
|
|
64
|
+
|
|
65
|
+
if (fs.existsSync(local)) {
|
|
66
|
+
const stat = fs.statSync(local);
|
|
67
|
+
if (stat.isDirectory()) {
|
|
68
|
+
fs.cpSync(local, stagingDir, {
|
|
69
|
+
recursive: true,
|
|
70
|
+
// Skip node_modules and .git at any depth (path is relative to local)
|
|
71
|
+
filter: (src) =>
|
|
72
|
+
!/(^|\/)(node_modules|\.git)(\/|$)/.test(
|
|
73
|
+
relativeWithin(local, src) || '',
|
|
74
|
+
),
|
|
75
|
+
});
|
|
76
|
+
return { kind: 'directory', root: stagingDir };
|
|
77
|
+
}
|
|
78
|
+
if (/\.(tgz|tar\.gz)$/i.test(local)) {
|
|
79
|
+
extractTarGz(fs.readFileSync(local), stagingDir);
|
|
80
|
+
return { kind: 'tarball', root: packageRoot(stagingDir) };
|
|
81
|
+
}
|
|
82
|
+
if (/\.(c?js)$/i.test(local)) {
|
|
83
|
+
fs.copyFileSync(local, path.join(stagingDir, 'index.js'));
|
|
84
|
+
return { kind: 'file', root: stagingDir };
|
|
85
|
+
}
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Unsupported plugin file: ${source} (expected a directory, .tgz or .js)`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (source.startsWith('npm:')) {
|
|
92
|
+
const spec = source.slice(4);
|
|
93
|
+
const packDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rag-npm-'));
|
|
94
|
+
try {
|
|
95
|
+
const { stdout } = await runNpm(
|
|
96
|
+
[
|
|
97
|
+
'pack',
|
|
98
|
+
spec,
|
|
99
|
+
'--silent',
|
|
100
|
+
'--ignore-scripts',
|
|
101
|
+
'--pack-destination',
|
|
102
|
+
packDir,
|
|
103
|
+
],
|
|
104
|
+
packDir,
|
|
105
|
+
);
|
|
106
|
+
const tarball = path.join(packDir, stdout.trim().split(/\r?\n/).pop());
|
|
107
|
+
extractTarGz(fs.readFileSync(tarball), stagingDir);
|
|
108
|
+
return { kind: 'npm', root: packageRoot(stagingDir) };
|
|
109
|
+
} finally {
|
|
110
|
+
fs.rmSync(packDir, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!options.hub) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`"${source}" is not a local path or npm:<package>, and no plugin registry is configured.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const hub = options.hub;
|
|
120
|
+
const info = await hub.getPluginInfo(source);
|
|
121
|
+
const url = await hub._getDownloadUrl(source, options.version || 'latest');
|
|
122
|
+
const archive = await hub._downloadPlugin(url);
|
|
123
|
+
await hub._verifyPluginIntegrity(archive, info.checksums); // throws on mismatch
|
|
124
|
+
extractTarGz(archive, stagingDir);
|
|
125
|
+
return { kind: 'registry', root: packageRoot(stagingDir), info };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {object} params
|
|
130
|
+
* @param {string} params.source - Registry id, path, .tgz, .js or "npm:<spec>"
|
|
131
|
+
* @param {string} params.configPath - .ragrc.json to register the plugin in
|
|
132
|
+
* @param {string} [params.stage] - Overrides package.json "ragPipeline.stage"
|
|
133
|
+
* @param {string} [params.name] - Overrides package.json "ragPipeline.name"
|
|
134
|
+
* @param {boolean} [params.force] - Replace an installed plugin with the same stage/name
|
|
135
|
+
* @param {boolean} [params.allowRisky] - Install despite high-risk scan findings
|
|
136
|
+
* @param {object} [params.hub] - PluginHub, required for registry ids
|
|
137
|
+
* @param {string} [params.version] - Registry version (default "latest")
|
|
138
|
+
* @param {function} [params.onProgress] - Called with a stage label
|
|
139
|
+
* @returns {Promise<object>} Installation summary
|
|
140
|
+
*/
|
|
141
|
+
async function installPlugin(params) {
|
|
142
|
+
const progress = params.onProgress || (() => {});
|
|
143
|
+
const configPath = path.resolve(params.configPath);
|
|
144
|
+
if (!fs.existsSync(configPath)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Config file not found: ${params.configPath}. Run "rag-pipeline init" first.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const configDir = path.dirname(configPath);
|
|
150
|
+
const pluginsDir = path.join(configDir, '.rag', 'plugins');
|
|
151
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
152
|
+
const stagingDir = path.join(
|
|
153
|
+
pluginsDir,
|
|
154
|
+
`.staging-${crypto.randomBytes(6).toString('hex')}`,
|
|
155
|
+
);
|
|
156
|
+
fs.mkdirSync(stagingDir);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
progress('fetching');
|
|
160
|
+
const staged = await stageSource(params.source, stagingDir, {
|
|
161
|
+
hub: params.hub,
|
|
162
|
+
version: params.version,
|
|
163
|
+
cwd: configDir,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const manifestPath = path.join(staged.root, 'package.json');
|
|
167
|
+
const manifest = fs.existsSync(manifestPath)
|
|
168
|
+
? JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
|
169
|
+
: {};
|
|
170
|
+
const stage = params.stage || manifest.ragPipeline?.stage;
|
|
171
|
+
const name = params.name || manifest.ragPipeline?.name;
|
|
172
|
+
if (!STAGE_METHODS[stage]) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Unknown or missing stage. Pass --stage (${Object.keys(STAGE_METHODS).join(', ')}) or set "ragPipeline.stage" in package.json.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (!name || !NAME_PATTERN.test(name)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
'Missing or invalid plugin name. Pass --name (letters, digits, ".", "_", "-") or set "ragPipeline.name" in package.json.',
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const entry = path.resolve(staged.root, manifest.main || 'index.js');
|
|
184
|
+
if (!isInside(staged.root, entry) || !fs.existsSync(entry)) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Plugin entry file not found: ${manifest.main || 'index.js'}`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
progress('scanning');
|
|
191
|
+
const scan = scanPlugin(staged.root);
|
|
192
|
+
if (scan.counts.high > 0 && !params.allowRisky) {
|
|
193
|
+
const error = new Error(
|
|
194
|
+
`Security scan found ${scan.counts.high} high-risk finding(s); not installed. Review them and re-run with --allow-risky to install anyway.`,
|
|
195
|
+
);
|
|
196
|
+
error.scan = scan;
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
progress('verifying');
|
|
201
|
+
const probe = await probeContract(entry, stage);
|
|
202
|
+
if (!probe.ok) {
|
|
203
|
+
const reason =
|
|
204
|
+
probe.error || `missing method(s): ${probe.missing.join(', ')}`;
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Plugin does not implement the ${stage} contract: ${reason}`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
progress('installing');
|
|
211
|
+
const destination = path.join(pluginsDir, `${stage}-${name}`);
|
|
212
|
+
if (fs.existsSync(destination)) {
|
|
213
|
+
if (!params.force) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`${stage} plugin "${name}" is already installed. Use --force to replace it.`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
219
|
+
}
|
|
220
|
+
fs.renameSync(staged.root, destination);
|
|
221
|
+
const installedEntry = path.join(
|
|
222
|
+
destination,
|
|
223
|
+
relativeWithin(staged.root, entry),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
227
|
+
raw.plugins = raw.plugins || {};
|
|
228
|
+
raw.plugins[stage] = raw.plugins[stage] || {};
|
|
229
|
+
raw.plugins[stage][name] = `./${relativeWithin(configDir, installedEntry)}`;
|
|
230
|
+
fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
stage,
|
|
234
|
+
name,
|
|
235
|
+
source: staged.kind,
|
|
236
|
+
version: manifest.version || staged.info?.version || null,
|
|
237
|
+
path: destination,
|
|
238
|
+
entry: raw.plugins[stage][name],
|
|
239
|
+
scan,
|
|
240
|
+
methods: probe.methods,
|
|
241
|
+
inPipeline: Array.isArray(raw.pipeline)
|
|
242
|
+
? raw.pipeline.some((p) => p.stage === stage && p.name === name)
|
|
243
|
+
: false,
|
|
244
|
+
};
|
|
245
|
+
} finally {
|
|
246
|
+
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = { installPlugin };
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Plugins registered under `plugins` in a config, with the installed
|
|
254
|
+
* package's version when it lives in .rag/plugins.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} configPath
|
|
257
|
+
* @returns {Array<{stage: string, name: string, entry: string, version: string|null, installed: boolean, inPipeline: boolean, exists: boolean}>}
|
|
258
|
+
*/
|
|
259
|
+
function listInstalledPlugins(configPath) {
|
|
260
|
+
const absolute = path.resolve(configPath);
|
|
261
|
+
if (!fs.existsSync(absolute)) return [];
|
|
262
|
+
const config = JSON.parse(fs.readFileSync(absolute, 'utf8'));
|
|
263
|
+
const configDir = path.dirname(absolute);
|
|
264
|
+
const pluginsDir = path.join(configDir, '.rag', 'plugins');
|
|
265
|
+
const pipeline = Array.isArray(config.pipeline) ? config.pipeline : [];
|
|
266
|
+
const list = [];
|
|
267
|
+
|
|
268
|
+
for (const [stage, plugins] of Object.entries(config.plugins || {})) {
|
|
269
|
+
for (const [name, entry] of Object.entries(plugins || {})) {
|
|
270
|
+
if (typeof entry !== 'string') continue;
|
|
271
|
+
const entryPath = path.resolve(configDir, entry);
|
|
272
|
+
const installedDir = path.join(pluginsDir, `${stage}-${name}`);
|
|
273
|
+
const installed = isInside(installedDir, entryPath);
|
|
274
|
+
let version = null;
|
|
275
|
+
const manifest = path.join(installedDir, 'package.json');
|
|
276
|
+
if (installed && fs.existsSync(manifest)) {
|
|
277
|
+
try {
|
|
278
|
+
version =
|
|
279
|
+
JSON.parse(fs.readFileSync(manifest, 'utf8')).version || null;
|
|
280
|
+
} catch {
|
|
281
|
+
// unreadable manifest: version unknown
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
list.push({
|
|
285
|
+
stage,
|
|
286
|
+
name,
|
|
287
|
+
entry,
|
|
288
|
+
version,
|
|
289
|
+
installed,
|
|
290
|
+
exists: fs.existsSync(entryPath),
|
|
291
|
+
inPipeline: pipeline.some((p) => p.stage === stage && p.name === name),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return list;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
module.exports.listInstalledPlugins = listInstalledPlugins;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Check that a plugin module exposes the methods its stage requires
|
|
5
|
+
* (contracts/<stage>-contract.json). Loading a module runs its code, so the
|
|
6
|
+
* check happens in a separate Node process with a timeout; this keeps the
|
|
7
|
+
* calling process clean but is NOT a security sandbox.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { execFile } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const STAGE_METHODS = {
|
|
15
|
+
loader: ['load'],
|
|
16
|
+
embedder: ['embed'],
|
|
17
|
+
retriever: ['store', 'retrieve'],
|
|
18
|
+
llm: ['generate'],
|
|
19
|
+
reranker: ['rerank'],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Runs in the child: load the module the way buildComponents() does and
|
|
23
|
+
// report which methods the resulting object has.
|
|
24
|
+
const MARKER = '@@rag-probe@@';
|
|
25
|
+
const PROBE_SCRIPT = `
|
|
26
|
+
const MARKER = '${MARKER}';
|
|
27
|
+
const mod = require(process.argv[1]);
|
|
28
|
+
const exported = mod && mod.default ? mod.default : mod;
|
|
29
|
+
let instance = exported;
|
|
30
|
+
if (typeof exported === 'function') {
|
|
31
|
+
instance = /^class\\s/.test(Function.prototype.toString.call(exported))
|
|
32
|
+
? new exported({})
|
|
33
|
+
: exported({});
|
|
34
|
+
}
|
|
35
|
+
const methods = [];
|
|
36
|
+
for (let o = instance; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
37
|
+
for (const key of Object.getOwnPropertyNames(o)) {
|
|
38
|
+
try { if (key !== 'constructor' && typeof instance[key] === 'function') methods.push(key); } catch {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
process.stdout.write(MARKER + JSON.stringify({ type: typeof instance, methods: [...new Set(methods)] }));
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
/** The "Error: ..." line of a Node stack trace, else the first line. */
|
|
45
|
+
function firstErrorLine(text) {
|
|
46
|
+
const lines = String(text)
|
|
47
|
+
.split(/\r?\n/)
|
|
48
|
+
.map((line) => line.trim())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
return (
|
|
51
|
+
lines.find((line) => /^\w*Error\b/.test(line)) ||
|
|
52
|
+
lines[0] ||
|
|
53
|
+
'unknown error'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} modulePath - Plugin entry file
|
|
59
|
+
* @param {string} stage - loader | embedder | retriever | llm | reranker
|
|
60
|
+
* @param {{timeoutMs?: number}} [options]
|
|
61
|
+
* @returns {Promise<{ok: boolean, missing: string[], methods: string[], error?: string}>}
|
|
62
|
+
*/
|
|
63
|
+
function probeContract(modulePath, stage, options = {}) {
|
|
64
|
+
const required = STAGE_METHODS[stage];
|
|
65
|
+
if (!required) {
|
|
66
|
+
return Promise.reject(
|
|
67
|
+
new Error(
|
|
68
|
+
`Unknown stage "${stage}". Expected: ${Object.keys(STAGE_METHODS).join(', ')}`,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const absolute = path.resolve(modulePath);
|
|
73
|
+
if (!fs.existsSync(absolute)) {
|
|
74
|
+
return Promise.resolve({
|
|
75
|
+
ok: false,
|
|
76
|
+
missing: required,
|
|
77
|
+
methods: [],
|
|
78
|
+
error: `Not found: ${modulePath}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
execFile(
|
|
84
|
+
process.execPath,
|
|
85
|
+
['-e', PROBE_SCRIPT, absolute],
|
|
86
|
+
{
|
|
87
|
+
timeout: options.timeoutMs || 10000,
|
|
88
|
+
cwd: path.dirname(absolute),
|
|
89
|
+
windowsHide: true,
|
|
90
|
+
},
|
|
91
|
+
(error, stdout, stderr) => {
|
|
92
|
+
if (error) {
|
|
93
|
+
const reason = error.killed
|
|
94
|
+
? 'timed out while loading'
|
|
95
|
+
: firstErrorLine(stderr || error.message);
|
|
96
|
+
resolve({
|
|
97
|
+
ok: false,
|
|
98
|
+
missing: required,
|
|
99
|
+
methods: [],
|
|
100
|
+
error: `Failed to load: ${reason}`,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
// The module may print while loading; the result follows the marker.
|
|
107
|
+
parsed = JSON.parse(
|
|
108
|
+
stdout.slice(stdout.lastIndexOf(MARKER) + MARKER.length),
|
|
109
|
+
);
|
|
110
|
+
} catch {
|
|
111
|
+
resolve({
|
|
112
|
+
ok: false,
|
|
113
|
+
missing: required,
|
|
114
|
+
methods: [],
|
|
115
|
+
error: 'Module printed unexpected output while loading',
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// An embedder may implement embedQuery only for queries; embed is required.
|
|
120
|
+
const missing = required.filter((m) => !parsed.methods.includes(m));
|
|
121
|
+
resolve({ ok: missing.length === 0, missing, methods: parsed.methods });
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { probeContract, STAGE_METHODS };
|