@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,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const pluginRegistry = require("../core/plugin-registry.js");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Built-in components a .ragrc.json pipeline entry can name. Each factory
|
|
8
|
+
* receives the entry's `config` object and the directory of the config file.
|
|
9
|
+
* Requires are lazy so naming one provider does not load the others.
|
|
10
|
+
*/
|
|
11
|
+
const BUILT_INS = {
|
|
12
|
+
// "auto" picks a loader per file extension (see loader/document-reader).
|
|
13
|
+
loader: {
|
|
14
|
+
auto: () => require("../loader/document-reader"),
|
|
15
|
+
},
|
|
16
|
+
embedder: {
|
|
17
|
+
local: (cfg) =>
|
|
18
|
+
new (require("../connectors/local-embedder").LocalEmbedder)(cfg),
|
|
19
|
+
openai: (cfg) =>
|
|
20
|
+
new (require("../connectors/openai-connector").OpenAIConnector)(cfg),
|
|
21
|
+
ollama: (cfg) =>
|
|
22
|
+
new (require("../connectors/ollama-connector").OllamaConnector)(cfg),
|
|
23
|
+
cohere: (cfg) =>
|
|
24
|
+
new (require("../connectors/cohere-connector").CohereConnector)(cfg),
|
|
25
|
+
},
|
|
26
|
+
retriever: {
|
|
27
|
+
"file-store": (cfg, configDir) =>
|
|
28
|
+
new (require("../retrieval/file-vector-store").FileVectorStore)({
|
|
29
|
+
path: path.resolve(configDir, cfg.path || ".rag/index.json"),
|
|
30
|
+
}),
|
|
31
|
+
memory: (cfg) =>
|
|
32
|
+
new (require("../connectors/memory-retriever").MemoryRetriever)(cfg),
|
|
33
|
+
},
|
|
34
|
+
llm: {
|
|
35
|
+
openai: (cfg) =>
|
|
36
|
+
new (require("../connectors/openai-connector").OpenAIConnector)(cfg),
|
|
37
|
+
anthropic: (cfg) =>
|
|
38
|
+
new (require("../connectors/anthropic-connector").AnthropicConnector)(
|
|
39
|
+
cfg,
|
|
40
|
+
),
|
|
41
|
+
ollama: (cfg) =>
|
|
42
|
+
new (require("../connectors/ollama-connector").OllamaConnector)(cfg),
|
|
43
|
+
},
|
|
44
|
+
reranker: {
|
|
45
|
+
scoring: (cfg) =>
|
|
46
|
+
new (require("../reranker/scoring-reranker").ScoringReranker)(cfg),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const STAGES = ["loader", "embedder", "retriever", "reranker", "llm"];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Instantiate a custom plugin from `plugins.<stage>.<name>` in the config.
|
|
54
|
+
* The module may export a class (constructed with the entry config), an
|
|
55
|
+
* instance, or either of those as `default`.
|
|
56
|
+
*/
|
|
57
|
+
function loadPluginModule(modulePath, entryConfig, configDir) {
|
|
58
|
+
const resolved = path.resolve(configDir, modulePath);
|
|
59
|
+
const mod = require(resolved);
|
|
60
|
+
const exported = mod && mod.default ? mod.default : mod;
|
|
61
|
+
if (typeof exported === "function") {
|
|
62
|
+
return /^class\s/.test(Function.prototype.toString.call(exported))
|
|
63
|
+
? new exported(entryConfig)
|
|
64
|
+
: exported(entryConfig);
|
|
65
|
+
}
|
|
66
|
+
if (exported && typeof exported === "object") return exported;
|
|
67
|
+
throw new Error(`Plugin module ${modulePath} must export a class or object`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Turn a normalized .ragrc.json config into component instances.
|
|
72
|
+
*
|
|
73
|
+
* Each `pipeline` entry `{ stage, name, config }` is resolved in order:
|
|
74
|
+
* 1. a built-in name (see BUILT_INS),
|
|
75
|
+
* 2. a custom module path under `plugins.<stage>.<name>`,
|
|
76
|
+
* 3. a plugin registered on the shared pluginRegistry.
|
|
77
|
+
*
|
|
78
|
+
* @param {object} config - Output of loadRagConfig()/validateRagrc().normalized
|
|
79
|
+
* @param {object} [options]
|
|
80
|
+
* @param {string} [options.configDir=process.cwd()] - Paths resolve relative to this
|
|
81
|
+
* @returns {{embedder?: object, retriever?: object, llm?: object, reranker?: object, loader?: object, names: object}}
|
|
82
|
+
*/
|
|
83
|
+
function buildComponents(config, options = {}) {
|
|
84
|
+
const configDir = options.configDir || process.cwd();
|
|
85
|
+
const components = { names: {} };
|
|
86
|
+
|
|
87
|
+
for (const entry of config.pipeline || []) {
|
|
88
|
+
if (!STAGES.includes(entry.stage)) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Unknown pipeline stage "${entry.stage}". Expected one of: ${STAGES.join(", ")}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
components[entry.stage] = resolveComponent(entry, config, configDir);
|
|
94
|
+
components.names[entry.stage] = entry.name;
|
|
95
|
+
}
|
|
96
|
+
return components;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveComponent(entry, config, configDir) {
|
|
100
|
+
const { stage, name } = entry;
|
|
101
|
+
const entryConfig = entry.config || {};
|
|
102
|
+
|
|
103
|
+
const builtIn = BUILT_INS[stage]?.[name];
|
|
104
|
+
if (builtIn) return builtIn(entryConfig, configDir);
|
|
105
|
+
|
|
106
|
+
const modulePath = config.plugins?.[stage]?.[name];
|
|
107
|
+
if (typeof modulePath === "string") {
|
|
108
|
+
return loadPluginModule(modulePath, entryConfig, configDir);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
return pluginRegistry.get(stage, name);
|
|
113
|
+
} catch {
|
|
114
|
+
const builtIns = Object.keys(BUILT_INS[stage] || {});
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Unknown ${stage} "${name}". ` +
|
|
117
|
+
(builtIns.length
|
|
118
|
+
? `Built-in ${stage}s: ${builtIns.join(", ")}. `
|
|
119
|
+
: "") +
|
|
120
|
+
`For a custom plugin add "plugins": { "${stage}": { "${name}": "./path/to/module.js" } }.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { buildComponents, loadPluginModule, BUILT_INS, STAGES };
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
* Single source of truth for configuration validation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const Ajv = require("ajv"); // eslint-disable-line global-require
|
|
7
|
-
const addFormats = require("ajv-formats"); // eslint-disable-line global-require
|
|
8
6
|
const { normalizeConfig } = require("./normalize-config");
|
|
9
7
|
|
|
10
8
|
/**
|
|
@@ -18,11 +18,18 @@ const CONFIG_FILENAME = ".ragrc.json";
|
|
|
18
18
|
* @param {string} [cwd=process.cwd()] - Directory to resolve the config from
|
|
19
19
|
* @returns {object} Validated configuration object
|
|
20
20
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Load and validate .ragrc.json.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} [location=process.cwd()] - A directory containing
|
|
25
|
+
* .ragrc.json, or the path of a config file (any name ending in .json)
|
|
26
|
+
* @returns {object} Normalized configuration
|
|
27
|
+
*/
|
|
28
|
+
function loadRagConfig(location = process.cwd()) {
|
|
29
|
+
const configPath = resolveConfigPath(location);
|
|
23
30
|
|
|
31
|
+
// Errors are thrown, not logged: callers (e.g. the CLI) report them once.
|
|
24
32
|
if (!fs.existsSync(configPath)) {
|
|
25
|
-
logger.error(`❌ Config file not found: ${configPath}`);
|
|
26
33
|
throw new Error(`Config file not found: ${configPath}`);
|
|
27
34
|
}
|
|
28
35
|
|
|
@@ -32,23 +39,35 @@ function loadRagConfig(cwd = process.cwd()) {
|
|
|
32
39
|
try {
|
|
33
40
|
config = JSON.parse(raw);
|
|
34
41
|
} catch (err) {
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Invalid JSON in config file ${configPath}: ${err.message}`,
|
|
44
|
+
);
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
const { valid, errors, normalized } = validateRagrc(config);
|
|
40
48
|
|
|
41
49
|
if (!valid) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
throw new Error("Config validation failed");
|
|
50
|
+
const details = (errors || []).map((e) => e.message).join("; ");
|
|
51
|
+
throw new Error(`Config validation failed: ${details}`);
|
|
46
52
|
}
|
|
47
53
|
|
|
48
|
-
logger.
|
|
54
|
+
logger.debug("Configuration loaded", { configPath });
|
|
49
55
|
return normalized;
|
|
50
56
|
}
|
|
51
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} [location=process.cwd()] - Directory or config file path
|
|
60
|
+
* @returns {string} Absolute path of the config file
|
|
61
|
+
*/
|
|
62
|
+
function resolveConfigPath(location = process.cwd()) {
|
|
63
|
+
const absolute = path.resolve(location);
|
|
64
|
+
const isFile =
|
|
65
|
+
(fs.existsSync(absolute) && fs.statSync(absolute).isFile()) ||
|
|
66
|
+
absolute.toLowerCase().endsWith(".json");
|
|
67
|
+
return isFile ? absolute : path.join(absolute, CONFIG_FILENAME);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
module.exports = {
|
|
53
71
|
loadRagConfig,
|
|
72
|
+
resolveConfigPath,
|
|
54
73
|
};
|
|
@@ -17,6 +17,48 @@ const { validatePluginSchema } = require("./validate-schema.js");
|
|
|
17
17
|
const { logger } = require("../utils/logger.js");
|
|
18
18
|
// eslint-disable-line global-require
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Verify a plugin file against its signed manifest BEFORE it is imported
|
|
22
|
+
* (importing runs the plugin's code).
|
|
23
|
+
*
|
|
24
|
+
* The manifest is read from `<plugin file>.manifest.json`. Its
|
|
25
|
+
* `contentDigest` (if any) must cover the plugin file plus any extra files
|
|
26
|
+
* listed in `manifest.files` (paths relative to the plugin file's
|
|
27
|
+
* directory). Nothing is checked when the registry's signature verification
|
|
28
|
+
* is disabled; when it is enabled and the registry was created with
|
|
29
|
+
* `requireSignedPlugins: true`, a missing manifest is an error.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} name - Plugin name
|
|
32
|
+
* @param {string} absPath - Absolute path of the plugin entry file
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
async function verifyPluginBeforeImport(name, absPath) {
|
|
36
|
+
if (
|
|
37
|
+
typeof registry.isSignatureVerificationEnabled !== "function" ||
|
|
38
|
+
!registry.isSignatureVerificationEnabled()
|
|
39
|
+
) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const manifestPath = `${absPath}.manifest.json`;
|
|
44
|
+
if (!fs.existsSync(manifestPath)) {
|
|
45
|
+
if (registry.requiresSignedPlugins()) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Plugin ${name} has no signed manifest (${manifestPath}) and requireSignedPlugins is enabled`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
54
|
+
const baseDir = path.dirname(absPath);
|
|
55
|
+
const extraFiles = Array.isArray(manifest.files) ? manifest.files : [];
|
|
56
|
+
await registry.verifyPluginContent(name, manifest, {
|
|
57
|
+
baseDir,
|
|
58
|
+
files: [absPath, ...extraFiles.map((f) => path.resolve(baseDir, f))],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
20
62
|
/**
|
|
21
63
|
* Loads plugins from validated _config path and registers them.
|
|
22
64
|
* @param {string} configPath
|
|
@@ -43,6 +85,7 @@ async function loadPluginsFromJson(configPath) {
|
|
|
43
85
|
const fileUrl = pathToFileURL(absPath).href;
|
|
44
86
|
|
|
45
87
|
try {
|
|
88
|
+
await verifyPluginBeforeImport(name, absPath);
|
|
46
89
|
const mod = await import(fileUrl);
|
|
47
90
|
const PluginClass = mod?.default;
|
|
48
91
|
if (typeof PluginClass !== "function") {
|
|
@@ -52,7 +95,8 @@ async function loadPluginsFromJson(configPath) {
|
|
|
52
95
|
}
|
|
53
96
|
|
|
54
97
|
const _instance = new PluginClass();
|
|
55
|
-
|
|
98
|
+
// register() is async; await it so failures are not unhandled
|
|
99
|
+
await registry.register(_type, name, _instance);
|
|
56
100
|
|
|
57
101
|
logger.info({ _type, name }, `Registered plugin: ${_type}:${name}`);
|
|
58
102
|
} catch (err) {
|
|
@@ -66,4 +110,4 @@ async function loadPluginsFromJson(configPath) {
|
|
|
66
110
|
}
|
|
67
111
|
}
|
|
68
112
|
|
|
69
|
-
module.exports = { loadPluginsFromJson };
|
|
113
|
+
module.exports = { loadPluginsFromJson, verifyPluginBeforeImport };
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* File: /src/_config/validate-schema.js
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// ajv loads when a schema is validated, not on import.
|
|
9
|
+
const loadAjv = () => require("ajv"); // eslint-disable-line global-require
|
|
9
10
|
// eslint-disable-line global-require
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -89,6 +90,7 @@ const pluginSchema = {
|
|
|
89
90
|
* @returns {{ valid: boolean, errors?: any[] }}
|
|
90
91
|
*/
|
|
91
92
|
function validateRagrcSchema(config) {
|
|
93
|
+
const Ajv = loadAjv();
|
|
92
94
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
93
95
|
const validate = ajv.compile(ragrcSchema);
|
|
94
96
|
const valid = validate(config);
|
|
@@ -101,6 +103,7 @@ function validateRagrcSchema(config) {
|
|
|
101
103
|
* @returns {{ valid: boolean, errors?: any[] }}
|
|
102
104
|
*/
|
|
103
105
|
function validatePluginSchema(config) {
|
|
106
|
+
const Ajv = loadAjv();
|
|
104
107
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
105
108
|
const validate = ajv.compile(pluginSchema);
|
|
106
109
|
const valid = validate(config);
|
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// axios loads on first use, so importing the package does not pay for it
|
|
4
|
+
// (about 125 ms) unless this connector is constructed.
|
|
5
|
+
const loadAxios = () => require('axios'); // eslint-disable-line global-require
|
|
4
6
|
const { BaseConnector } = require('./base-connector');
|
|
7
|
+
const {
|
|
8
|
+
missingApiKeyError,
|
|
9
|
+
raceAbort,
|
|
10
|
+
resolveApiKey,
|
|
11
|
+
signalOptions,
|
|
12
|
+
throwIfAborted,
|
|
13
|
+
} = require('./connector-utils');
|
|
14
|
+
const { buildRagPrompt } = require('./rag-prompt');
|
|
15
|
+
const { componentCacheKey } = require('../cache/component-keys');
|
|
16
|
+
|
|
17
|
+
const API_KEY_ENV = 'ANTHROPIC_API_KEY';
|
|
5
18
|
|
|
6
19
|
/**
|
|
7
20
|
* Default configuration for the Anthropic connector.
|
|
21
|
+
* `apiKey` is a getter so the environment is read when a connector is
|
|
22
|
+
* constructed, not when this module is first required.
|
|
8
23
|
* @type {Object}
|
|
9
24
|
*/
|
|
10
25
|
const DEFAULT_CONFIG = {
|
|
11
26
|
model: 'claude-3-5-sonnet-20241022',
|
|
12
|
-
apiKey: process.env.ANTHROPIC_API_KEY || '',
|
|
13
27
|
baseURL: 'https://api.anthropic.com',
|
|
14
28
|
apiVersion: '2023-06-01',
|
|
15
29
|
maxTokens: 4096,
|
|
16
30
|
temperature: 0.7,
|
|
31
|
+
maxRetries: 3,
|
|
32
|
+
retryDelay: 1000,
|
|
33
|
+
get apiKey() {
|
|
34
|
+
return process.env[API_KEY_ENV] || '';
|
|
35
|
+
},
|
|
17
36
|
};
|
|
18
37
|
|
|
19
38
|
/**
|
|
@@ -23,19 +42,43 @@ const DEFAULT_CONFIG = {
|
|
|
23
42
|
* @extends BaseConnector
|
|
24
43
|
*/
|
|
25
44
|
class AnthropicConnector extends BaseConnector {
|
|
45
|
+
/**
|
|
46
|
+
* Cache identity for createRagPipeline({ cache }).
|
|
47
|
+
* @param {'llm'} role
|
|
48
|
+
* @returns {string|null}
|
|
49
|
+
*/
|
|
50
|
+
getCacheKey(role) {
|
|
51
|
+
if (role !== 'llm') return null;
|
|
52
|
+
const c = this.config;
|
|
53
|
+
return componentCacheKey('anthropic', {
|
|
54
|
+
baseURL: c.baseURL,
|
|
55
|
+
model: c.model,
|
|
56
|
+
temperature: c.temperature,
|
|
57
|
+
maxTokens: c.maxTokens,
|
|
58
|
+
systemPrompt: c.systemPrompt,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
26
62
|
/**
|
|
27
63
|
* @param {Object} [options]
|
|
28
64
|
* @param {string} [options.model='claude-3-5-sonnet-20241022'] - Claude model to use
|
|
29
|
-
* @param {string} [options.apiKey] - Anthropic API key
|
|
65
|
+
* @param {string} [options.apiKey] - Anthropic API key (defaults to ANTHROPIC_API_KEY at construction)
|
|
30
66
|
* @param {string} [options.baseURL='https://api.anthropic.com'] - API base URL
|
|
31
67
|
* @param {string} [options.apiVersion='2023-06-01'] - API version header
|
|
32
68
|
* @param {number} [options.maxTokens=4096] - Max tokens in response
|
|
33
69
|
* @param {number} [options.temperature=0.7] - Sampling temperature
|
|
70
|
+
* @param {number} [options.maxRetries=3] - Retries for transient failures
|
|
71
|
+
* @param {number} [options.retryDelay=1000] - Base backoff delay in ms
|
|
72
|
+
* @param {string} [options.systemPrompt] - System prompt used when RAG context is supplied
|
|
34
73
|
*/
|
|
35
74
|
constructor(options = {}) {
|
|
36
75
|
super({ name: 'anthropic', ...options });
|
|
37
|
-
this.config = {
|
|
38
|
-
|
|
76
|
+
this.config = {
|
|
77
|
+
...DEFAULT_CONFIG,
|
|
78
|
+
...options,
|
|
79
|
+
apiKey: resolveApiKey(options, API_KEY_ENV),
|
|
80
|
+
};
|
|
81
|
+
this.client = loadAxios().create({
|
|
39
82
|
baseURL: this.config.baseURL,
|
|
40
83
|
headers: {
|
|
41
84
|
'x-api-key': this.config.apiKey,
|
|
@@ -45,51 +88,73 @@ class AnthropicConnector extends BaseConnector {
|
|
|
45
88
|
});
|
|
46
89
|
}
|
|
47
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @throws {Error} code MISSING_API_KEY when no key is configured
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
_requireApiKey() {
|
|
96
|
+
if (!this.config.apiKey) {
|
|
97
|
+
throw missingApiKeyError('Anthropic', API_KEY_ENV);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
48
101
|
/**
|
|
49
102
|
* Connect to Anthropic. Verifies the API key is present.
|
|
50
103
|
* @returns {Promise<void>}
|
|
51
104
|
* @throws {Error} If no API key is configured
|
|
52
105
|
*/
|
|
53
106
|
async connect() {
|
|
54
|
-
|
|
55
|
-
throw new Error(
|
|
56
|
-
'Anthropic API key required. Set ANTHROPIC_API_KEY or pass apiKey option.',
|
|
57
|
-
);
|
|
58
|
-
}
|
|
107
|
+
this._requireApiKey();
|
|
59
108
|
await super.connect();
|
|
60
109
|
}
|
|
61
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Build the Messages API request body. Retrieved context goes into the
|
|
113
|
+
* user message as delimited, untrusted reference data; the system prompt
|
|
114
|
+
* only carries instructions.
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_buildBody(query, context, options) {
|
|
118
|
+
const prompt = buildRagPrompt(query, context, {
|
|
119
|
+
systemPrompt: this.config.systemPrompt,
|
|
120
|
+
});
|
|
121
|
+
const body = {
|
|
122
|
+
model: options.model || this.config.model,
|
|
123
|
+
max_tokens: options.maxTokens ?? this.config.maxTokens,
|
|
124
|
+
temperature: options.temperature ?? this.config.temperature,
|
|
125
|
+
messages: [{ role: 'user', content: prompt.user }],
|
|
126
|
+
};
|
|
127
|
+
if (prompt.system) body.system = prompt.system;
|
|
128
|
+
return body;
|
|
129
|
+
}
|
|
130
|
+
|
|
62
131
|
/**
|
|
63
132
|
* Generate a response from Claude using the provided query and optional RAG context.
|
|
64
133
|
*
|
|
65
134
|
* @param {string} query - The user query / prompt
|
|
66
135
|
* @param {Array<Object|string>} [context=[]] - Retrieved documents for RAG
|
|
67
136
|
* @param {Object} [options={}] - Override model, temperature, maxTokens per-call
|
|
137
|
+
* @param {AbortSignal} [options.signal] - Cancels the request
|
|
68
138
|
* @returns {Promise<string>} The generated text
|
|
69
139
|
*/
|
|
70
140
|
async generate(query, context = [], options = {}) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const response = await this.client.post('/v1/messages', body);
|
|
91
|
-
return response.data.content[0].text;
|
|
92
|
-
});
|
|
141
|
+
const { signal } = options;
|
|
142
|
+
throwIfAborted(signal);
|
|
143
|
+
this._requireApiKey();
|
|
144
|
+
const body = this._buildBody(query, context, options);
|
|
145
|
+
return this.withRetry(
|
|
146
|
+
async () => {
|
|
147
|
+
const response = await this.client.post(
|
|
148
|
+
'/v1/messages',
|
|
149
|
+
body,
|
|
150
|
+
signal ? { signal } : undefined,
|
|
151
|
+
);
|
|
152
|
+
return response.data.content[0].text;
|
|
153
|
+
},
|
|
154
|
+
this.config.maxRetries,
|
|
155
|
+
this.config.retryDelay,
|
|
156
|
+
{ signal },
|
|
157
|
+
);
|
|
93
158
|
}
|
|
94
159
|
|
|
95
160
|
/**
|
|
@@ -98,32 +163,27 @@ class AnthropicConnector extends BaseConnector {
|
|
|
98
163
|
* @param {string} query - The user query / prompt
|
|
99
164
|
* @param {Array<Object|string>} [context=[]] - Retrieved documents for RAG
|
|
100
165
|
* @param {Object} [options={}] - Override model, temperature, maxTokens per-call
|
|
166
|
+
* @param {AbortSignal} [options.signal] - Cancels the stream
|
|
101
167
|
* @yields {{ token: string, done: boolean }}
|
|
102
168
|
*/
|
|
103
169
|
async *generateStream(query, context = [], options = {}) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
body.system = `Use the following context to answer the question:\n\n${contextText}`;
|
|
117
|
-
body.messages = [{ role: 'user', content: query }];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const response = await this.client.post('/v1/messages', body, {
|
|
121
|
-
responseType: 'stream',
|
|
122
|
-
});
|
|
170
|
+
const { signal } = options;
|
|
171
|
+
throwIfAborted(signal);
|
|
172
|
+
this._requireApiKey();
|
|
173
|
+
const body = { ...this._buildBody(query, context, options), stream: true };
|
|
174
|
+
|
|
175
|
+
const response = await raceAbort(
|
|
176
|
+
this.client.post('/v1/messages', body, {
|
|
177
|
+
responseType: 'stream',
|
|
178
|
+
...signalOptions(signal),
|
|
179
|
+
}),
|
|
180
|
+
signal,
|
|
181
|
+
);
|
|
123
182
|
|
|
124
183
|
let buffer = '';
|
|
125
184
|
|
|
126
185
|
for await (const chunk of response.data) {
|
|
186
|
+
throwIfAborted(signal);
|
|
127
187
|
buffer += chunk.toString();
|
|
128
188
|
const lines = buffer.split('\n');
|
|
129
189
|
// Keep the last incomplete line in the buffer
|
|
@@ -160,7 +220,9 @@ class AnthropicConnector extends BaseConnector {
|
|
|
160
220
|
}
|
|
161
221
|
|
|
162
222
|
/**
|
|
163
|
-
* Build a prompt string that includes RAG context when available.
|
|
223
|
+
* Build a single prompt string that includes RAG context when available.
|
|
224
|
+
* Kept for backward compatibility; the context is delimited and labelled
|
|
225
|
+
* as untrusted reference data.
|
|
164
226
|
*
|
|
165
227
|
* @param {string} query - User query
|
|
166
228
|
* @param {Array<Object|string>} context - RAG context documents
|
|
@@ -168,11 +230,7 @@ class AnthropicConnector extends BaseConnector {
|
|
|
168
230
|
* @private
|
|
169
231
|
*/
|
|
170
232
|
_buildPrompt(query, context) {
|
|
171
|
-
|
|
172
|
-
const contextText = context
|
|
173
|
-
.map((doc) => doc.content || doc.text || String(doc))
|
|
174
|
-
.join('\n\n');
|
|
175
|
-
return `Context:\n${contextText}\n\nQuestion: ${query}`;
|
|
233
|
+
return buildRagPrompt(query, context).user;
|
|
176
234
|
}
|
|
177
235
|
}
|
|
178
236
|
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { EventEmitter } = require('events');
|
|
4
|
+
const {
|
|
5
|
+
abortableSleep,
|
|
6
|
+
computeRetryDelay,
|
|
7
|
+
createAbortError,
|
|
8
|
+
getErrorStatus,
|
|
9
|
+
isAbortError,
|
|
10
|
+
isRetryableError,
|
|
11
|
+
raceAbort,
|
|
12
|
+
throwIfAborted,
|
|
13
|
+
} = require('./connector-utils');
|
|
4
14
|
|
|
5
15
|
/**
|
|
6
16
|
* Abstract base class for all connectors.
|
|
@@ -49,33 +59,78 @@ class BaseConnector extends EventEmitter {
|
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
/**
|
|
52
|
-
* Retry wrapper
|
|
53
|
-
*
|
|
54
|
-
*
|
|
62
|
+
* Retry wrapper for transient failures.
|
|
63
|
+
*
|
|
64
|
+
* Only retryable errors are retried: network errors and HTTP
|
|
65
|
+
* 408/409/425/429/5xx (see {@link isRetryableError}). Other errors
|
|
66
|
+
* (400, 401, 403, 404, aborts, errors with no status or network code) are
|
|
67
|
+
* thrown immediately. Backoff is exponential with full jitter, starting at
|
|
68
|
+
* `delay` and capped at `opts.maxDelay`; a Retry-After header on the error
|
|
69
|
+
* is honoured (capped at `opts.maxDelay`). Emits a 'retry' event before
|
|
70
|
+
* each retry.
|
|
71
|
+
*
|
|
72
|
+
* @param {Function} fn - Async function to execute; receives the attempt number
|
|
55
73
|
* @param {number} [retries=3] - Maximum number of retry attempts
|
|
56
|
-
* @param {number} [delay=1000] -
|
|
74
|
+
* @param {number} [delay=1000] - Base backoff delay in ms
|
|
75
|
+
* @param {object} [opts]
|
|
76
|
+
* @param {AbortSignal} [opts.signal] - Aborts the call and any pending backoff
|
|
77
|
+
* @param {number} [opts.maxDelay=30000] - Upper bound for a single backoff delay
|
|
78
|
+
* @param {(err: *) => boolean} [opts.shouldRetry] - Override retry classification
|
|
57
79
|
* @returns {Promise<*>} Result of the function
|
|
58
|
-
* @throws {Error}
|
|
80
|
+
* @throws {Error} The first non-retryable error, or the last error once retries are exhausted
|
|
59
81
|
*/
|
|
60
|
-
async withRetry(fn, retries = 3, delay = 1000) {
|
|
61
|
-
|
|
62
|
-
|
|
82
|
+
async withRetry(fn, retries = 3, delay = 1000, opts = {}) {
|
|
83
|
+
const signal = opts.signal;
|
|
84
|
+
const shouldRetry = opts.shouldRetry || isRetryableError;
|
|
85
|
+
const maxDelay = opts.maxDelay ?? this.options.retryMaxDelay ?? 30000;
|
|
86
|
+
|
|
87
|
+
for (let attempt = 0; ; attempt++) {
|
|
88
|
+
throwIfAborted(signal);
|
|
63
89
|
try {
|
|
64
|
-
return await fn();
|
|
90
|
+
return await raceAbort(() => fn(attempt), signal);
|
|
65
91
|
} catch (err) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
maxRetries: retries,
|
|
72
|
-
error: err.message,
|
|
73
|
-
});
|
|
74
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
92
|
+
if (isAbortError(err) || (signal && signal.aborted)) {
|
|
93
|
+
throw createAbortError(signal);
|
|
94
|
+
}
|
|
95
|
+
if (attempt >= retries || !shouldRetry(err)) {
|
|
96
|
+
throw err;
|
|
75
97
|
}
|
|
98
|
+
const delayMs = computeRetryDelay(err, attempt, {
|
|
99
|
+
baseDelay: delay,
|
|
100
|
+
maxDelay,
|
|
101
|
+
random: () => this._random(),
|
|
102
|
+
});
|
|
103
|
+
this.emit('retry', {
|
|
104
|
+
name: this.name,
|
|
105
|
+
attempt: attempt + 1,
|
|
106
|
+
maxRetries: retries,
|
|
107
|
+
error: err.message,
|
|
108
|
+
status: getErrorStatus(err),
|
|
109
|
+
delayMs,
|
|
110
|
+
});
|
|
111
|
+
await this._sleep(delayMs, signal);
|
|
76
112
|
}
|
|
77
113
|
}
|
|
78
|
-
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Wait between retries. Overridable in tests.
|
|
118
|
+
* @param {number} ms
|
|
119
|
+
* @param {AbortSignal} [signal]
|
|
120
|
+
* @returns {Promise<void>}
|
|
121
|
+
* @protected
|
|
122
|
+
*/
|
|
123
|
+
_sleep(ms, signal) {
|
|
124
|
+
return abortableSleep(ms, signal);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Jitter source for retry backoff. Overridable in tests.
|
|
129
|
+
* @returns {number} Value in [0, 1)
|
|
130
|
+
* @protected
|
|
131
|
+
*/
|
|
132
|
+
_random() {
|
|
133
|
+
return Math.random();
|
|
79
134
|
}
|
|
80
135
|
|
|
81
136
|
/**
|