@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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Caching for createRagPipeline({ cache }): three opt-in layers.
|
|
5
|
+
*
|
|
6
|
+
* - embeddings: the query vector, keyed by the embedder's identity and the
|
|
7
|
+
* query text.
|
|
8
|
+
* - retrieval: the retriever's raw results, keyed by the retriever's state,
|
|
9
|
+
* the query and topK. Cached before `filterResults` (guardrail ACLs) and
|
|
10
|
+
* the reranker, so those still run on every request and one user's
|
|
11
|
+
* permissions can never shape another user's results.
|
|
12
|
+
* - generation: the answer, keyed by the llm's identity, the query, the
|
|
13
|
+
* exact documents it was given and the generation options. Skipped when
|
|
14
|
+
* the temperature is above 0.
|
|
15
|
+
*
|
|
16
|
+
* A layer caches only when its component identifies itself through
|
|
17
|
+
* `getCacheKey(role)` or a `cacheKey` string (the built-ins do), so a cache
|
|
18
|
+
* can never return results for a different model, index or configuration.
|
|
19
|
+
* Only successful, completed work is stored: nothing from an aborted or
|
|
20
|
+
* timed-out run.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const crypto = require('crypto');
|
|
24
|
+
const { MemoryCache } = require('./memory-cache');
|
|
25
|
+
|
|
26
|
+
const LAYERS = ['embeddings', 'retrieval', 'generation'];
|
|
27
|
+
const DEFAULT_TTL_SECONDS = {
|
|
28
|
+
embeddings: 30 * 24 * 3600,
|
|
29
|
+
retrieval: 3600,
|
|
30
|
+
generation: 24 * 3600,
|
|
31
|
+
};
|
|
32
|
+
const ROLE = {
|
|
33
|
+
embeddings: 'embedder',
|
|
34
|
+
retrieval: 'retriever',
|
|
35
|
+
generation: 'llm',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Generation options that change the answer and so belong in its key. */
|
|
39
|
+
const GENERATION_OPTIONS = [
|
|
40
|
+
'temperature',
|
|
41
|
+
'maxTokens',
|
|
42
|
+
'maxOutputTokens',
|
|
43
|
+
'topP',
|
|
44
|
+
'model',
|
|
45
|
+
'systemPrompt',
|
|
46
|
+
'seed',
|
|
47
|
+
'stopSequences',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function hash(value) {
|
|
51
|
+
return crypto
|
|
52
|
+
.createHash('sha256')
|
|
53
|
+
.update(JSON.stringify(value))
|
|
54
|
+
.digest('hex');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isBackend(value) {
|
|
58
|
+
return (
|
|
59
|
+
value && typeof value.get === 'function' && typeof value.set === 'function'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A component's identity for a role, or null when it has none. */
|
|
64
|
+
function componentKey(component, role) {
|
|
65
|
+
if (!component) return null;
|
|
66
|
+
if (typeof component.getCacheKey === 'function') {
|
|
67
|
+
const key = component.getCacheKey(role);
|
|
68
|
+
return typeof key === 'string' && key ? key : null;
|
|
69
|
+
}
|
|
70
|
+
return typeof component.cacheKey === 'string' && component.cacheKey
|
|
71
|
+
? component.cacheKey
|
|
72
|
+
: null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const warned = new Set();
|
|
76
|
+
function warnOnce(code, message) {
|
|
77
|
+
if (warned.has(code)) return;
|
|
78
|
+
warned.add(code);
|
|
79
|
+
process.emitWarning(message, { type: 'RagCacheWarning', code });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Normalise the `cache` option of createRagPipeline.
|
|
84
|
+
*
|
|
85
|
+
* - `true`: all three layers in one in-memory cache
|
|
86
|
+
* - a CacheBackend: all three layers in it
|
|
87
|
+
* - `{ embeddings, retrieval, generation, ttlSeconds }`: per layer, each
|
|
88
|
+
* `true` (in-memory), `false`/absent (off) or a CacheBackend;
|
|
89
|
+
* `ttlSeconds: { embeddings, retrieval, generation }` overrides the
|
|
90
|
+
* defaults (30 days, 1 hour, 24 hours)
|
|
91
|
+
*
|
|
92
|
+
* @returns {Object|null} Layer config, or null when caching is off
|
|
93
|
+
*/
|
|
94
|
+
function normalizeCacheOption(option) {
|
|
95
|
+
if (!option) return null;
|
|
96
|
+
const shared =
|
|
97
|
+
option === true ? new MemoryCache() : isBackend(option) ? option : null;
|
|
98
|
+
const layers = {};
|
|
99
|
+
for (const layer of LAYERS) {
|
|
100
|
+
let backend = null;
|
|
101
|
+
if (shared) {
|
|
102
|
+
backend = shared;
|
|
103
|
+
} else if (option[layer] === true) {
|
|
104
|
+
backend = new MemoryCache();
|
|
105
|
+
} else if (isBackend(option[layer])) {
|
|
106
|
+
backend = option[layer];
|
|
107
|
+
}
|
|
108
|
+
if (backend) {
|
|
109
|
+
layers[layer] = {
|
|
110
|
+
backend,
|
|
111
|
+
ttlSeconds:
|
|
112
|
+
(option.ttlSeconds && option.ttlSeconds[layer]) ??
|
|
113
|
+
DEFAULT_TTL_SECONDS[layer],
|
|
114
|
+
hits: 0,
|
|
115
|
+
misses: 0,
|
|
116
|
+
skipped: 0,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return Object.keys(layers).length ? layers : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Per-pipeline cache. Every method returns undefined and does nothing for a
|
|
125
|
+
* layer that is off or whose component has no cache key.
|
|
126
|
+
*/
|
|
127
|
+
class PipelineCache {
|
|
128
|
+
/**
|
|
129
|
+
* @param {Object} layers - normalizeCacheOption() output
|
|
130
|
+
* @param {{ embedder?: Object, retriever?: Object, llm?: Object }} components
|
|
131
|
+
*/
|
|
132
|
+
constructor(layers, components) {
|
|
133
|
+
this.layers = layers;
|
|
134
|
+
this.components = components;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** @private */
|
|
138
|
+
_key(layer, parts) {
|
|
139
|
+
const config = this.layers[layer];
|
|
140
|
+
if (!config) return null;
|
|
141
|
+
const component = this.components[ROLE[layer]];
|
|
142
|
+
const id = componentKey(component, ROLE[layer]);
|
|
143
|
+
if (!id) {
|
|
144
|
+
config.skipped += 1;
|
|
145
|
+
warnOnce(
|
|
146
|
+
`RAG_CACHE_NO_KEY_${layer.toUpperCase()}`,
|
|
147
|
+
`The ${layer} cache is off for this ${ROLE[layer]}: it has no getCacheKey(role) method or cacheKey string, so cached results could not be told apart from another configuration's. Add one to enable it.`,
|
|
148
|
+
);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return `${layer}:${hash([id, parts])}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** @private */
|
|
155
|
+
async _get(layer, key, status) {
|
|
156
|
+
if (!key) {
|
|
157
|
+
status[layer] = 'skip';
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const config = this.layers[layer];
|
|
161
|
+
let value;
|
|
162
|
+
try {
|
|
163
|
+
value = await config.backend.get(key);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// A cache outage must not fail the run.
|
|
166
|
+
warnOnce('RAG_CACHE_READ_FAILED', `Cache read failed: ${error.message}`);
|
|
167
|
+
value = undefined;
|
|
168
|
+
}
|
|
169
|
+
if (value === undefined) {
|
|
170
|
+
config.misses += 1;
|
|
171
|
+
status[layer] = 'miss';
|
|
172
|
+
} else {
|
|
173
|
+
config.hits += 1;
|
|
174
|
+
status[layer] = 'hit';
|
|
175
|
+
}
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** @private */
|
|
180
|
+
async _set(layer, key, value) {
|
|
181
|
+
if (!key || value === undefined) return;
|
|
182
|
+
const config = this.layers[layer];
|
|
183
|
+
try {
|
|
184
|
+
await config.backend.set(key, value, { ttlSeconds: config.ttlSeconds });
|
|
185
|
+
} catch (error) {
|
|
186
|
+
warnOnce(
|
|
187
|
+
'RAG_CACHE_WRITE_FAILED',
|
|
188
|
+
`Cache write failed: ${error.message}`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
embeddingKey(query) {
|
|
194
|
+
return this._key('embeddings', { query });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
retrievalKey({ query, queryVector, topK }) {
|
|
198
|
+
// Keyed on the query vector, not just the text, so a different embedder
|
|
199
|
+
// (or a retrained one) never gets another embedder's results; the text
|
|
200
|
+
// stays in the key for keyword and hybrid retrievers.
|
|
201
|
+
return this._key('retrieval', {
|
|
202
|
+
query: query ?? null,
|
|
203
|
+
vector: queryVector ? hash(queryVector) : null,
|
|
204
|
+
topK,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @returns {string|null} The key, or null to skip (temperature above 0,
|
|
210
|
+
* or no llm cache key)
|
|
211
|
+
*/
|
|
212
|
+
generationKey({ query, docs, options }) {
|
|
213
|
+
if (!this.layers.generation) return null;
|
|
214
|
+
const llm = this.components.llm;
|
|
215
|
+
const temperature =
|
|
216
|
+
options.temperature ?? (llm && llm.config && llm.config.temperature);
|
|
217
|
+
if (typeof temperature === 'number' && temperature > 0) {
|
|
218
|
+
this.layers.generation.skipped += 1;
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const generationOptions = {};
|
|
222
|
+
for (const name of GENERATION_OPTIONS) {
|
|
223
|
+
if (options[name] !== undefined) generationOptions[name] = options[name];
|
|
224
|
+
}
|
|
225
|
+
const context = (docs || []).map((d) =>
|
|
226
|
+
typeof d === 'string' ? d : [d.id ?? null, d.content ?? d.text ?? ''],
|
|
227
|
+
);
|
|
228
|
+
return this._key('generation', {
|
|
229
|
+
query,
|
|
230
|
+
context: hash(context),
|
|
231
|
+
options: generationOptions,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
get(layer, key, status) {
|
|
236
|
+
if (!this.layers[layer]) return Promise.resolve(undefined);
|
|
237
|
+
return this._get(layer, key, status);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
set(layer, key, value) {
|
|
241
|
+
if (!this.layers[layer]) return Promise.resolve();
|
|
242
|
+
return this._set(layer, key, value);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Hits, misses and skips per layer since the pipeline was created. */
|
|
246
|
+
stats() {
|
|
247
|
+
const out = {};
|
|
248
|
+
for (const [layer, c] of Object.entries(this.layers)) {
|
|
249
|
+
out[layer] = { hits: c.hits, misses: c.misses, skipped: c.skipped };
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = {
|
|
256
|
+
normalizeCacheOption,
|
|
257
|
+
PipelineCache,
|
|
258
|
+
componentKey,
|
|
259
|
+
DEFAULT_TTL_SECONDS,
|
|
260
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CacheBackend on Redis. Pass a connected client from `redis` (node-redis 4
|
|
5
|
+
* and later) or `ioredis`; neither is a dependency of this package.
|
|
6
|
+
*
|
|
7
|
+
* Values are stored as JSON under `prefix + key`, with a millisecond expiry
|
|
8
|
+
* when a time to live applies.
|
|
9
|
+
*/
|
|
10
|
+
class RedisCache {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} options
|
|
13
|
+
* @param {Object} options.client - Connected node-redis or ioredis client
|
|
14
|
+
* @param {string} [options.prefix='rag:'] - Key prefix
|
|
15
|
+
* @param {number} [options.ttlSeconds] - Default time to live
|
|
16
|
+
*/
|
|
17
|
+
constructor({ client, prefix = 'rag:', ttlSeconds } = {}) {
|
|
18
|
+
if (
|
|
19
|
+
!client ||
|
|
20
|
+
typeof client.get !== 'function' ||
|
|
21
|
+
typeof client.set !== 'function'
|
|
22
|
+
) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'RedisCache needs a connected client from "redis" or "ioredis"',
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
this.client = client;
|
|
28
|
+
this.prefix = prefix;
|
|
29
|
+
this.ttlSeconds = ttlSeconds;
|
|
30
|
+
// node-redis 4+ exposes isOpen and takes set(key, value, { PX }); ioredis
|
|
31
|
+
// takes set(key, value, 'PX', ms).
|
|
32
|
+
this._nodeRedis = typeof client.isOpen === 'boolean';
|
|
33
|
+
this._hits = 0;
|
|
34
|
+
this._misses = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} key
|
|
39
|
+
* @returns {Promise<unknown|undefined>}
|
|
40
|
+
*/
|
|
41
|
+
async get(key) {
|
|
42
|
+
const raw = await this.client.get(this.prefix + key);
|
|
43
|
+
if (raw === null || raw === undefined) {
|
|
44
|
+
this._misses += 1;
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
this._hits += 1;
|
|
48
|
+
return JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} key
|
|
53
|
+
* @param {unknown} value - JSON-serialisable value
|
|
54
|
+
* @param {{ ttlSeconds?: number }} [options]
|
|
55
|
+
*/
|
|
56
|
+
async set(key, value, { ttlSeconds = this.ttlSeconds } = {}) {
|
|
57
|
+
const json = JSON.stringify(value);
|
|
58
|
+
if (json === undefined) return;
|
|
59
|
+
const k = this.prefix + key;
|
|
60
|
+
const ms = ttlSeconds > 0 ? Math.round(ttlSeconds * 1000) : 0;
|
|
61
|
+
if (!ms) {
|
|
62
|
+
await this.client.set(k, json);
|
|
63
|
+
} else if (this._nodeRedis) {
|
|
64
|
+
await this.client.set(k, json, { PX: ms });
|
|
65
|
+
} else {
|
|
66
|
+
await this.client.set(k, json, 'PX', ms);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @param {string} key */
|
|
71
|
+
async delete(key) {
|
|
72
|
+
const del = this.client.del || this.client.unlink;
|
|
73
|
+
if (typeof del === 'function')
|
|
74
|
+
await del.call(this.client, this.prefix + key);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Hits and misses seen by this instance (size is not tracked). */
|
|
78
|
+
async stats() {
|
|
79
|
+
return { hits: this._hits, misses: this._misses };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { RedisCache };
|
|
@@ -10,7 +10,7 @@ const { detectHallucinations } = require('./hallucination-detector');
|
|
|
10
10
|
* @type {object}
|
|
11
11
|
*/
|
|
12
12
|
const DEFAULT_CONFIG = {
|
|
13
|
-
similarityThreshold: 0.
|
|
13
|
+
similarityThreshold: 0.5,
|
|
14
14
|
maxCitationsPerSentence: 3,
|
|
15
15
|
useIDFWeighting: true,
|
|
16
16
|
};
|
|
@@ -24,7 +24,7 @@ const DEFAULT_CONFIG = {
|
|
|
24
24
|
class CitationTracker extends EventEmitter {
|
|
25
25
|
/**
|
|
26
26
|
* @param {object} [options] - Override default configuration
|
|
27
|
-
* @param {number} [options.similarityThreshold=0.
|
|
27
|
+
* @param {number} [options.similarityThreshold=0.5] - Minimum similarity for a citation match
|
|
28
28
|
* @param {number} [options.maxCitationsPerSentence=3] - Max citations per sentence
|
|
29
29
|
* @param {boolean} [options.useIDFWeighting=true] - Whether to apply IDF weighting
|
|
30
30
|
*/
|
|
@@ -76,9 +76,11 @@ class CitationTracker extends EventEmitter {
|
|
|
76
76
|
'CitationTracker.track(): answer must be a non-empty string',
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
// An empty array is valid (retrieval found nothing): every sentence is
|
|
80
|
+
// then reported as unsupported rather than failing the whole run.
|
|
81
|
+
if (!Array.isArray(retrievedDocs)) {
|
|
80
82
|
throw new Error(
|
|
81
|
-
'CitationTracker.track(): retrievedDocs must be
|
|
83
|
+
'CitationTracker.track(): retrievedDocs must be an array',
|
|
82
84
|
);
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @param {Array<{sentence: string, sources: Array<{docId: string|undefined, docIndex: number, score: number}>}>} citationResults
|
|
12
12
|
* @param {object} [options]
|
|
13
|
-
* @param {number} [options.threshold=0.
|
|
13
|
+
* @param {number} [options.threshold=0.5] - Score threshold for grounded classification
|
|
14
14
|
* @returns {{
|
|
15
15
|
* hallucinationRate: number,
|
|
16
16
|
* sentences: Array<{text: string, classification: string, bestScore: number, bestSource: object|null}>,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* }}
|
|
19
19
|
*/
|
|
20
20
|
function detectHallucinations(citationResults, options = {}) {
|
|
21
|
-
const { threshold = 0.
|
|
21
|
+
const { threshold = 0.5 } = options;
|
|
22
22
|
|
|
23
23
|
if (!Array.isArray(citationResults) || citationResults.length === 0) {
|
|
24
24
|
return {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { tokenize,
|
|
3
|
+
const { tokenize, computeSupportScore } = require('../evaluate/scoring');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Build IDF (Inverse Document Frequency) weights from a collection of documents.
|
|
7
|
-
* IDF = log(totalDocs / docsContainingToken)
|
|
7
|
+
* IDF = log(1 + totalDocs / docsContainingToken). The +1 keeps weights
|
|
8
|
+
* positive, so tokens found in every document (and every token when only one
|
|
9
|
+
* document was retrieved) still count instead of weighing zero.
|
|
8
10
|
* @param {Array<{content?: string, text?: string}>} docs - Array of document objects
|
|
9
11
|
* @returns {Map<string, number>} Map of token to IDF weight
|
|
10
12
|
*/
|
|
@@ -24,24 +26,25 @@ function buildIDFWeights(docs) {
|
|
|
24
26
|
|
|
25
27
|
const idfWeights = new Map();
|
|
26
28
|
for (const [token, df] of docFrequency) {
|
|
27
|
-
idfWeights.set(token, Math.log(totalDocs / df));
|
|
29
|
+
idfWeights.set(token, Math.log(1 + totalDocs / df));
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
return idfWeights;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
|
-
* Map a sentence to its most relevant source documents
|
|
36
|
+
* Map a sentence to its most relevant source documents by how much of the
|
|
37
|
+
* sentence each document supports (see computeSupportScore).
|
|
35
38
|
* @param {string} sentence - The sentence to map
|
|
36
39
|
* @param {Array<{content?: string, text?: string, id?: string}>} docs - Source documents
|
|
37
40
|
* @param {object} [options] - Configuration options
|
|
38
41
|
* @param {number} [options.maxCitations=3] - Maximum number of citations to return
|
|
39
|
-
* @param {number} [options.threshold=0.
|
|
42
|
+
* @param {number} [options.threshold=0.5] - Minimum similarity threshold
|
|
40
43
|
* @param {Map<string, number>|null} [options.idfWeights=null] - Optional IDF weights for weighted similarity
|
|
41
44
|
* @returns {Array<{docIndex: number, docId: string|undefined, score: number, matchedTokens: string[]}>}
|
|
42
45
|
*/
|
|
43
46
|
function mapSentenceToSources(sentence, docs, options = {}) {
|
|
44
|
-
const { maxCitations = 3, threshold = 0.
|
|
47
|
+
const { maxCitations = 3, threshold = 0.5, idfWeights = null } = options;
|
|
45
48
|
|
|
46
49
|
const sentenceTokens = tokenize(sentence);
|
|
47
50
|
if (!sentenceTokens.length) return [];
|
|
@@ -54,29 +57,13 @@ function mapSentenceToSources(sentence, docs, options = {}) {
|
|
|
54
57
|
const docTokens = tokenize(docText);
|
|
55
58
|
if (!docTokens.length) continue;
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
const sentenceSet = new Set(sentenceTokens);
|
|
60
|
+
// Share of the sentence's content tokens found in this document.
|
|
59
61
|
const docSet = new Set(docTokens);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
let intersectionWeight = 0;
|
|
66
|
-
let unionWeight = 0;
|
|
67
|
-
|
|
68
|
-
for (const token of union) {
|
|
69
|
-
const weight = idfWeights.get(token) || 1;
|
|
70
|
-
if (sentenceSet.has(token) && docSet.has(token)) {
|
|
71
|
-
intersectionWeight += weight;
|
|
72
|
-
}
|
|
73
|
-
unionWeight += weight;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
score = unionWeight > 0 ? intersectionWeight / unionWeight : 0;
|
|
77
|
-
} else {
|
|
78
|
-
score = computeJaccardSimilarity(sentenceTokens, docTokens);
|
|
79
|
-
}
|
|
62
|
+
const score = computeSupportScore(sentenceTokens, docSet, idfWeights, {
|
|
63
|
+
claimText: sentence,
|
|
64
|
+
docText,
|
|
65
|
+
});
|
|
66
|
+
const matched = [...new Set(sentenceTokens)].filter((t) => docSet.has(t));
|
|
80
67
|
|
|
81
68
|
if (score >= threshold) {
|
|
82
69
|
results.push({
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `rag-pipeline ai ...`: training, benchmarking, relevance feedback and file
|
|
5
|
+
* inspection built on the real pipeline components (.ragrc.json + index).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Command, Option } = require("commander");
|
|
9
|
+
const { parsePositiveInteger } = require("../shared/cli-utils.js");
|
|
10
|
+
// The handler (document loaders: jsdom, marked, csv-parse) loads when an
|
|
11
|
+
// ai command runs, not when the CLI starts.
|
|
12
|
+
const aiHandler = () => require("../handlers/ai-handler.js"); // eslint-disable-line global-require
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {() => object} getGlobalOptions - Returns program.opts()
|
|
16
|
+
* @returns {Command}
|
|
17
|
+
*/
|
|
18
|
+
function createAiCommand(getGlobalOptions) {
|
|
19
|
+
const ai = new Command("ai").description(
|
|
20
|
+
"Train the local embedder, benchmark queries, relevance feedback, file inspection",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const train = ai
|
|
24
|
+
.command("train")
|
|
25
|
+
.description("Fit corpus-trained components");
|
|
26
|
+
train
|
|
27
|
+
.command("embedding")
|
|
28
|
+
.description(
|
|
29
|
+
"Refit the local TF-IDF embedder (on the index, or on [path]) and re-embed the index",
|
|
30
|
+
)
|
|
31
|
+
.argument(
|
|
32
|
+
"[path]",
|
|
33
|
+
"Train on documents from this file or directory instead",
|
|
34
|
+
)
|
|
35
|
+
.action(async (inputPath) => {
|
|
36
|
+
await aiHandler().handleTrainEmbedding(getGlobalOptions(), inputPath);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
ai.command("benchmark")
|
|
40
|
+
.description(
|
|
41
|
+
"Measure query latency against the configured pipeline and index",
|
|
42
|
+
)
|
|
43
|
+
.option(
|
|
44
|
+
"--queries <file>",
|
|
45
|
+
"Queries file: JSON array of strings or one per line (default: sampled from the index)",
|
|
46
|
+
)
|
|
47
|
+
.option("--runs <n>", "Passes over the query set", parsePositiveInteger, 3)
|
|
48
|
+
.option("--top-k <n>", "Passages to retrieve", parsePositiveInteger, 3)
|
|
49
|
+
.option("--json", "Print the measurements as JSON")
|
|
50
|
+
.action(async (options) => {
|
|
51
|
+
await aiHandler().handleBenchmark(getGlobalOptions(), options);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
ai.command("feedback")
|
|
55
|
+
.description(
|
|
56
|
+
"Record whether an indexed chunk was a good result for a question",
|
|
57
|
+
)
|
|
58
|
+
.argument("<question>", "Question the chunk was returned for")
|
|
59
|
+
.argument("<docId>", "Chunk id as shown by `ai search` (e.g. docs/a.md#0)")
|
|
60
|
+
.addOption(
|
|
61
|
+
new Option("--rating <rating>", "Relevance vote")
|
|
62
|
+
.choices(["up", "down"])
|
|
63
|
+
.makeOptionMandatory(),
|
|
64
|
+
)
|
|
65
|
+
.action(async (question, docId, options) => {
|
|
66
|
+
await aiHandler().handleFeedback(
|
|
67
|
+
getGlobalOptions(),
|
|
68
|
+
question,
|
|
69
|
+
docId,
|
|
70
|
+
options,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
ai.command("search")
|
|
75
|
+
.description(
|
|
76
|
+
"Retrieve passages re-scored with stored feedback (retrieval only)",
|
|
77
|
+
)
|
|
78
|
+
.argument("<question>", "Question text")
|
|
79
|
+
.option("--top-k <n>", "Passages to return", parsePositiveInteger, 3)
|
|
80
|
+
.option("--json", "Print the results as JSON")
|
|
81
|
+
.action(async (question, options) => {
|
|
82
|
+
await aiHandler().handleSearch(getGlobalOptions(), question, options);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const multimodal = ai
|
|
86
|
+
.command("multimodal")
|
|
87
|
+
.description("Inspect files before ingesting");
|
|
88
|
+
multimodal
|
|
89
|
+
.command("process")
|
|
90
|
+
.description(
|
|
91
|
+
"Report type, characters and chunks for text files (images need a vision model)",
|
|
92
|
+
)
|
|
93
|
+
.argument("<path>", "File or directory")
|
|
94
|
+
.option("--json", "Print the report as JSON")
|
|
95
|
+
.action(async (inputPath, options) => {
|
|
96
|
+
await aiHandler().handleMultimodalProcess(
|
|
97
|
+
getGlobalOptions(),
|
|
98
|
+
inputPath,
|
|
99
|
+
options,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return ai;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { createAiCommand };
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
const chalk = require("chalk");
|
|
7
7
|
|
|
8
8
|
function setupEventListeners(hub, certification) {
|
|
9
|
+
// The hub emits 'error' and then throws a descriptive error that each
|
|
10
|
+
// command reports. Without a listener, EventEmitter would throw its own
|
|
11
|
+
// "Unhandled error" first and hide that message.
|
|
12
|
+
hub.on("error", () => {});
|
|
13
|
+
certification.on("error", () => {});
|
|
14
|
+
|
|
9
15
|
// Hub events
|
|
10
16
|
hub.on("install_start", (data) => {
|
|
11
17
|
console.log(
|
|
@@ -69,34 +69,6 @@ async function searchPlugins(hub, query, _options) {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
async function installPlugin(hub, plugin, _options) {
|
|
73
|
-
try {
|
|
74
|
-
const installOptions = {
|
|
75
|
-
securityScan: _options.securityScan,
|
|
76
|
-
requireCertified: _options.requireCertified,
|
|
77
|
-
sandboxTimeout: _options.sandboxTimeout,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const result = await hub.installPlugin(
|
|
81
|
-
plugin,
|
|
82
|
-
_options.version,
|
|
83
|
-
installOptions,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
console.log(chalk.green("\nā
Installation completed successfully!")); // eslint-disable-line no-console
|
|
87
|
-
console.log(chalk.blue(`Plugin: ${result.pluginInfo.name}`)); // eslint-disable-line no-console
|
|
88
|
-
console.log(chalk.blue(`Version: ${result.version}`)); // eslint-disable-line no-console
|
|
89
|
-
console.log(chalk.blue(`Install Path: ${result.installPath}`)); // eslint-disable-line no-console
|
|
90
|
-
|
|
91
|
-
if (result.pluginInfo.certified) {
|
|
92
|
-
console.log(chalk.green("š This plugin is certified!")); // eslint-disable-line no-console
|
|
93
|
-
}
|
|
94
|
-
} catch (error) {
|
|
95
|
-
console.error(chalk.red(`Installation failed: ${error.message}`)); // eslint-disable-line no-console
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
72
|
async function getPluginInfo(hub, plugin) {
|
|
101
73
|
const spinner = ora("Fetching plugin information...").start();
|
|
102
74
|
|
|
@@ -170,50 +142,6 @@ async function getPluginInfo(hub, plugin) {
|
|
|
170
142
|
}
|
|
171
143
|
}
|
|
172
144
|
|
|
173
|
-
async function listInstalledPlugins(hub, _options) {
|
|
174
|
-
const spinner = ora("Loading installed plugins...").start();
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const installed = await hub.getInstalledPlugins();
|
|
178
|
-
spinner.stop();
|
|
179
|
-
|
|
180
|
-
if (installed.length === 0) {
|
|
181
|
-
console.log(chalk.yellow("No plugins installed.")); // eslint-disable-line no-console
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (_options.format === "json") {
|
|
186
|
-
console.log(JSON.stringify(installed, null, 2)); // eslint-disable-line no-console
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
console.log(chalk.green(`${installed.length} plugins installed:\n`)); // eslint-disable-line no-console
|
|
191
|
-
|
|
192
|
-
const table = new Table({
|
|
193
|
-
head: ["Name", "Version", "Type", "Last Used", "Status"],
|
|
194
|
-
colWidths: [25, 12, 15, 15, 10],
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
for (const plugin of installed) {
|
|
198
|
-
const lastUsed = plugin.lastUsed
|
|
199
|
-
? new Date(plugin.lastUsed).toLocaleDateString()
|
|
200
|
-
: chalk.gray("Never");
|
|
201
|
-
|
|
202
|
-
const status = plugin.certified
|
|
203
|
-
? chalk.green("Certified")
|
|
204
|
-
: chalk.gray("Standard");
|
|
205
|
-
|
|
206
|
-
table.push([plugin.name, plugin.version, plugin._type, lastUsed, status]);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
console.log(table.toString()); // eslint-disable-line no-console
|
|
210
|
-
} catch (error) {
|
|
211
|
-
spinner.stop();
|
|
212
|
-
console.error(chalk.red(`Failed to list plugins: ${error.message}`)); // eslint-disable-line no-console
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
145
|
async function publishPlugin(hub, pluginPath, _options) {
|
|
218
146
|
if (_options.dryRun) {
|
|
219
147
|
console.log(
|
|
@@ -282,9 +210,7 @@ async function getTrendingPlugins(hub, _options) {
|
|
|
282
210
|
|
|
283
211
|
module.exports = {
|
|
284
212
|
searchPlugins,
|
|
285
|
-
installPlugin,
|
|
286
213
|
getPluginInfo,
|
|
287
|
-
listInstalledPlugins,
|
|
288
214
|
publishPlugin,
|
|
289
215
|
getTrendingPlugins,
|
|
290
216
|
};
|