@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
|
@@ -2,12 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
|
|
5
|
+
// ajv loads with the contracts; see _ensureContracts().
|
|
6
|
+
const loadAjv = () => require('ajv'); // eslint-disable-line global-require
|
|
6
7
|
const {
|
|
7
8
|
createPluginVerifier,
|
|
8
9
|
} = require('../security/plugin-signature-verifier.js');
|
|
9
10
|
const { logger } = require('../utils/logger');
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Locate the package's contracts/ directory. This file lives in src/core/ when
|
|
14
|
+
* loaded from source but is inlined into dist/index.cjs when bundled, so
|
|
15
|
+
* __dirname differs between the two layouts.
|
|
16
|
+
*
|
|
17
|
+
* @returns {string} Absolute path to the contracts directory
|
|
18
|
+
*/
|
|
19
|
+
function resolveContractsDir() {
|
|
20
|
+
const candidates = [
|
|
21
|
+
path.join(__dirname, '../../contracts'),
|
|
22
|
+
path.join(__dirname, '../contracts'),
|
|
23
|
+
];
|
|
24
|
+
return candidates.find((dir) => fs.existsSync(dir)) || candidates[0];
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
class PluginRegistry {
|
|
12
28
|
constructor(options = {}) {
|
|
13
29
|
this._plugins = new Map();
|
|
@@ -27,7 +43,43 @@ class PluginRegistry {
|
|
|
27
43
|
? options.validateContractSchema
|
|
28
44
|
: true; // Enable schema validation by default
|
|
29
45
|
|
|
30
|
-
//
|
|
46
|
+
// Environment-aware signature verification defaults
|
|
47
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
48
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
49
|
+
|
|
50
|
+
this._signatureVerifier = createPluginVerifier({
|
|
51
|
+
enabled:
|
|
52
|
+
options.verifySignatures !== undefined
|
|
53
|
+
? options.verifySignatures
|
|
54
|
+
: isProduction, // Enabled by default in production, disabled in development
|
|
55
|
+
failClosed:
|
|
56
|
+
options.failClosed !== undefined ? options.failClosed : isProduction, // Fail closed in production, fail open in development
|
|
57
|
+
trustedKeysPath: options.trustedKeysPath,
|
|
58
|
+
requireContentDigest: options.requireContentDigest === true,
|
|
59
|
+
});
|
|
60
|
+
// Opt-in: when verification is enabled, refuse plugins registered
|
|
61
|
+
// without a signed manifest (default false for backward compatibility).
|
|
62
|
+
this._requireSignedPlugins = options.requireSignedPlugins === true;
|
|
63
|
+
|
|
64
|
+
// Contracts (and ajv, which validates them) load in the constructor, or
|
|
65
|
+
// on first use with `lazyContracts: true`. The shared registry exported
|
|
66
|
+
// by this module is lazy, so importing the package does not pay for
|
|
67
|
+
// them unless a plugin is registered or validated.
|
|
68
|
+
this._contractsLoaded = false;
|
|
69
|
+
if (!options.lazyContracts) {
|
|
70
|
+
this._ensureContracts();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load the contract schema and the stage contracts once.
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
_ensureContracts() {
|
|
79
|
+
if (this._contractsLoaded) return;
|
|
80
|
+
this._contractsLoaded = true;
|
|
81
|
+
|
|
82
|
+
const Ajv = loadAjv();
|
|
31
83
|
this._ajv = new Ajv({
|
|
32
84
|
allErrors: true,
|
|
33
85
|
verbose: true,
|
|
@@ -55,19 +107,6 @@ class PluginRegistry {
|
|
|
55
107
|
}
|
|
56
108
|
}
|
|
57
109
|
|
|
58
|
-
// Environment-aware signature verification defaults
|
|
59
|
-
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
60
|
-
const isProduction = process.env.NODE_ENV === 'production';
|
|
61
|
-
|
|
62
|
-
this._signatureVerifier = createPluginVerifier({
|
|
63
|
-
enabled:
|
|
64
|
-
options.verifySignatures !== undefined
|
|
65
|
-
? options.verifySignatures
|
|
66
|
-
: isProduction, // Enabled by default in production, disabled in development
|
|
67
|
-
failClosed:
|
|
68
|
-
options.failClosed !== undefined ? options.failClosed : isProduction, // Fail closed in production, fail open in development
|
|
69
|
-
trustedKeysPath: options.trustedKeysPath,
|
|
70
|
-
});
|
|
71
110
|
this._loadContracts();
|
|
72
111
|
}
|
|
73
112
|
|
|
@@ -82,8 +121,8 @@ class PluginRegistry {
|
|
|
82
121
|
_loadContractSchema() {
|
|
83
122
|
try {
|
|
84
123
|
const schemaPath = path.join(
|
|
85
|
-
|
|
86
|
-
'
|
|
124
|
+
resolveContractsDir(),
|
|
125
|
+
'contract-schema.json',
|
|
87
126
|
);
|
|
88
127
|
if (fs.existsSync(schemaPath)) {
|
|
89
128
|
const schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
|
@@ -186,6 +225,7 @@ class PluginRegistry {
|
|
|
186
225
|
* @since 2.2.4
|
|
187
226
|
*/
|
|
188
227
|
checkContractExists(type) {
|
|
228
|
+
this._ensureContracts();
|
|
189
229
|
return this._contracts.has(type);
|
|
190
230
|
}
|
|
191
231
|
|
|
@@ -256,7 +296,7 @@ class PluginRegistry {
|
|
|
256
296
|
}
|
|
257
297
|
|
|
258
298
|
_loadContracts() {
|
|
259
|
-
const contractsDir =
|
|
299
|
+
const contractsDir = resolveContractsDir();
|
|
260
300
|
|
|
261
301
|
try {
|
|
262
302
|
const contractFiles = {
|
|
@@ -338,6 +378,7 @@ class PluginRegistry {
|
|
|
338
378
|
}
|
|
339
379
|
|
|
340
380
|
_validatePluginContract(category, impl) {
|
|
381
|
+
this._ensureContracts();
|
|
341
382
|
const contract = this._contracts.get(category);
|
|
342
383
|
if (!contract) {
|
|
343
384
|
// Use new warning method when registering a plugin without a contract
|
|
@@ -396,7 +437,20 @@ class PluginRegistry {
|
|
|
396
437
|
return methods;
|
|
397
438
|
}
|
|
398
439
|
|
|
399
|
-
|
|
440
|
+
/**
|
|
441
|
+
* Register a plugin.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} category - Plugin type
|
|
444
|
+
* @param {string} name - Plugin name
|
|
445
|
+
* @param {object} impl - Plugin implementation
|
|
446
|
+
* @param {object|null} [manifest] - Signed manifest; when given, its
|
|
447
|
+
* signature is verified before registration
|
|
448
|
+
* @param {object} [options]
|
|
449
|
+
* @param {object} [options.content] - Plugin content checked against the
|
|
450
|
+
* manifest's contentDigest: `{ files, baseDir, contents }`
|
|
451
|
+
* @returns {Promise<PluginRegistry>}
|
|
452
|
+
*/
|
|
453
|
+
async register(category, name, impl, manifest = null, options = {}) {
|
|
400
454
|
if (!category || typeof category !== 'string') {
|
|
401
455
|
throw new Error('Category must be a non-empty string');
|
|
402
456
|
}
|
|
@@ -412,7 +466,22 @@ class PluginRegistry {
|
|
|
412
466
|
|
|
413
467
|
// Verify plugin signature if manifest provided
|
|
414
468
|
if (manifest) {
|
|
415
|
-
await this._verifyPluginSignature(manifest, name);
|
|
469
|
+
await this._verifyPluginSignature(manifest, name, options.content);
|
|
470
|
+
} else if (
|
|
471
|
+
this._requireSignedPlugins &&
|
|
472
|
+
this.isSignatureVerificationEnabled()
|
|
473
|
+
) {
|
|
474
|
+
this._emitAuditEntry('plugin_verification_failed', {
|
|
475
|
+
pluginName: name,
|
|
476
|
+
signerId: 'unknown',
|
|
477
|
+
version: 'unknown',
|
|
478
|
+
verified: false,
|
|
479
|
+
error: 'missing signed manifest',
|
|
480
|
+
timestamp: new Date().toISOString(),
|
|
481
|
+
});
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Plugin ${name} has no signed manifest and requireSignedPlugins is enabled`,
|
|
484
|
+
);
|
|
416
485
|
}
|
|
417
486
|
|
|
418
487
|
// Validate plugin against contract
|
|
@@ -428,7 +497,7 @@ class PluginRegistry {
|
|
|
428
497
|
* @param {object} manifest - Plugin manifest
|
|
429
498
|
* @param {string} pluginName - Plugin name
|
|
430
499
|
*/
|
|
431
|
-
async _verifyPluginSignature(manifest, pluginName) {
|
|
500
|
+
async _verifyPluginSignature(manifest, pluginName, content = undefined) {
|
|
432
501
|
try {
|
|
433
502
|
// Extract signature information from manifest
|
|
434
503
|
if (!manifest.signature || !manifest.signerId) {
|
|
@@ -437,12 +506,12 @@ class PluginRegistry {
|
|
|
437
506
|
);
|
|
438
507
|
}
|
|
439
508
|
|
|
509
|
+
const args = [manifest, manifest.signature, manifest.signerId];
|
|
510
|
+
if (content) {
|
|
511
|
+
args.push(content);
|
|
512
|
+
}
|
|
440
513
|
const verificationResult =
|
|
441
|
-
await this._signatureVerifier.verifyPluginSignature(
|
|
442
|
-
manifest,
|
|
443
|
-
manifest.signature,
|
|
444
|
-
manifest.signerId,
|
|
445
|
-
);
|
|
514
|
+
await this._signatureVerifier.verifyPluginSignature(...args);
|
|
446
515
|
|
|
447
516
|
if (!verificationResult.verified) {
|
|
448
517
|
throw new Error(
|
|
@@ -473,6 +542,35 @@ class PluginRegistry {
|
|
|
473
542
|
}
|
|
474
543
|
}
|
|
475
544
|
|
|
545
|
+
/**
|
|
546
|
+
* Whether plugin signature verification is enabled for this registry.
|
|
547
|
+
* @returns {boolean}
|
|
548
|
+
*/
|
|
549
|
+
isSignatureVerificationEnabled() {
|
|
550
|
+
return Boolean(this._signatureVerifier && this._signatureVerifier.enabled);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Whether plugins without a signed manifest are rejected.
|
|
555
|
+
* @returns {boolean}
|
|
556
|
+
*/
|
|
557
|
+
requiresSignedPlugins() {
|
|
558
|
+
return this._requireSignedPlugins && this.isSignatureVerificationEnabled();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Verify a plugin's signed manifest and code before it is loaded (use this
|
|
563
|
+
* before importing plugin files, since importing executes them).
|
|
564
|
+
*
|
|
565
|
+
* @param {string} pluginName - Plugin name (for audit entries)
|
|
566
|
+
* @param {object} manifest - Signed manifest
|
|
567
|
+
* @param {object} content - `{ files, baseDir, contents }`
|
|
568
|
+
* @returns {Promise<void>} Rejects if verification fails
|
|
569
|
+
*/
|
|
570
|
+
async verifyPluginContent(pluginName, manifest, content) {
|
|
571
|
+
await this._verifyPluginSignature(manifest, pluginName, content);
|
|
572
|
+
}
|
|
573
|
+
|
|
476
574
|
/**
|
|
477
575
|
* Emit audit entry
|
|
478
576
|
* @param {string} action - Audit action
|
|
@@ -533,6 +631,7 @@ class PluginRegistry {
|
|
|
533
631
|
const registry = new PluginRegistry({
|
|
534
632
|
verifySignatures: process.env.NODE_ENV === 'production',
|
|
535
633
|
failClosed: process.env.NODE_ENV === 'production',
|
|
634
|
+
lazyContracts: true,
|
|
536
635
|
});
|
|
537
636
|
|
|
538
637
|
// CJS+ESM interop pattern
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What each pipeline stage must provide, as createRagPipeline and the CLI
|
|
5
|
+
* use it. A plugin needs at least one of `anyOf`; a missing `recommended`
|
|
6
|
+
* method only matters for the command named in its note.
|
|
7
|
+
*
|
|
8
|
+
* Shared by validatePlugin() and the `rag-pipeline init` wizard. The
|
|
9
|
+
* stricter rules pluginRegistry.register() applies (metadata, and the
|
|
10
|
+
* methods in contracts/*.json) are checked by validatePlugin(..., {
|
|
11
|
+
* registry: true }).
|
|
12
|
+
*/
|
|
13
|
+
const STAGE_CONTRACTS = Object.freeze({
|
|
14
|
+
loader: {
|
|
15
|
+
anyOf: ['load'],
|
|
16
|
+
recommended: {},
|
|
17
|
+
},
|
|
18
|
+
embedder: {
|
|
19
|
+
anyOf: ['embed', 'embedQuery'],
|
|
20
|
+
recommended: { embed: '`ingest` calls embed() to embed documents' },
|
|
21
|
+
},
|
|
22
|
+
retriever: {
|
|
23
|
+
anyOf: ['retrieve'],
|
|
24
|
+
recommended: { store: '`ingest` calls store() to save vectors' },
|
|
25
|
+
},
|
|
26
|
+
reranker: {
|
|
27
|
+
anyOf: ['rerank'],
|
|
28
|
+
recommended: {},
|
|
29
|
+
},
|
|
30
|
+
llm: {
|
|
31
|
+
anyOf: ['generate'],
|
|
32
|
+
recommended: {},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
module.exports = { STAGE_CONTRACTS };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { STAGE_CONTRACTS } = require('./stage-contracts');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} PluginValidationResult
|
|
7
|
+
* @property {boolean} valid - True when there are no errors
|
|
8
|
+
* @property {string[]} errors - Problems that stop the plugin from working
|
|
9
|
+
* @property {string[]} warnings - Methods some commands need, e.g. `store`
|
|
10
|
+
* for `rag-pipeline ingest`
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks that an object can be used as a pipeline stage, without throwing,
|
|
15
|
+
* so plugin authors can assert on it in their own tests.
|
|
16
|
+
*
|
|
17
|
+
* By default it checks what createRagPipeline() calls. With
|
|
18
|
+
* `{ registry: true }` it also applies the rules pluginRegistry.register()
|
|
19
|
+
* enforces: `metadata` with a string `name` and `version` and a `type`
|
|
20
|
+
* equal to the stage, plus the methods in the stage's contract.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const { validatePlugin } = require('@devilsdev/rag-pipeline-utils');
|
|
24
|
+
* const result = validatePlugin('llm', myLlm, { registry: true });
|
|
25
|
+
* expect(result.errors).toEqual([]);
|
|
26
|
+
*
|
|
27
|
+
* @param {'loader'|'embedder'|'retriever'|'reranker'|'llm'} stage
|
|
28
|
+
* @param {object} plugin - Plugin instance or object
|
|
29
|
+
* @param {Object} [options]
|
|
30
|
+
* @param {boolean} [options.registry=false] - Also check registry rules
|
|
31
|
+
* @returns {PluginValidationResult}
|
|
32
|
+
*/
|
|
33
|
+
function validatePlugin(stage, plugin, options = {}) {
|
|
34
|
+
const contract = STAGE_CONTRACTS[stage];
|
|
35
|
+
if (!contract) {
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
errors: [
|
|
39
|
+
`Unknown stage "${stage}". Expected one of: ${Object.keys(STAGE_CONTRACTS).join(', ')}`,
|
|
40
|
+
],
|
|
41
|
+
warnings: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
plugin === null ||
|
|
47
|
+
(typeof plugin !== 'object' && typeof plugin !== 'function')
|
|
48
|
+
) {
|
|
49
|
+
return {
|
|
50
|
+
valid: false,
|
|
51
|
+
errors: [`A ${stage} plugin must be an object or class instance`],
|
|
52
|
+
warnings: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const has = (method) => typeof plugin[method] === 'function';
|
|
57
|
+
const errors = [];
|
|
58
|
+
const warnings = [];
|
|
59
|
+
|
|
60
|
+
if (!contract.anyOf.some(has)) {
|
|
61
|
+
errors.push(
|
|
62
|
+
`A ${stage} needs ${contract.anyOf.map((m) => `${m}()`).join(' or ')}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
for (const [method, why] of Object.entries(contract.recommended)) {
|
|
66
|
+
if (!has(method)) warnings.push(`No ${method}() method: ${why}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (options.registry) {
|
|
70
|
+
// Reuse the registry's own check so the two cannot drift. It stops at
|
|
71
|
+
// the first problem, so this reports one registry error at a time.
|
|
72
|
+
// eslint-disable-next-line global-require
|
|
73
|
+
const { PluginRegistry } = require('./plugin-registry');
|
|
74
|
+
try {
|
|
75
|
+
new PluginRegistry()._validatePluginContract(stage, plugin);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const message = `pluginRegistry.register(): ${error.message}`;
|
|
78
|
+
if (!errors.includes(message)) errors.push(message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = { validatePlugin };
|
|
@@ -2,26 +2,134 @@
|
|
|
2
2
|
|
|
3
3
|
const { EventEmitter } = require('events');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Where the built-in prices come from and when they were recorded.
|
|
7
|
+
*
|
|
8
|
+
* The values are the providers' published list prices as copied into this
|
|
9
|
+
* library on `asOf`; they have not been re-verified since and providers
|
|
10
|
+
* change prices often. Pass `pricing` or `customPricing` to CostCalculator
|
|
11
|
+
* to use current or negotiated prices.
|
|
12
|
+
*/
|
|
13
|
+
const PRICING_METADATA = Object.freeze({
|
|
14
|
+
asOf: '2026-04-07',
|
|
15
|
+
verified: false,
|
|
16
|
+
currency: 'USD',
|
|
17
|
+
sources: Object.freeze({
|
|
18
|
+
openai: 'https://openai.com/api/pricing/',
|
|
19
|
+
anthropic: 'https://www.anthropic.com/pricing#api',
|
|
20
|
+
cohere: 'https://cohere.com/pricing',
|
|
21
|
+
}),
|
|
22
|
+
note: 'Built-in list prices, not re-verified since asOf. Override with the `pricing` or `customPricing` option.',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const OPENAI = PRICING_METADATA.sources.openai;
|
|
26
|
+
const ANTHROPIC = PRICING_METADATA.sources.anthropic;
|
|
27
|
+
const COHERE = PRICING_METADATA.sources.cohere;
|
|
28
|
+
const AS_OF = PRICING_METADATA.asOf;
|
|
29
|
+
|
|
5
30
|
/**
|
|
6
31
|
* Built-in pricing table for common LLM and embedding models.
|
|
7
|
-
* Prices are in USD per `unit` tokens.
|
|
8
|
-
*
|
|
32
|
+
* Prices are in USD per `unit` tokens. Each entry records its `source`
|
|
33
|
+
* (provider pricing page) and `asOf` date.
|
|
34
|
+
* @type {Record<string, {input: number, output: number, unit: number, source: string, asOf: string}>}
|
|
9
35
|
*/
|
|
10
36
|
const PRICING = {
|
|
11
|
-
'gpt-4': {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
'gpt-4': {
|
|
38
|
+
input: 0.03,
|
|
39
|
+
output: 0.06,
|
|
40
|
+
unit: 1000,
|
|
41
|
+
source: OPENAI,
|
|
42
|
+
asOf: AS_OF,
|
|
43
|
+
},
|
|
44
|
+
'gpt-4-turbo': {
|
|
45
|
+
input: 0.01,
|
|
46
|
+
output: 0.03,
|
|
47
|
+
unit: 1000,
|
|
48
|
+
source: OPENAI,
|
|
49
|
+
asOf: AS_OF,
|
|
50
|
+
},
|
|
51
|
+
'gpt-4o': {
|
|
52
|
+
input: 0.005,
|
|
53
|
+
output: 0.015,
|
|
54
|
+
unit: 1000,
|
|
55
|
+
source: OPENAI,
|
|
56
|
+
asOf: AS_OF,
|
|
57
|
+
},
|
|
58
|
+
'gpt-4o-mini': {
|
|
59
|
+
input: 0.00015,
|
|
60
|
+
output: 0.0006,
|
|
61
|
+
unit: 1000,
|
|
62
|
+
source: OPENAI,
|
|
63
|
+
asOf: AS_OF,
|
|
64
|
+
},
|
|
65
|
+
'gpt-3.5-turbo': {
|
|
66
|
+
input: 0.0005,
|
|
67
|
+
output: 0.0015,
|
|
68
|
+
unit: 1000,
|
|
69
|
+
source: OPENAI,
|
|
70
|
+
asOf: AS_OF,
|
|
71
|
+
},
|
|
72
|
+
'claude-3-opus': {
|
|
73
|
+
input: 0.015,
|
|
74
|
+
output: 0.075,
|
|
75
|
+
unit: 1000,
|
|
76
|
+
source: ANTHROPIC,
|
|
77
|
+
asOf: AS_OF,
|
|
78
|
+
},
|
|
79
|
+
'claude-3-sonnet': {
|
|
80
|
+
input: 0.003,
|
|
81
|
+
output: 0.015,
|
|
82
|
+
unit: 1000,
|
|
83
|
+
source: ANTHROPIC,
|
|
84
|
+
asOf: AS_OF,
|
|
85
|
+
},
|
|
86
|
+
'claude-3-haiku': {
|
|
87
|
+
input: 0.00025,
|
|
88
|
+
output: 0.00125,
|
|
89
|
+
unit: 1000,
|
|
90
|
+
source: ANTHROPIC,
|
|
91
|
+
asOf: AS_OF,
|
|
92
|
+
},
|
|
93
|
+
'claude-3.5-sonnet': {
|
|
94
|
+
input: 0.003,
|
|
95
|
+
output: 0.015,
|
|
96
|
+
unit: 1000,
|
|
97
|
+
source: ANTHROPIC,
|
|
98
|
+
asOf: AS_OF,
|
|
99
|
+
},
|
|
100
|
+
'cohere-command': {
|
|
101
|
+
input: 0.001,
|
|
102
|
+
output: 0.002,
|
|
103
|
+
unit: 1000,
|
|
104
|
+
source: COHERE,
|
|
105
|
+
asOf: AS_OF,
|
|
106
|
+
},
|
|
107
|
+
'text-embedding-3-small': {
|
|
108
|
+
input: 0.00002,
|
|
109
|
+
output: 0,
|
|
110
|
+
unit: 1000,
|
|
111
|
+
source: OPENAI,
|
|
112
|
+
asOf: AS_OF,
|
|
113
|
+
},
|
|
114
|
+
'text-embedding-3-large': {
|
|
115
|
+
input: 0.00013,
|
|
116
|
+
output: 0,
|
|
117
|
+
unit: 1000,
|
|
118
|
+
source: OPENAI,
|
|
119
|
+
asOf: AS_OF,
|
|
120
|
+
},
|
|
23
121
|
};
|
|
24
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Canonical form used for model-name matching: lower case, with '.' and '_'
|
|
125
|
+
* treated like '-' so "claude-3-5-sonnet-20241022" matches "claude-3.5-sonnet".
|
|
126
|
+
* @param {string} name
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
129
|
+
function canonical(name) {
|
|
130
|
+
return String(name).toLowerCase().replace(/[._]/g, '-');
|
|
131
|
+
}
|
|
132
|
+
|
|
25
133
|
/**
|
|
26
134
|
* Maps token counts to cost estimates by provider and model.
|
|
27
135
|
*
|
|
@@ -33,34 +141,80 @@ const PRICING = {
|
|
|
33
141
|
class CostCalculator extends EventEmitter {
|
|
34
142
|
/**
|
|
35
143
|
* @param {object} [options]
|
|
144
|
+
* @param {Record<string, {input: number, output: number, unit?: number, source?: string, asOf?: string}>} [options.pricing]
|
|
145
|
+
* Replaces the built-in pricing table entirely (only these models are known).
|
|
36
146
|
* @param {Record<string, {input: number, output: number, unit: number}>} [options.customPricing]
|
|
37
|
-
* Additional or override model pricing entries.
|
|
147
|
+
* Additional or override model pricing entries, merged over the base table.
|
|
38
148
|
*/
|
|
39
149
|
constructor(options = {}) {
|
|
40
150
|
super();
|
|
41
|
-
|
|
151
|
+
const base = options.pricing ? {} : PRICING;
|
|
152
|
+
const overrides = {
|
|
153
|
+
...(options.pricing || {}),
|
|
154
|
+
...(options.customPricing || {}),
|
|
155
|
+
};
|
|
156
|
+
this.pricing = { ...base };
|
|
157
|
+
for (const [model, entry] of Object.entries(overrides)) {
|
|
158
|
+
this.pricing[model] = {
|
|
159
|
+
unit: 1000,
|
|
160
|
+
source: 'custom',
|
|
161
|
+
asOf: null,
|
|
162
|
+
...entry,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** @private Models already warned about as unknown */
|
|
166
|
+
this._warnedModels = new Set();
|
|
42
167
|
}
|
|
43
168
|
|
|
44
169
|
/**
|
|
45
170
|
* Resolve a model key from the pricing table.
|
|
46
|
-
* Tries exact match (case-insensitive), then
|
|
171
|
+
* Tries an exact match (case-insensitive, '.' and '-' equivalent), then the
|
|
172
|
+
* longest known key contained in the model name (so dated snapshots such
|
|
173
|
+
* as "gpt-4o-mini-2024-07-18" resolve to "gpt-4o-mini", not "gpt-4"), then
|
|
174
|
+
* a known key that contains the model name.
|
|
47
175
|
* @param {string} model
|
|
48
176
|
* @returns {string|null} The matched pricing key or null.
|
|
49
177
|
* @private
|
|
50
178
|
*/
|
|
51
179
|
_resolveModel(model) {
|
|
52
|
-
|
|
180
|
+
if (model == null) return null;
|
|
181
|
+
const wanted = canonical(model);
|
|
53
182
|
const keys = Object.keys(this.pricing);
|
|
54
183
|
|
|
55
|
-
|
|
56
|
-
const exact = keys.find((k) => k.toLowerCase() === lower);
|
|
184
|
+
const exact = keys.find((k) => canonical(k) === wanted);
|
|
57
185
|
if (exact) return exact;
|
|
58
186
|
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
(
|
|
62
|
-
|
|
63
|
-
|
|
187
|
+
let best = null;
|
|
188
|
+
for (const k of keys) {
|
|
189
|
+
if (wanted.includes(canonical(k)) && (!best || k.length > best.length)) {
|
|
190
|
+
best = k;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (best) return best;
|
|
194
|
+
|
|
195
|
+
const containing = keys.find((k) => canonical(k).includes(wanted));
|
|
196
|
+
return containing || null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Emit a one-time warning for a model with no known price.
|
|
201
|
+
* Listeners on 'warning' receive `{ type: 'unknown-model', model, message }`;
|
|
202
|
+
* without a listener the warning goes to process.emitWarning.
|
|
203
|
+
* @param {string} model
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
_warnUnknownModel(model) {
|
|
207
|
+
const key = String(model);
|
|
208
|
+
if (this._warnedModels.has(key)) return;
|
|
209
|
+
this._warnedModels.add(key);
|
|
210
|
+
const message =
|
|
211
|
+
`No pricing known for model "${key}"; cost reported as 0. ` +
|
|
212
|
+
'Pass `pricing` or `customPricing` to CostCalculator to set a price.';
|
|
213
|
+
if (this.listenerCount('warning') > 0) {
|
|
214
|
+
this.emit('warning', { type: 'unknown-model', model: key, message });
|
|
215
|
+
} else {
|
|
216
|
+
process.emitWarning(message, { code: 'RAG_UNKNOWN_MODEL_PRICING' });
|
|
217
|
+
}
|
|
64
218
|
}
|
|
65
219
|
|
|
66
220
|
/**
|
|
@@ -70,12 +224,14 @@ class CostCalculator extends EventEmitter {
|
|
|
70
224
|
* @param {number} inputTokens - Number of input / prompt tokens.
|
|
71
225
|
* @param {number} [outputTokens=0] - Number of output / completion tokens.
|
|
72
226
|
* @returns {{inputCost: number, outputCost: number, totalCost: number, model: string,
|
|
73
|
-
* inputTokens: number, outputTokens: number, currency: string,
|
|
227
|
+
* inputTokens: number, outputTokens: number, currency: string,
|
|
228
|
+
* pricingSource?: string, pricingAsOf?: string|null, warning?: string}}
|
|
74
229
|
*/
|
|
75
230
|
estimate(model, inputTokens, outputTokens = 0) {
|
|
76
231
|
const resolvedKey = this._resolveModel(model);
|
|
77
232
|
|
|
78
233
|
if (!resolvedKey) {
|
|
234
|
+
this._warnUnknownModel(model);
|
|
79
235
|
return {
|
|
80
236
|
inputCost: 0,
|
|
81
237
|
outputCost: 0,
|
|
@@ -89,8 +245,9 @@ class CostCalculator extends EventEmitter {
|
|
|
89
245
|
}
|
|
90
246
|
|
|
91
247
|
const price = this.pricing[resolvedKey];
|
|
92
|
-
const
|
|
93
|
-
const
|
|
248
|
+
const unit = price.unit || 1000;
|
|
249
|
+
const inputCost = (inputTokens / unit) * price.input;
|
|
250
|
+
const outputCost = (outputTokens / unit) * price.output;
|
|
94
251
|
const totalCost = inputCost + outputCost;
|
|
95
252
|
|
|
96
253
|
return {
|
|
@@ -101,6 +258,8 @@ class CostCalculator extends EventEmitter {
|
|
|
101
258
|
inputTokens,
|
|
102
259
|
outputTokens,
|
|
103
260
|
currency: 'USD',
|
|
261
|
+
pricingSource: price.source || 'custom',
|
|
262
|
+
pricingAsOf: price.asOf ?? null,
|
|
104
263
|
};
|
|
105
264
|
}
|
|
106
265
|
|
|
@@ -108,7 +267,7 @@ class CostCalculator extends EventEmitter {
|
|
|
108
267
|
* Register custom model pricing.
|
|
109
268
|
*
|
|
110
269
|
* @param {string} model - Model identifier.
|
|
111
|
-
* @param {{input: number, output: number, unit
|
|
270
|
+
* @param {{input: number, output: number, unit?: number, source?: string, asOf?: string}} pricing - Pricing definition.
|
|
112
271
|
*/
|
|
113
272
|
addModel(model, pricing) {
|
|
114
273
|
if (
|
|
@@ -120,7 +279,13 @@ class CostCalculator extends EventEmitter {
|
|
|
120
279
|
'Pricing must include numeric "input" and "output" fields',
|
|
121
280
|
);
|
|
122
281
|
}
|
|
123
|
-
this.pricing[model] = {
|
|
282
|
+
this.pricing[model] = {
|
|
283
|
+
unit: 1000,
|
|
284
|
+
source: 'custom',
|
|
285
|
+
asOf: null,
|
|
286
|
+
...pricing,
|
|
287
|
+
};
|
|
288
|
+
this._warnedModels.clear();
|
|
124
289
|
}
|
|
125
290
|
|
|
126
291
|
/**
|
|
@@ -135,12 +300,20 @@ class CostCalculator extends EventEmitter {
|
|
|
135
300
|
* Retrieve pricing for a specific model.
|
|
136
301
|
*
|
|
137
302
|
* @param {string} model - Model identifier (supports fuzzy resolution).
|
|
138
|
-
* @returns {{input: number, output: number, unit: number}|null}
|
|
303
|
+
* @returns {{input: number, output: number, unit: number, source?: string, asOf?: string|null}|null}
|
|
139
304
|
*/
|
|
140
305
|
getModelPricing(model) {
|
|
141
306
|
const key = this._resolveModel(model);
|
|
142
307
|
return key ? { ...this.pricing[key] } : null;
|
|
143
308
|
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Provenance of the built-in pricing table.
|
|
312
|
+
* @returns {typeof PRICING_METADATA}
|
|
313
|
+
*/
|
|
314
|
+
static getPricingMetadata() {
|
|
315
|
+
return PRICING_METADATA;
|
|
316
|
+
}
|
|
144
317
|
}
|
|
145
318
|
|
|
146
|
-
module.exports = { CostCalculator, PRICING };
|
|
319
|
+
module.exports = { CostCalculator, PRICING, PRICING_METADATA };
|