@devilsdev/rag-pipeline-utils 2.4.5 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.ragrc.schema.json +83 -192
  2. package/CHANGELOG.md +345 -0
  3. package/README.md +418 -117
  4. package/SECURITY.md +5 -5
  5. package/dist/experimental.cjs +2 -0
  6. package/dist/experimental.d.ts +1 -0
  7. package/dist/experimental.mjs +5 -0
  8. package/dist/index.cjs +4992 -1242
  9. package/dist/index.d.ts +80 -2
  10. package/dist/index.mjs +1 -1
  11. package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
  12. package/dist/types/agentic/iterative-retriever.d.ts +85 -0
  13. package/dist/types/agentic/query-planner.d.ts +145 -0
  14. package/dist/types/agentic/self-critique.d.ts +45 -0
  15. package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
  16. package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
  17. package/dist/types/ai/multimodal-processing.d.ts +32 -0
  18. package/dist/types/ai/multimodal.d.ts +39 -0
  19. package/dist/types/ai/retrieval-engine.d.ts +37 -0
  20. package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
  21. package/dist/types/chunking/chunking-engine.d.ts +70 -0
  22. package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
  23. package/dist/types/chunking/strategies/recursive.d.ts +20 -0
  24. package/dist/types/chunking/strategies/semantic.d.ts +19 -0
  25. package/dist/types/chunking/strategies/sentence.d.ts +13 -0
  26. package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
  27. package/dist/types/citation/citation-tracker.d.ts +64 -0
  28. package/dist/types/citation/hallucination-detector.d.ts +40 -0
  29. package/dist/types/citation/source-mapper.d.ts +37 -0
  30. package/dist/types/connectors/anthropic-connector.d.ts +92 -0
  31. package/dist/types/connectors/base-connector.d.ts +87 -0
  32. package/dist/types/connectors/cohere-connector.d.ts +89 -0
  33. package/dist/types/connectors/connector-utils.d.ts +110 -0
  34. package/dist/types/connectors/local-embedder.d.ts +86 -0
  35. package/dist/types/connectors/memory-retriever.d.ts +91 -0
  36. package/dist/types/connectors/ollama-connector.d.ts +110 -0
  37. package/dist/types/connectors/openai-connector.d.ts +114 -0
  38. package/dist/types/connectors/rag-prompt.d.ts +40 -0
  39. package/dist/types/cost/cost-calculator.d.ts +152 -0
  40. package/dist/types/cost/cost-tracker.d.ts +126 -0
  41. package/dist/types/cost/token-budget.d.ts +110 -0
  42. package/dist/types/dag/core/error-context.d.ts +239 -0
  43. package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
  44. package/dist/types/dag/core/topology-validation.d.ts +226 -0
  45. package/dist/types/dag/dag-engine.d.ts +156 -0
  46. package/dist/types/dag/dag-node.d.ts +37 -0
  47. package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
  48. package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
  49. package/dist/types/debug/execution-tracer.d.ts +57 -0
  50. package/dist/types/debug/trace-inspector.d.ts +31 -0
  51. package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
  52. package/dist/types/enterprise/data-governance.d.ts +203 -0
  53. package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
  54. package/dist/types/enterprise/sso-integration.d.ts +420 -0
  55. package/dist/types/evaluate/context-metrics.d.ts +55 -0
  56. package/dist/types/evaluate/faithfulness.d.ts +41 -0
  57. package/dist/types/evaluate/groundedness.d.ts +60 -0
  58. package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
  59. package/dist/types/evaluate/relevance.d.ts +21 -0
  60. package/dist/types/evaluate/scoring.d.ts +123 -0
  61. package/dist/types/experimental.d.ts +15 -0
  62. package/dist/types/graph/entity-extractor.d.ts +84 -0
  63. package/dist/types/graph/graph-index.d.ts +49 -0
  64. package/dist/types/graph/graph-retriever.d.ts +81 -0
  65. package/dist/types/graph/knowledge-graph.d.ts +129 -0
  66. package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
  67. package/dist/types/guardrails/injection-detector.d.ts +47 -0
  68. package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
  69. package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
  70. package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
  71. package/dist/types/mcp/mcp-server.d.ts +85 -0
  72. package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
  73. package/dist/types/reranker/cascade-reranker.d.ts +40 -0
  74. package/dist/types/reranker/embedding-reranker.d.ts +77 -0
  75. package/dist/types/reranker/scoring-reranker.d.ts +41 -0
  76. package/dist/types/retrieval/bm25-search.d.ts +43 -0
  77. package/dist/types/retrieval/file-vector-store.d.ts +87 -0
  78. package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
  79. package/dist/types/retrieval/rank-fusion.d.ts +10 -0
  80. package/dist/types/security/jwt-config.d.ts +80 -0
  81. package/dist/types/security/jwt-validator.d.ts +195 -0
  82. package/dist/types/security/plugin-sandbox.d.ts +158 -0
  83. package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
  84. package/dist/types/security/validator.d.ts +5 -0
  85. package/dist/types/streaming/embedding-stream.d.ts +65 -0
  86. package/dist/types/streaming/sse-adapter.d.ts +59 -0
  87. package/dist/types/streaming/stream-router.d.ts +88 -0
  88. package/dist/types/streaming/streaming-embedder.d.ts +99 -0
  89. package/dist/types/streaming/websocket-adapter.d.ts +71 -0
  90. package/dist/types/templates/scaffold.d.ts +55 -0
  91. package/dist/types/templates/template-registry.d.ts +63 -0
  92. package/dist/types/utils/embed-query.d.ts +20 -0
  93. package/dist/types/utils/logger.d.ts +50 -0
  94. package/dist/types/utils/rate-limiter.d.ts +66 -0
  95. package/dist/types/utils/retry.d.ts +27 -0
  96. package/dist/types/utils/run-abort.d.ts +34 -0
  97. package/dist/types/utils/secure-logging.d.ts +51 -0
  98. package/dist/types/utils/structured-logger.d.ts +126 -0
  99. package/package.json +53 -29
  100. package/src/agentic/agentic-pipeline.js +75 -7
  101. package/src/agentic/iterative-retriever.js +47 -10
  102. package/src/agentic/query-planner.js +329 -78
  103. package/src/citation/citation-tracker.js +6 -4
  104. package/src/citation/hallucination-detector.js +2 -2
  105. package/src/citation/source-mapper.js +15 -28
  106. package/src/cli/commands/ai.js +101 -0
  107. package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
  108. package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
  109. package/src/cli/commands/plugin-hub/local-commands.js +143 -0
  110. package/src/cli/commands/plugin-hub.js +96 -36
  111. package/src/cli/diagnostics/config-checker.js +76 -63
  112. package/src/cli/diagnostics/environment-checker.js +62 -31
  113. package/src/cli/diagnostics/plugin-checker.js +36 -89
  114. package/src/cli/doctor-command.js +39 -5
  115. package/src/cli/enhanced-cli-commands.js +72 -217
  116. package/src/cli/handlers/ai-handler.js +364 -0
  117. package/src/cli/handlers/diagnostics-handler.js +78 -31
  118. package/src/cli/handlers/ingest-handler.js +28 -22
  119. package/src/cli/handlers/init-handler.js +148 -25
  120. package/src/cli/handlers/query-handler.js +41 -26
  121. package/src/cli/handlers/run-handler.js +16 -167
  122. package/src/cli/handlers/serve-handler.js +60 -0
  123. package/src/cli/services/bench-stats.js +54 -0
  124. package/src/cli/services/benchmark.js +161 -0
  125. package/src/cli/services/feedback.js +155 -0
  126. package/src/cli/services/rag-service.js +322 -0
  127. package/src/cli/shared/cli-utils.js +24 -7
  128. package/src/cli/shared/config-context.js +21 -0
  129. package/src/cli/wizard/config-builder.js +81 -0
  130. package/src/cli/wizard/init-wizard.js +617 -0
  131. package/src/config/component-factory.js +125 -0
  132. package/src/config/load-config.js +29 -10
  133. package/src/config/load-plugin-config.js +46 -2
  134. package/src/connectors/anthropic-connector.js +93 -55
  135. package/src/connectors/base-connector.js +74 -19
  136. package/src/connectors/cohere-connector.js +88 -32
  137. package/src/connectors/connector-utils.js +302 -0
  138. package/src/connectors/local-embedder.js +43 -5
  139. package/src/connectors/memory-retriever.js +108 -15
  140. package/src/connectors/ollama-connector.js +83 -49
  141. package/src/connectors/openai-connector.js +108 -44
  142. package/src/connectors/rag-prompt.js +88 -0
  143. package/src/core/create-pipeline.js +209 -60
  144. package/src/core/plugin-registry.js +87 -11
  145. package/src/cost/cost-calculator.js +205 -32
  146. package/src/dev/dev-server.js +166 -11
  147. package/src/dev/hot-reload.js +40 -1
  148. package/src/ecosystem/plugin-checks.js +181 -0
  149. package/src/ecosystem/plugin-hub.js +50 -125
  150. package/src/ecosystem/plugin-installer.js +298 -0
  151. package/src/ecosystem/plugin-probe.js +127 -0
  152. package/src/ecosystem/plugin-scanner.js +185 -0
  153. package/src/ecosystem/tar-extract.js +114 -0
  154. package/src/enterprise/audit-logging.js +158 -29
  155. package/src/enterprise/sso-integration.js +715 -228
  156. package/src/evaluate/context-metrics.js +28 -16
  157. package/src/evaluate/evaluator.js +3 -3
  158. package/src/evaluate/faithfulness.js +18 -14
  159. package/src/evaluate/groundedness.js +43 -12
  160. package/src/evaluate/pipeline-evaluator.js +35 -8
  161. package/src/evaluate/scoring.js +251 -3
  162. package/src/experimental.js +71 -0
  163. package/src/guardrails/guardrails-pipeline.js +69 -7
  164. package/src/guardrails/injection-detector.js +283 -0
  165. package/src/guardrails/post-generation-guard.js +19 -0
  166. package/src/guardrails/pre-retrieval-guard.js +36 -36
  167. package/src/guardrails/retrieval-guard.js +58 -8
  168. package/src/index.js +36 -1
  169. package/src/loader/document-reader.js +91 -0
  170. package/src/reranker/cascade-reranker.js +3 -0
  171. package/src/reranker/embedding-reranker.js +136 -21
  172. package/src/reranker/llm-reranker.js +79 -12
  173. package/src/reranker/scoring-reranker.js +3 -0
  174. package/src/retrieval/bm25-search.js +12 -6
  175. package/src/retrieval/file-vector-store.js +212 -0
  176. package/src/retrieval/hybrid-retriever.js +22 -19
  177. package/src/security/jwt-config.js +13 -2
  178. package/src/security/jwt-validator.js +134 -30
  179. package/src/security/plugin-sandbox.js +18 -1
  180. package/src/security/plugin-signature-verifier.js +245 -70
  181. package/src/utils/deprecate.js +69 -0
  182. package/src/utils/embed-query.js +33 -0
  183. package/src/utils/input-sanitizer.js +19 -4
  184. package/src/utils/logger.js +34 -4
  185. package/src/utils/run-abort.js +102 -0
  186. package/src/ai/federated-learning.js +0 -211
  187. package/src/ai/federation.js +0 -71
  188. package/src/ai/index.js +0 -22
  189. package/src/ai/model-trainer.js +0 -217
  190. package/src/ai/model-training.js +0 -202
  191. package/src/ai/multimodal/multi-modal-processor.js +0 -345
  192. package/src/ai/orchestrator.js +0 -65
  193. package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
  194. package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
  195. package/src/cli/commands/ai-ml/federated-commands.js +0 -207
  196. package/src/cli/commands/ai-ml/index.js +0 -17
  197. package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
  198. package/src/cli/commands/ai-ml/shared.js +0 -51
  199. package/src/cli/commands/ai-ml/training-commands.js +0 -228
  200. package/src/cli/commands/ai-ml.js +0 -386
  201. package/src/cli/commands/docs.js +0 -228
  202. package/src/cli/commands/dx.js +0 -538
  203. package/src/cli/commands/wizard.js +0 -627
  204. package/src/cli/input-sanitization-middleware.js +0 -356
  205. package/src/cli/interactive-wizard.js +0 -373
  206. package/src/cli/templates/development.template.json +0 -64
  207. package/src/cli/templates/minimal.template.json +0 -33
  208. package/src/cli/templates/production.template.json +0 -104
  209. package/src/cli/templates/testing.template.json +0 -45
  210. package/src/cli/wizard/configuration.js +0 -148
  211. package/src/cli/wizard/observability.js +0 -79
  212. package/src/cli/wizard/performance.js +0 -76
  213. package/src/cli/wizard/plugin-selection.js +0 -297
  214. package/src/cli/wizard/preview-save.js +0 -172
  215. package/src/cli/wizard/project-setup.js +0 -66
  216. package/src/core/pipeline-factory.js +0 -59
  217. package/src/dag/enhanced-dag-engine.js +0 -894
  218. package/src/dx/index.js +0 -40
  219. package/src/dx/integration-templates.js +0 -662
  220. package/src/dx/performance-profiler.js +0 -968
  221. package/src/dx/realtime-debugger.js +0 -755
  222. package/src/dx/visual-pipeline-builder.js +0 -578
  223. package/src/ingest.js +0 -59
  224. package/src/pipeline/pipeline.js +0 -18
  225. package/src/query.js +0 -117
@@ -8,6 +8,21 @@ const {
8
8
  } = require('../security/plugin-signature-verifier.js');
9
9
  const { logger } = require('../utils/logger');
10
10
 
11
+ /**
12
+ * Locate the package's contracts/ directory. This file lives in src/core/ when
13
+ * loaded from source but is inlined into dist/index.cjs when bundled, so
14
+ * __dirname differs between the two layouts.
15
+ *
16
+ * @returns {string} Absolute path to the contracts directory
17
+ */
18
+ function resolveContractsDir() {
19
+ const candidates = [
20
+ path.join(__dirname, '../../contracts'),
21
+ path.join(__dirname, '../contracts'),
22
+ ];
23
+ return candidates.find((dir) => fs.existsSync(dir)) || candidates[0];
24
+ }
25
+
11
26
  class PluginRegistry {
12
27
  constructor(options = {}) {
13
28
  this._plugins = new Map();
@@ -67,7 +82,11 @@ class PluginRegistry {
67
82
  failClosed:
68
83
  options.failClosed !== undefined ? options.failClosed : isProduction, // Fail closed in production, fail open in development
69
84
  trustedKeysPath: options.trustedKeysPath,
85
+ requireContentDigest: options.requireContentDigest === true,
70
86
  });
87
+ // Opt-in: when verification is enabled, refuse plugins registered
88
+ // without a signed manifest (default false for backward compatibility).
89
+ this._requireSignedPlugins = options.requireSignedPlugins === true;
71
90
  this._loadContracts();
72
91
  }
73
92
 
@@ -82,8 +101,8 @@ class PluginRegistry {
82
101
  _loadContractSchema() {
83
102
  try {
84
103
  const schemaPath = path.join(
85
- __dirname,
86
- '../../contracts/contract-schema.json',
104
+ resolveContractsDir(),
105
+ 'contract-schema.json',
87
106
  );
88
107
  if (fs.existsSync(schemaPath)) {
89
108
  const schemaContent = fs.readFileSync(schemaPath, 'utf8');
@@ -256,7 +275,7 @@ class PluginRegistry {
256
275
  }
257
276
 
258
277
  _loadContracts() {
259
- const contractsDir = path.join(__dirname, '../../contracts');
278
+ const contractsDir = resolveContractsDir();
260
279
 
261
280
  try {
262
281
  const contractFiles = {
@@ -396,7 +415,20 @@ class PluginRegistry {
396
415
  return methods;
397
416
  }
398
417
 
399
- async register(category, name, impl, manifest = null) {
418
+ /**
419
+ * Register a plugin.
420
+ *
421
+ * @param {string} category - Plugin type
422
+ * @param {string} name - Plugin name
423
+ * @param {object} impl - Plugin implementation
424
+ * @param {object|null} [manifest] - Signed manifest; when given, its
425
+ * signature is verified before registration
426
+ * @param {object} [options]
427
+ * @param {object} [options.content] - Plugin content checked against the
428
+ * manifest's contentDigest: `{ files, baseDir, contents }`
429
+ * @returns {Promise<PluginRegistry>}
430
+ */
431
+ async register(category, name, impl, manifest = null, options = {}) {
400
432
  if (!category || typeof category !== 'string') {
401
433
  throw new Error('Category must be a non-empty string');
402
434
  }
@@ -412,7 +444,22 @@ class PluginRegistry {
412
444
 
413
445
  // Verify plugin signature if manifest provided
414
446
  if (manifest) {
415
- await this._verifyPluginSignature(manifest, name);
447
+ await this._verifyPluginSignature(manifest, name, options.content);
448
+ } else if (
449
+ this._requireSignedPlugins &&
450
+ this.isSignatureVerificationEnabled()
451
+ ) {
452
+ this._emitAuditEntry('plugin_verification_failed', {
453
+ pluginName: name,
454
+ signerId: 'unknown',
455
+ version: 'unknown',
456
+ verified: false,
457
+ error: 'missing signed manifest',
458
+ timestamp: new Date().toISOString(),
459
+ });
460
+ throw new Error(
461
+ `Plugin ${name} has no signed manifest and requireSignedPlugins is enabled`,
462
+ );
416
463
  }
417
464
 
418
465
  // Validate plugin against contract
@@ -428,7 +475,7 @@ class PluginRegistry {
428
475
  * @param {object} manifest - Plugin manifest
429
476
  * @param {string} pluginName - Plugin name
430
477
  */
431
- async _verifyPluginSignature(manifest, pluginName) {
478
+ async _verifyPluginSignature(manifest, pluginName, content = undefined) {
432
479
  try {
433
480
  // Extract signature information from manifest
434
481
  if (!manifest.signature || !manifest.signerId) {
@@ -437,12 +484,12 @@ class PluginRegistry {
437
484
  );
438
485
  }
439
486
 
487
+ const args = [manifest, manifest.signature, manifest.signerId];
488
+ if (content) {
489
+ args.push(content);
490
+ }
440
491
  const verificationResult =
441
- await this._signatureVerifier.verifyPluginSignature(
442
- manifest,
443
- manifest.signature,
444
- manifest.signerId,
445
- );
492
+ await this._signatureVerifier.verifyPluginSignature(...args);
446
493
 
447
494
  if (!verificationResult.verified) {
448
495
  throw new Error(
@@ -473,6 +520,35 @@ class PluginRegistry {
473
520
  }
474
521
  }
475
522
 
523
+ /**
524
+ * Whether plugin signature verification is enabled for this registry.
525
+ * @returns {boolean}
526
+ */
527
+ isSignatureVerificationEnabled() {
528
+ return Boolean(this._signatureVerifier && this._signatureVerifier.enabled);
529
+ }
530
+
531
+ /**
532
+ * Whether plugins without a signed manifest are rejected.
533
+ * @returns {boolean}
534
+ */
535
+ requiresSignedPlugins() {
536
+ return this._requireSignedPlugins && this.isSignatureVerificationEnabled();
537
+ }
538
+
539
+ /**
540
+ * Verify a plugin's signed manifest and code before it is loaded (use this
541
+ * before importing plugin files, since importing executes them).
542
+ *
543
+ * @param {string} pluginName - Plugin name (for audit entries)
544
+ * @param {object} manifest - Signed manifest
545
+ * @param {object} content - `{ files, baseDir, contents }`
546
+ * @returns {Promise<void>} Rejects if verification fails
547
+ */
548
+ async verifyPluginContent(pluginName, manifest, content) {
549
+ await this._verifyPluginSignature(manifest, pluginName, content);
550
+ }
551
+
476
552
  /**
477
553
  * Emit audit entry
478
554
  * @param {string} action - Audit action
@@ -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
- * @type {Record<string, {input: number, output: number, unit: number}>}
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': { input: 0.03, output: 0.06, unit: 1000 },
12
- 'gpt-4-turbo': { input: 0.01, output: 0.03, unit: 1000 },
13
- 'gpt-4o': { input: 0.005, output: 0.015, unit: 1000 },
14
- 'gpt-4o-mini': { input: 0.00015, output: 0.0006, unit: 1000 },
15
- 'gpt-3.5-turbo': { input: 0.0005, output: 0.0015, unit: 1000 },
16
- 'claude-3-opus': { input: 0.015, output: 0.075, unit: 1000 },
17
- 'claude-3-sonnet': { input: 0.003, output: 0.015, unit: 1000 },
18
- 'claude-3-haiku': { input: 0.00025, output: 0.00125, unit: 1000 },
19
- 'claude-3.5-sonnet': { input: 0.003, output: 0.015, unit: 1000 },
20
- 'cohere-command': { input: 0.001, output: 0.002, unit: 1000 },
21
- 'text-embedding-3-small': { input: 0.00002, output: 0, unit: 1000 },
22
- 'text-embedding-3-large': { input: 0.00013, output: 0, unit: 1000 },
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
- this.pricing = { ...PRICING, ...(options.customPricing || {}) };
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 partial match.
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
- const lower = model.toLowerCase();
180
+ if (model == null) return null;
181
+ const wanted = canonical(model);
53
182
  const keys = Object.keys(this.pricing);
54
183
 
55
- // Exact case-insensitive match
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
- // Partial match — model string contains a known key or vice-versa
60
- const partial = keys.find(
61
- (k) => lower.includes(k.toLowerCase()) || k.toLowerCase().includes(lower),
62
- );
63
- return partial || null;
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, warning?: 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 inputCost = (inputTokens / price.unit) * price.input;
93
- const outputCost = (outputTokens / price.unit) * price.output;
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: number}} pricing - Pricing definition.
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] = { unit: 1000, ...pricing };
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 };