@newrelic/preflight 0.0.1-pre.1 → 1.0.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/LICENSE +183 -0
- package/README.md +498 -0
- package/dist/alerts/alert-log.d.ts +24 -0
- package/dist/alerts/alert-log.d.ts.map +1 -0
- package/dist/alerts/alert-log.js +159 -0
- package/dist/alerts/alert-log.js.map +1 -0
- package/dist/alerts/alert-snapshot-collector.d.ts +168 -0
- package/dist/alerts/alert-snapshot-collector.d.ts.map +1 -0
- package/dist/alerts/alert-snapshot-collector.js +243 -0
- package/dist/alerts/alert-snapshot-collector.js.map +1 -0
- package/dist/alerts/local-alert-engine.d.ts +86 -0
- package/dist/alerts/local-alert-engine.d.ts.map +1 -0
- package/dist/alerts/local-alert-engine.js +466 -0
- package/dist/alerts/local-alert-engine.js.map +1 -0
- package/dist/alerts/local-alert-rule.d.ts +439 -0
- package/dist/alerts/local-alert-rule.d.ts.map +1 -0
- package/dist/alerts/local-alert-rule.js +139 -0
- package/dist/alerts/local-alert-rule.js.map +1 -0
- package/dist/alerts/os-notifier.d.ts +39 -0
- package/dist/alerts/os-notifier.d.ts.map +1 -0
- package/dist/alerts/os-notifier.js +170 -0
- package/dist/alerts/os-notifier.js.map +1 -0
- package/dist/alerts/types.d.ts +35 -0
- package/dist/alerts/types.d.ts.map +1 -0
- package/dist/alerts/types.js +8 -0
- package/dist/alerts/types.js.map +1 -0
- package/dist/config.d.ts +169 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +860 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/dashboard-server.d.ts +38 -0
- package/dist/dashboard/dashboard-server.d.ts.map +1 -0
- package/dist/dashboard/dashboard-server.js +207 -0
- package/dist/dashboard/dashboard-server.js.map +1 -0
- package/dist/dashboard/index.d.ts +3 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +2 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/live-event-bus.d.ts +99 -0
- package/dist/dashboard/live-event-bus.d.ts.map +1 -0
- package/dist/dashboard/live-event-bus.js +56 -0
- package/dist/dashboard/live-event-bus.js.map +1 -0
- package/dist/dashboard/routes/api-handler.d.ts +122 -0
- package/dist/dashboard/routes/api-handler.d.ts.map +1 -0
- package/dist/dashboard/routes/api-handler.js +1414 -0
- package/dist/dashboard/routes/api-handler.js.map +1 -0
- package/dist/dashboard/routes/replay-analyzer.d.ts +15 -0
- package/dist/dashboard/routes/replay-analyzer.d.ts.map +1 -0
- package/dist/dashboard/routes/replay-analyzer.js +227 -0
- package/dist/dashboard/routes/replay-analyzer.js.map +1 -0
- package/dist/dashboard/routes/sse-handler.d.ts +4 -0
- package/dist/dashboard/routes/sse-handler.d.ts.map +1 -0
- package/dist/dashboard/routes/sse-handler.js +122 -0
- package/dist/dashboard/routes/sse-handler.js.map +1 -0
- package/dist/dashboard/routes/static-handler.d.ts +3 -0
- package/dist/dashboard/routes/static-handler.d.ts.map +1 -0
- package/dist/dashboard/routes/static-handler.js +103 -0
- package/dist/dashboard/routes/static-handler.js.map +1 -0
- package/dist/data/alerts/conditions/01-daily-cost-spike.json +16 -0
- package/dist/data/alerts/conditions/02-low-efficiency-score.json +16 -0
- package/dist/data/alerts/conditions/03-stuck-loop-rate.json +16 -0
- package/dist/data/alerts/conditions/04-anti-pattern-rate.json +16 -0
- package/dist/data/alerts/conditions/05-session-cost-budget.json +16 -0
- package/dist/data/alerts/conditions-personal/01-personal-daily-cost.json +16 -0
- package/dist/data/alerts/conditions-personal/02-personal-session-cost.json +16 -0
- package/dist/data/alerts/conditions-personal/03-personal-low-efficiency.json +16 -0
- package/dist/data/alerts/conditions-personal/04-personal-anti-pattern-rate.json +16 -0
- package/dist/data/alerts/conditions-personal/05-personal-stuck-loop.json +16 -0
- package/dist/data/alerts/policy.json +4 -0
- package/dist/data/dashboards/ai-coding-assistant-manager-view.json +103 -0
- package/dist/data/dashboards/ai-coding-assistant-overview.json +239 -0
- package/dist/data/dashboards/ai-coding-assistant-personal.json +442 -0
- package/dist/data/dashboards/ai-coding-assistant-platform-comparison.json +320 -0
- package/dist/data/dashboards/ai-coding-assistant-security.json +275 -0
- package/dist/data/dashboards/ai-coding-assistant-session-detail.json +296 -0
- package/dist/data/dashboards/ai-coding-assistant-team-view.json +345 -0
- package/dist/deploy/data-paths.d.ts +22 -0
- package/dist/deploy/data-paths.d.ts.map +1 -0
- package/dist/deploy/data-paths.js +69 -0
- package/dist/deploy/data-paths.js.map +1 -0
- package/dist/deploy/deploy-alerts.d.ts +58 -0
- package/dist/deploy/deploy-alerts.d.ts.map +1 -0
- package/dist/deploy/deploy-alerts.js +371 -0
- package/dist/deploy/deploy-alerts.js.map +1 -0
- package/dist/deploy/deploy-dashboards.d.ts +92 -0
- package/dist/deploy/deploy-dashboards.d.ts.map +1 -0
- package/dist/deploy/deploy-dashboards.js +282 -0
- package/dist/deploy/deploy-dashboards.js.map +1 -0
- package/dist/digest/digest-formatter.d.ts +3 -0
- package/dist/digest/digest-formatter.d.ts.map +1 -0
- package/dist/digest/digest-formatter.js +37 -0
- package/dist/digest/digest-formatter.js.map +1 -0
- package/dist/digest/digest-sender.d.ts +2 -0
- package/dist/digest/digest-sender.d.ts.map +1 -0
- package/dist/digest/digest-sender.js +29 -0
- package/dist/digest/digest-sender.js.map +1 -0
- package/dist/hooks/bash-classifier.d.ts +26 -0
- package/dist/hooks/bash-classifier.d.ts.map +1 -0
- package/dist/hooks/bash-classifier.js +409 -0
- package/dist/hooks/bash-classifier.js.map +1 -0
- package/dist/hooks/collector-script.d.ts +47 -0
- package/dist/hooks/collector-script.d.ts.map +1 -0
- package/dist/hooks/collector-script.js +662 -0
- package/dist/hooks/collector-script.js.map +1 -0
- package/dist/hooks/event-processor.d.ts +65 -0
- package/dist/hooks/event-processor.d.ts.map +1 -0
- package/dist/hooks/event-processor.js +342 -0
- package/dist/hooks/event-processor.js.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/session-resolver.d.ts +66 -0
- package/dist/hooks/session-resolver.d.ts.map +1 -0
- package/dist/hooks/session-resolver.js +196 -0
- package/dist/hooks/session-resolver.js.map +1 -0
- package/dist/hooks/tool-parsers.d.ts +19 -0
- package/dist/hooks/tool-parsers.d.ts.map +1 -0
- package/dist/hooks/tool-parsers.js +260 -0
- package/dist/hooks/tool-parsers.js.map +1 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1505 -0
- package/dist/index.js.map +1 -0
- package/dist/install/cli.d.ts +11 -0
- package/dist/install/cli.d.ts.map +1 -0
- package/dist/install/cli.js +365 -0
- package/dist/install/cli.js.map +1 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +3 -0
- package/dist/install/index.js.map +1 -0
- package/dist/install/install-helper.d.ts +35 -0
- package/dist/install/install-helper.d.ts.map +1 -0
- package/dist/install/install-helper.js +227 -0
- package/dist/install/install-helper.js.map +1 -0
- package/dist/install/key-validator.d.ts +19 -0
- package/dist/install/key-validator.d.ts.map +1 -0
- package/dist/install/key-validator.js +122 -0
- package/dist/install/key-validator.js.map +1 -0
- package/dist/install/migrate.d.ts +12 -0
- package/dist/install/migrate.d.ts.map +1 -0
- package/dist/install/migrate.js +115 -0
- package/dist/install/migrate.js.map +1 -0
- package/dist/install/schedule.d.ts +11 -0
- package/dist/install/schedule.d.ts.map +1 -0
- package/dist/install/schedule.js +114 -0
- package/dist/install/schedule.js.map +1 -0
- package/dist/install/setup-wizard.d.ts +40 -0
- package/dist/install/setup-wizard.d.ts.map +1 -0
- package/dist/install/setup-wizard.js +489 -0
- package/dist/install/setup-wizard.js.map +1 -0
- package/dist/lib/date.d.ts +54 -0
- package/dist/lib/date.d.ts.map +1 -0
- package/dist/lib/date.js +85 -0
- package/dist/lib/date.js.map +1 -0
- package/dist/metrics/anti-patterns.d.ts +62 -0
- package/dist/metrics/anti-patterns.d.ts.map +1 -0
- package/dist/metrics/anti-patterns.js +301 -0
- package/dist/metrics/anti-patterns.js.map +1 -0
- package/dist/metrics/api-failure-tracker.d.ts +82 -0
- package/dist/metrics/api-failure-tracker.d.ts.map +1 -0
- package/dist/metrics/api-failure-tracker.js +202 -0
- package/dist/metrics/api-failure-tracker.js.map +1 -0
- package/dist/metrics/budget-tracker.d.ts +60 -0
- package/dist/metrics/budget-tracker.d.ts.map +1 -0
- package/dist/metrics/budget-tracker.js +130 -0
- package/dist/metrics/budget-tracker.js.map +1 -0
- package/dist/metrics/claudemd-tracker.d.ts +108 -0
- package/dist/metrics/claudemd-tracker.d.ts.map +1 -0
- package/dist/metrics/claudemd-tracker.js +337 -0
- package/dist/metrics/claudemd-tracker.js.map +1 -0
- package/dist/metrics/collaboration-profile.d.ts +65 -0
- package/dist/metrics/collaboration-profile.d.ts.map +1 -0
- package/dist/metrics/collaboration-profile.js +231 -0
- package/dist/metrics/collaboration-profile.js.map +1 -0
- package/dist/metrics/context-composition-tracker.d.ts +74 -0
- package/dist/metrics/context-composition-tracker.d.ts.map +1 -0
- package/dist/metrics/context-composition-tracker.js +202 -0
- package/dist/metrics/context-composition-tracker.js.map +1 -0
- package/dist/metrics/context-tracker.d.ts +78 -0
- package/dist/metrics/context-tracker.d.ts.map +1 -0
- package/dist/metrics/context-tracker.js +222 -0
- package/dist/metrics/context-tracker.js.map +1 -0
- package/dist/metrics/context-window-tracker.d.ts +18 -0
- package/dist/metrics/context-window-tracker.d.ts.map +1 -0
- package/dist/metrics/context-window-tracker.js +35 -0
- package/dist/metrics/context-window-tracker.js.map +1 -0
- package/dist/metrics/cost-forecast.d.ts +36 -0
- package/dist/metrics/cost-forecast.d.ts.map +1 -0
- package/dist/metrics/cost-forecast.js +91 -0
- package/dist/metrics/cost-forecast.js.map +1 -0
- package/dist/metrics/cost-per-outcome.d.ts +102 -0
- package/dist/metrics/cost-per-outcome.d.ts.map +1 -0
- package/dist/metrics/cost-per-outcome.js +266 -0
- package/dist/metrics/cost-per-outcome.js.map +1 -0
- package/dist/metrics/cost-tracker.d.ts +78 -0
- package/dist/metrics/cost-tracker.d.ts.map +1 -0
- package/dist/metrics/cost-tracker.js +169 -0
- package/dist/metrics/cost-tracker.js.map +1 -0
- package/dist/metrics/decision-tracker.d.ts +49 -0
- package/dist/metrics/decision-tracker.d.ts.map +1 -0
- package/dist/metrics/decision-tracker.js +161 -0
- package/dist/metrics/decision-tracker.js.map +1 -0
- package/dist/metrics/efficiency-score.d.ts +80 -0
- package/dist/metrics/efficiency-score.d.ts.map +1 -0
- package/dist/metrics/efficiency-score.js +219 -0
- package/dist/metrics/efficiency-score.js.map +1 -0
- package/dist/metrics/git-efficiency-tracker.d.ts +165 -0
- package/dist/metrics/git-efficiency-tracker.d.ts.map +1 -0
- package/dist/metrics/git-efficiency-tracker.js +1056 -0
- package/dist/metrics/git-efficiency-tracker.js.map +1 -0
- package/dist/metrics/index.d.ts +26 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +14 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instruction-drift-tracker.d.ts +69 -0
- package/dist/metrics/instruction-drift-tracker.d.ts.map +1 -0
- package/dist/metrics/instruction-drift-tracker.js +213 -0
- package/dist/metrics/instruction-drift-tracker.js.map +1 -0
- package/dist/metrics/latency-decomposition.d.ts +50 -0
- package/dist/metrics/latency-decomposition.d.ts.map +1 -0
- package/dist/metrics/latency-decomposition.js +112 -0
- package/dist/metrics/latency-decomposition.js.map +1 -0
- package/dist/metrics/latency-tracker.d.ts +33 -0
- package/dist/metrics/latency-tracker.d.ts.map +1 -0
- package/dist/metrics/latency-tracker.js +93 -0
- package/dist/metrics/latency-tracker.js.map +1 -0
- package/dist/metrics/live-session-registry.d.ts +29 -0
- package/dist/metrics/live-session-registry.d.ts.map +1 -0
- package/dist/metrics/live-session-registry.js +103 -0
- package/dist/metrics/live-session-registry.js.map +1 -0
- package/dist/metrics/model-usage-tracker.d.ts +21 -0
- package/dist/metrics/model-usage-tracker.d.ts.map +1 -0
- package/dist/metrics/model-usage-tracker.js +53 -0
- package/dist/metrics/model-usage-tracker.js.map +1 -0
- package/dist/metrics/percentile.d.ts +5 -0
- package/dist/metrics/percentile.d.ts.map +1 -0
- package/dist/metrics/percentile.js +10 -0
- package/dist/metrics/percentile.js.map +1 -0
- package/dist/metrics/personal-coach.d.ts +47 -0
- package/dist/metrics/personal-coach.d.ts.map +1 -0
- package/dist/metrics/personal-coach.js +241 -0
- package/dist/metrics/personal-coach.js.map +1 -0
- package/dist/metrics/prompt-feedback.d.ts +75 -0
- package/dist/metrics/prompt-feedback.d.ts.map +1 -0
- package/dist/metrics/prompt-feedback.js +286 -0
- package/dist/metrics/prompt-feedback.js.map +1 -0
- package/dist/metrics/proxy-metrics.d.ts +54 -0
- package/dist/metrics/proxy-metrics.d.ts.map +1 -0
- package/dist/metrics/proxy-metrics.js +228 -0
- package/dist/metrics/proxy-metrics.js.map +1 -0
- package/dist/metrics/quality-proxy-tracker.d.ts +51 -0
- package/dist/metrics/quality-proxy-tracker.d.ts.map +1 -0
- package/dist/metrics/quality-proxy-tracker.js +162 -0
- package/dist/metrics/quality-proxy-tracker.js.map +1 -0
- package/dist/metrics/recommendation-engine.d.ts +72 -0
- package/dist/metrics/recommendation-engine.d.ts.map +1 -0
- package/dist/metrics/recommendation-engine.js +207 -0
- package/dist/metrics/recommendation-engine.js.map +1 -0
- package/dist/metrics/retry-detector.d.ts +43 -0
- package/dist/metrics/retry-detector.d.ts.map +1 -0
- package/dist/metrics/retry-detector.js +179 -0
- package/dist/metrics/retry-detector.js.map +1 -0
- package/dist/metrics/session-tracker.d.ts +75 -0
- package/dist/metrics/session-tracker.d.ts.map +1 -0
- package/dist/metrics/session-tracker.js +249 -0
- package/dist/metrics/session-tracker.js.map +1 -0
- package/dist/metrics/task-completion-tracker.d.ts +15 -0
- package/dist/metrics/task-completion-tracker.d.ts.map +1 -0
- package/dist/metrics/task-completion-tracker.js +27 -0
- package/dist/metrics/task-completion-tracker.js.map +1 -0
- package/dist/metrics/task-detector.d.ts +84 -0
- package/dist/metrics/task-detector.d.ts.map +1 -0
- package/dist/metrics/task-detector.js +302 -0
- package/dist/metrics/task-detector.js.map +1 -0
- package/dist/metrics/tool-selection-scorer.d.ts +39 -0
- package/dist/metrics/tool-selection-scorer.d.ts.map +1 -0
- package/dist/metrics/tool-selection-scorer.js +193 -0
- package/dist/metrics/tool-selection-scorer.js.map +1 -0
- package/dist/metrics/trend-analyzer.d.ts +92 -0
- package/dist/metrics/trend-analyzer.d.ts.map +1 -0
- package/dist/metrics/trend-analyzer.js +293 -0
- package/dist/metrics/trend-analyzer.js.map +1 -0
- package/dist/metrics/turn-cost-attributor.d.ts +41 -0
- package/dist/metrics/turn-cost-attributor.d.ts.map +1 -0
- package/dist/metrics/turn-cost-attributor.js +118 -0
- package/dist/metrics/turn-cost-attributor.js.map +1 -0
- package/dist/metrics/turn-tracker.d.ts +49 -0
- package/dist/metrics/turn-tracker.d.ts.map +1 -0
- package/dist/metrics/turn-tracker.js +192 -0
- package/dist/metrics/turn-tracker.js.map +1 -0
- package/dist/platforms/amazon-q-adapter.d.ts +10 -0
- package/dist/platforms/amazon-q-adapter.d.ts.map +1 -0
- package/dist/platforms/amazon-q-adapter.js +75 -0
- package/dist/platforms/amazon-q-adapter.js.map +1 -0
- package/dist/platforms/claude-code-adapter.d.ts +10 -0
- package/dist/platforms/claude-code-adapter.d.ts.map +1 -0
- package/dist/platforms/claude-code-adapter.js +48 -0
- package/dist/platforms/claude-code-adapter.js.map +1 -0
- package/dist/platforms/continue-adapter.d.ts +10 -0
- package/dist/platforms/continue-adapter.d.ts.map +1 -0
- package/dist/platforms/continue-adapter.js +73 -0
- package/dist/platforms/continue-adapter.js.map +1 -0
- package/dist/platforms/copilot-adapter.d.ts +37 -0
- package/dist/platforms/copilot-adapter.d.ts.map +1 -0
- package/dist/platforms/copilot-adapter.js +66 -0
- package/dist/platforms/copilot-adapter.js.map +1 -0
- package/dist/platforms/cursor-adapter.d.ts +10 -0
- package/dist/platforms/cursor-adapter.d.ts.map +1 -0
- package/dist/platforms/cursor-adapter.js +60 -0
- package/dist/platforms/cursor-adapter.js.map +1 -0
- package/dist/platforms/generic-mcp-adapter.d.ts +113 -0
- package/dist/platforms/generic-mcp-adapter.d.ts.map +1 -0
- package/dist/platforms/generic-mcp-adapter.js +139 -0
- package/dist/platforms/generic-mcp-adapter.js.map +1 -0
- package/dist/platforms/index.d.ts +15 -0
- package/dist/platforms/index.d.ts.map +1 -0
- package/dist/platforms/index.js +12 -0
- package/dist/platforms/index.js.map +1 -0
- package/dist/platforms/platform-registry.d.ts +11 -0
- package/dist/platforms/platform-registry.d.ts.map +1 -0
- package/dist/platforms/platform-registry.js +54 -0
- package/dist/platforms/platform-registry.js.map +1 -0
- package/dist/platforms/types.d.ts +36 -0
- package/dist/platforms/types.d.ts.map +1 -0
- package/dist/platforms/types.js +2 -0
- package/dist/platforms/types.js.map +1 -0
- package/dist/platforms/windsurf-adapter.d.ts +10 -0
- package/dist/platforms/windsurf-adapter.d.ts.map +1 -0
- package/dist/platforms/windsurf-adapter.js +63 -0
- package/dist/platforms/windsurf-adapter.js.map +1 -0
- package/dist/platforms/zed-adapter.d.ts +10 -0
- package/dist/platforms/zed-adapter.d.ts.map +1 -0
- package/dist/platforms/zed-adapter.js +72 -0
- package/dist/platforms/zed-adapter.js.map +1 -0
- package/dist/proxy/index.d.ts +7 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +5 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/otlp-receiver.d.ts +28 -0
- package/dist/proxy/otlp-receiver.d.ts.map +1 -0
- package/dist/proxy/otlp-receiver.js +319 -0
- package/dist/proxy/otlp-receiver.js.map +1 -0
- package/dist/proxy/proxy-manager.d.ts +47 -0
- package/dist/proxy/proxy-manager.d.ts.map +1 -0
- package/dist/proxy/proxy-manager.js +338 -0
- package/dist/proxy/proxy-manager.js.map +1 -0
- package/dist/proxy/types.d.ts +72 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/types.js +33 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/proxy/upstream-http.d.ts +26 -0
- package/dist/proxy/upstream-http.d.ts.map +1 -0
- package/dist/proxy/upstream-http.js +209 -0
- package/dist/proxy/upstream-http.js.map +1 -0
- package/dist/proxy/upstream-stdio.d.ts +25 -0
- package/dist/proxy/upstream-stdio.d.ts.map +1 -0
- package/dist/proxy/upstream-stdio.js +256 -0
- package/dist/proxy/upstream-stdio.js.map +1 -0
- package/dist/security/audit-trail.d.ts +74 -0
- package/dist/security/audit-trail.d.ts.map +1 -0
- package/dist/security/audit-trail.js +338 -0
- package/dist/security/audit-trail.js.map +1 -0
- package/dist/security/index.d.ts +5 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +4 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/ssrf.d.ts +2 -0
- package/dist/security/ssrf.d.ts.map +1 -0
- package/dist/security/ssrf.js +126 -0
- package/dist/security/ssrf.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +117 -0
- package/dist/server.js.map +1 -0
- package/dist/shared/__test-utils__/log-output.d.ts +49 -0
- package/dist/shared/__test-utils__/log-output.d.ts.map +1 -0
- package/dist/shared/__test-utils__/log-output.js +38 -0
- package/dist/shared/__test-utils__/log-output.js.map +1 -0
- package/dist/shared/config.d.ts +56 -0
- package/dist/shared/config.d.ts.map +1 -0
- package/dist/shared/config.js +290 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/errors.d.ts +139 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +406 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/events/factory.d.ts +143 -0
- package/dist/shared/events/factory.d.ts.map +1 -0
- package/dist/shared/events/factory.js +351 -0
- package/dist/shared/events/factory.js.map +1 -0
- package/dist/shared/events/index.d.ts +6 -0
- package/dist/shared/events/index.d.ts.map +1 -0
- package/dist/shared/events/index.js +3 -0
- package/dist/shared/events/index.js.map +1 -0
- package/dist/shared/events/serialize.d.ts +87 -0
- package/dist/shared/events/serialize.d.ts.map +1 -0
- package/dist/shared/events/serialize.js +510 -0
- package/dist/shared/events/serialize.js.map +1 -0
- package/dist/shared/events/types.d.ts +139 -0
- package/dist/shared/events/types.d.ts.map +1 -0
- package/dist/shared/events/types.js +2 -0
- package/dist/shared/events/types.js.map +1 -0
- package/dist/shared/harvest/event-buffer.d.ts +59 -0
- package/dist/shared/harvest/event-buffer.d.ts.map +1 -0
- package/dist/shared/harvest/event-buffer.js +100 -0
- package/dist/shared/harvest/event-buffer.js.map +1 -0
- package/dist/shared/harvest/harvest-scheduler.d.ts +200 -0
- package/dist/shared/harvest/harvest-scheduler.d.ts.map +1 -0
- package/dist/shared/harvest/harvest-scheduler.js +647 -0
- package/dist/shared/harvest/harvest-scheduler.js.map +1 -0
- package/dist/shared/harvest/index.d.ts +7 -0
- package/dist/shared/harvest/index.d.ts.map +1 -0
- package/dist/shared/harvest/index.js +4 -0
- package/dist/shared/harvest/index.js.map +1 -0
- package/dist/shared/harvest/metric-aggregator.d.ts +115 -0
- package/dist/shared/harvest/metric-aggregator.d.ts.map +1 -0
- package/dist/shared/harvest/metric-aggregator.js +247 -0
- package/dist/shared/harvest/metric-aggregator.js.map +1 -0
- package/dist/shared/index.d.ts +22 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +13 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/logger.d.ts +57 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +166 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/pricing-data.d.ts +4 -0
- package/dist/shared/pricing-data.d.ts.map +1 -0
- package/dist/shared/pricing-data.js +473 -0
- package/dist/shared/pricing-data.js.map +1 -0
- package/dist/shared/pricing.d.ts +148 -0
- package/dist/shared/pricing.d.ts.map +1 -0
- package/dist/shared/pricing.js +528 -0
- package/dist/shared/pricing.js.map +1 -0
- package/dist/shared/redact.d.ts +33 -0
- package/dist/shared/redact.d.ts.map +1 -0
- package/dist/shared/redact.js +110 -0
- package/dist/shared/redact.js.map +1 -0
- package/dist/shared/timing.d.ts +96 -0
- package/dist/shared/timing.d.ts.map +1 -0
- package/dist/shared/timing.js +173 -0
- package/dist/shared/timing.js.map +1 -0
- package/dist/shared/tokens.d.ts +145 -0
- package/dist/shared/tokens.d.ts.map +1 -0
- package/dist/shared/tokens.js +492 -0
- package/dist/shared/tokens.js.map +1 -0
- package/dist/shared/transport/events-api.d.ts +14 -0
- package/dist/shared/transport/events-api.d.ts.map +1 -0
- package/dist/shared/transport/events-api.js +29 -0
- package/dist/shared/transport/events-api.js.map +1 -0
- package/dist/shared/transport/http-client.d.ts +49 -0
- package/dist/shared/transport/http-client.d.ts.map +1 -0
- package/dist/shared/transport/http-client.js +381 -0
- package/dist/shared/transport/http-client.js.map +1 -0
- package/dist/shared/transport/index.d.ts +10 -0
- package/dist/shared/transport/index.d.ts.map +1 -0
- package/dist/shared/transport/index.js +6 -0
- package/dist/shared/transport/index.js.map +1 -0
- package/dist/shared/transport/logs-api.d.ts +29 -0
- package/dist/shared/transport/logs-api.d.ts.map +1 -0
- package/dist/shared/transport/logs-api.js +40 -0
- package/dist/shared/transport/logs-api.js.map +1 -0
- package/dist/shared/transport/metric-api.d.ts +9 -0
- package/dist/shared/transport/metric-api.d.ts.map +1 -0
- package/dist/shared/transport/metric-api.js +39 -0
- package/dist/shared/transport/metric-api.js.map +1 -0
- package/dist/shared/transport/otlp-event-bridge.d.ts +22 -0
- package/dist/shared/transport/otlp-event-bridge.d.ts.map +1 -0
- package/dist/shared/transport/otlp-event-bridge.js +50 -0
- package/dist/shared/transport/otlp-event-bridge.js.map +1 -0
- package/dist/shared/transport/otlp-shared.d.ts +14 -0
- package/dist/shared/transport/otlp-shared.d.ts.map +1 -0
- package/dist/shared/transport/otlp-shared.js +49 -0
- package/dist/shared/transport/otlp-shared.js.map +1 -0
- package/dist/shared/transport/otlp-transport.d.ts +58 -0
- package/dist/shared/transport/otlp-transport.d.ts.map +1 -0
- package/dist/shared/transport/otlp-transport.js +236 -0
- package/dist/shared/transport/otlp-transport.js.map +1 -0
- package/dist/shared/transport/types.d.ts +129 -0
- package/dist/shared/transport/types.d.ts.map +1 -0
- package/dist/shared/transport/types.js +2 -0
- package/dist/shared/transport/types.js.map +1 -0
- package/dist/shared/version.d.ts +2 -0
- package/dist/shared/version.d.ts.map +1 -0
- package/dist/shared/version.js +2 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local-store.d.ts +153 -0
- package/dist/storage/local-store.d.ts.map +1 -0
- package/dist/storage/local-store.js +719 -0
- package/dist/storage/local-store.js.map +1 -0
- package/dist/storage/retention.d.ts +2 -0
- package/dist/storage/retention.d.ts.map +1 -0
- package/dist/storage/retention.js +53 -0
- package/dist/storage/retention.js.map +1 -0
- package/dist/storage/session-store.d.ts +97 -0
- package/dist/storage/session-store.d.ts.map +1 -0
- package/dist/storage/session-store.js +391 -0
- package/dist/storage/session-store.js.map +1 -0
- package/dist/storage/types.d.ts +64 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/storage/weekly-summary.d.ts +61 -0
- package/dist/storage/weekly-summary.d.ts.map +1 -0
- package/dist/storage/weekly-summary.js +243 -0
- package/dist/storage/weekly-summary.js.map +1 -0
- package/dist/tools/analytics-tools.d.ts +101 -0
- package/dist/tools/analytics-tools.d.ts.map +1 -0
- package/dist/tools/analytics-tools.js +71 -0
- package/dist/tools/analytics-tools.js.map +1 -0
- package/dist/tools/cost-tools.d.ts +121 -0
- package/dist/tools/cost-tools.d.ts.map +1 -0
- package/dist/tools/cost-tools.js +174 -0
- package/dist/tools/cost-tools.js.map +1 -0
- package/dist/tools/cross-session-tools.d.ts +376 -0
- package/dist/tools/cross-session-tools.d.ts.map +1 -0
- package/dist/tools/cross-session-tools.js +820 -0
- package/dist/tools/cross-session-tools.js.map +1 -0
- package/dist/tools/extended-analytics-tools.d.ts +164 -0
- package/dist/tools/extended-analytics-tools.d.ts.map +1 -0
- package/dist/tools/extended-analytics-tools.js +121 -0
- package/dist/tools/extended-analytics-tools.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/session-stats.d.ts +162 -0
- package/dist/tools/session-stats.d.ts.map +1 -0
- package/dist/tools/session-stats.js +1054 -0
- package/dist/tools/session-stats.js.map +1 -0
- package/dist/tools/workflow-tools.d.ts +126 -0
- package/dist/tools/workflow-tools.d.ts.map +1 -0
- package/dist/tools/workflow-tools.js +274 -0
- package/dist/tools/workflow-tools.js.map +1 -0
- package/dist/tracing/mcp-tracer.d.ts +4 -0
- package/dist/tracing/mcp-tracer.d.ts.map +1 -0
- package/dist/tracing/mcp-tracer.js +14 -0
- package/dist/tracing/mcp-tracer.js.map +1 -0
- package/dist/tracing/session-span.d.ts +14 -0
- package/dist/tracing/session-span.d.ts.map +1 -0
- package/dist/tracing/session-span.js +53 -0
- package/dist/tracing/session-span.js.map +1 -0
- package/dist/tracing/task-span-tracker.d.ts +11 -0
- package/dist/tracing/task-span-tracker.d.ts.map +1 -0
- package/dist/tracing/task-span-tracker.js +59 -0
- package/dist/tracing/task-span-tracker.js.map +1 -0
- package/dist/tracing/tool-call-span.d.ts +4 -0
- package/dist/tracing/tool-call-span.d.ts.map +1 -0
- package/dist/tracing/tool-call-span.js +60 -0
- package/dist/tracing/tool-call-span.js.map +1 -0
- package/dist/transport/index.d.ts +3 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +2 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/log-ingest.d.ts +42 -0
- package/dist/transport/log-ingest.d.ts.map +1 -0
- package/dist/transport/log-ingest.js +151 -0
- package/dist/transport/log-ingest.js.map +1 -0
- package/dist/transport/nr-ingest.d.ts +171 -0
- package/dist/transport/nr-ingest.d.ts.map +1 -0
- package/dist/transport/nr-ingest.js +659 -0
- package/dist/transport/nr-ingest.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/web/assets/index-BrL281N-.css +2 -0
- package/dist/web/assets/index-CcaYZzXm.js +42 -0
- package/dist/web/favicon.svg +15 -0
- package/dist/web/index.html +15 -0
- package/examples/local-alert-rules.json +106 -0
- package/package.json +125 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { TokenUsage } from './tokens.js';
|
|
2
|
+
export interface ModelPricing {
|
|
3
|
+
readonly inputPerMTok: number;
|
|
4
|
+
readonly outputPerMTok: number;
|
|
5
|
+
readonly thinkingPerMTok?: number;
|
|
6
|
+
readonly cacheReadPerMTok?: number;
|
|
7
|
+
readonly cacheCreationPerMTok?: number;
|
|
8
|
+
readonly contextWindow: number;
|
|
9
|
+
/** Input-token count above which tier rates apply. */
|
|
10
|
+
readonly tierThreshold?: number;
|
|
11
|
+
readonly tierInputPerMTok?: number;
|
|
12
|
+
readonly tierOutputPerMTok?: number;
|
|
13
|
+
readonly tierThinkingPerMTok?: number;
|
|
14
|
+
/**
|
|
15
|
+
* How tier rates are applied once `inputTokens > tierThreshold`. Defaults to
|
|
16
|
+
* `'flat'` (current behavior, matches Gemini 1.5/2.5 Pro semantics).
|
|
17
|
+
*
|
|
18
|
+
* - `'flat'`: the **entire request** (input, output, thinking) is billed at
|
|
19
|
+
* the tier rates. This is what every provider we currently price uses.
|
|
20
|
+
* - `'marginal'`: only the **input tokens above the threshold** are billed
|
|
21
|
+
* at `tierInputPerMTok`; tokens up to the threshold use `inputPerMTok`.
|
|
22
|
+
* Output and thinking always use their base rates in this mode — the
|
|
23
|
+
* `tierOutputPerMTok` / `tierThinkingPerMTok` fields are ignored. This
|
|
24
|
+
* models providers that charge a higher rate purely for excess context.
|
|
25
|
+
*
|
|
26
|
+
* No currently wrapped provider needs `'marginal'`; the mode exists for
|
|
27
|
+
* forward compatibility.
|
|
28
|
+
*/
|
|
29
|
+
readonly tierMode?: 'flat' | 'marginal';
|
|
30
|
+
}
|
|
31
|
+
export interface CostBreakdown {
|
|
32
|
+
inputUsd: number;
|
|
33
|
+
outputUsd: number;
|
|
34
|
+
thinkingUsd: number;
|
|
35
|
+
cacheReadUsd: number;
|
|
36
|
+
cacheCreationUsd: number;
|
|
37
|
+
totalUsd: number;
|
|
38
|
+
savingsFromCacheUsd: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Load a custom pricing override file from disk.
|
|
42
|
+
*
|
|
43
|
+
* **This function is synchronous (`readFileSync` / `statSync`) and is intended
|
|
44
|
+
* for startup-time use only**. Calling it from a hot path
|
|
45
|
+
* — for example, a SIGHUP-triggered reload while the process is also serving
|
|
46
|
+
* inference traffic — will block the event loop for the duration of the read
|
|
47
|
+
* and parse. If you need reload-on-signal, schedule it from a worker thread
|
|
48
|
+
* or wrap it in `setImmediate` so the surrounding tick can complete first.
|
|
49
|
+
*
|
|
50
|
+
* Files larger than {@link MAX_PRICING_FILE_BYTES} are rejected without being
|
|
51
|
+
* read — a pricing table is a small dictionary, and a
|
|
52
|
+
* mis-pointed multi-GB file would OOM `JSON.parse`.
|
|
53
|
+
*
|
|
54
|
+
* Returns the parsed override map on success, or `null` when the file is
|
|
55
|
+
* missing, the wrong shape, too large, or contains no valid entries (§PR7).
|
|
56
|
+
*
|
|
57
|
+
* **Note:** `null` is returned for both "file not found" and "all entries
|
|
58
|
+
* invalid" — callers cannot distinguish the two cases from the return value
|
|
59
|
+
* alone. Both emit a `logger.warn` with a distinguishing message so the
|
|
60
|
+
* difference is visible in stderr. A future improvement would be to return a
|
|
61
|
+
* discriminated union `{ status: 'ok' | 'not-found' | 'all-invalid', ... }`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadCustomPricing(filePath: string): Record<string, ModelPricing> | null;
|
|
64
|
+
/**
|
|
65
|
+
* Encapsulated pricing table for a single agent / tenant.
|
|
66
|
+
*
|
|
67
|
+
* Each `PricingTable` instance owns its own merged table and resolution state.
|
|
68
|
+
* Construct one per agent when a single Node process needs to serve multiple
|
|
69
|
+
* tenants with different custom pricing overrides; otherwise the module-level
|
|
70
|
+
* functions (`resolveModelPricing`, `calculateCost`, `initPricing`) operate on
|
|
71
|
+
* a process-wide default singleton and are sufficient for the common case.
|
|
72
|
+
*
|
|
73
|
+
* Multi-tenant example:
|
|
74
|
+
* ```ts
|
|
75
|
+
* const tenantA = new PricingTable('/etc/agent-a/pricing.json');
|
|
76
|
+
* const tenantB = new PricingTable('/etc/agent-b/pricing.json');
|
|
77
|
+
* tenantA.resolve('claude-opus-4-7'); // sees agent-a overrides
|
|
78
|
+
* tenantB.resolve('claude-opus-4-7'); // sees agent-b overrides
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare class PricingTable {
|
|
82
|
+
private table;
|
|
83
|
+
constructor(customFilePath?: string | null);
|
|
84
|
+
/**
|
|
85
|
+
* Reset the table to built-in defaults, optionally re-overlaying a custom
|
|
86
|
+
* pricing file. Equivalent to `new PricingTable(customFilePath)` but reuses
|
|
87
|
+
* the existing instance.
|
|
88
|
+
*/
|
|
89
|
+
reset(customFilePath?: string | null): void;
|
|
90
|
+
/**
|
|
91
|
+
* Resolve a model name to its pricing entry. See module-level
|
|
92
|
+
* `resolveModelPricing` for the resolution algorithm.
|
|
93
|
+
*/
|
|
94
|
+
resolve(modelName: string): ModelPricing | null;
|
|
95
|
+
/**
|
|
96
|
+
* Calculate a cost breakdown for the given model and token usage. See
|
|
97
|
+
* module-level `calculateCost` for tiered-pricing semantics.
|
|
98
|
+
*/
|
|
99
|
+
calculateCost(model: string, usage: TokenUsage): CostBreakdown;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* (Re-)initialize the **default singleton** pricing table. Call with a custom
|
|
103
|
+
* file path to overlay user-provided prices on top of the built-in table.
|
|
104
|
+
* Call with `null`/`undefined` to reset to the built-in defaults.
|
|
105
|
+
*
|
|
106
|
+
* **Synchronous I/O — startup-only.** Reads the custom file with `readFileSync`
|
|
107
|
+
*; calling this on a hot path (e.g. a SIGHUP-triggered
|
|
108
|
+
* reload while the process is serving inference traffic) blocks the event
|
|
109
|
+
* loop. Schedule reloads from a worker thread or wrap in `setImmediate`.
|
|
110
|
+
*
|
|
111
|
+
* **Note:** This mutates a process-wide singleton. If your process serves
|
|
112
|
+
* multiple agents with distinct pricing files, instantiate a `PricingTable`
|
|
113
|
+
* per agent instead — calling `initPricing` again will overwrite the
|
|
114
|
+
* previously-loaded prices and the first agent will silently see the second's.
|
|
115
|
+
*/
|
|
116
|
+
export declare function initPricing(customFilePath?: string | null): void;
|
|
117
|
+
/**
|
|
118
|
+
* Resolve a model name against the default singleton pricing table.
|
|
119
|
+
*
|
|
120
|
+
* 1. Exact match (e.g. `claude-sonnet-4-20250514`)
|
|
121
|
+
* 2. Family-name alias (e.g. `claude-opus-4` → `claude-opus-4-7`) — see
|
|
122
|
+
* MODEL_ALIASES in pricing-data.ts. Aliases are the *primary* mechanism
|
|
123
|
+
* for routing family names to current-generation pricing.
|
|
124
|
+
* 3. Forward prefix — table key starts with modelName
|
|
125
|
+
* (e.g. an unaliased `gemini-2.5-flash` would match itself; only used for
|
|
126
|
+
* coverage of new keys not yet in the alias map)
|
|
127
|
+
* 4. Reverse prefix — modelName starts with table key's base (date stripped)
|
|
128
|
+
* (e.g. `claude-opus-4-99` matches base `claude-opus-4` from a dated key)
|
|
129
|
+
* 5. Return `null` and log a warning if nothing matches.
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveModelPricing(modelName: string): ModelPricing | null;
|
|
132
|
+
/**
|
|
133
|
+
* Calculate a cost breakdown for the given model and token usage against the
|
|
134
|
+
* default singleton pricing table.
|
|
135
|
+
*
|
|
136
|
+
* If the model is unknown, returns an all-zero breakdown and logs a warning.
|
|
137
|
+
*
|
|
138
|
+
* Tiered pricing semantics (when `inputTokens > tierThreshold`):
|
|
139
|
+
*
|
|
140
|
+
* - `tierMode: 'flat'` (default) — the entire request (input, output, thinking)
|
|
141
|
+
* is billed at the configured tier rates. Matches Gemini 1.5/2.5 Pro.
|
|
142
|
+
* - `tierMode: 'marginal'` — only the input tokens above the threshold are
|
|
143
|
+
* billed at `tierInputPerMTok`; tokens up to the threshold use `inputPerMTok`.
|
|
144
|
+
* Output and thinking always use their base rates (the `tierOutput*` /
|
|
145
|
+
* `tierThinking*` fields are ignored in this mode).
|
|
146
|
+
*/
|
|
147
|
+
export declare function calculateCost(model: string, usage: TokenUsage): CostBreakdown;
|
|
148
|
+
//# sourceMappingURL=pricing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/shared/pricing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAU9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,sDAAsD;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AA+LD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CA4DvF;AAqCD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAA+B;gBAEhC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI;IAgB1C;;;;OAIG;IACH,KAAK,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAa3C;;;OAGG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IA+E/C;;;OAGG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,aAAa;CAO/D;AAQD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAE1E;AAuFD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,aAAa,CAE7E"}
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve, extname } from 'node:path';
|
|
3
|
+
import { DEFAULT_PRICING_TABLE, MODEL_ALIASES } from './pricing-data.js';
|
|
4
|
+
import { createLogger } from './logger.js';
|
|
5
|
+
const logger = createLogger('pricing');
|
|
6
|
+
const ZERO_COST = Object.freeze({
|
|
7
|
+
inputUsd: 0,
|
|
8
|
+
outputUsd: 0,
|
|
9
|
+
thinkingUsd: 0,
|
|
10
|
+
cacheReadUsd: 0,
|
|
11
|
+
cacheCreationUsd: 0,
|
|
12
|
+
totalUsd: 0,
|
|
13
|
+
savingsFromCacheUsd: 0,
|
|
14
|
+
});
|
|
15
|
+
// Strip a trailing dated suffix (-YYYYMMDD) to get the model family base name.
|
|
16
|
+
const DATED_SUFFIX_RE = /-\d{8}$/;
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Custom pricing file
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Sanity ceiling for per-MTok rates. The most expensive frontier model rates
|
|
21
|
+
// at the time of writing are well under $100/MTok; a value above $10,000/MTok
|
|
22
|
+
// almost certainly reflects a typo (e.g. forgetting that the unit is per
|
|
23
|
+
// million tokens, or pricing in cents instead of dollars). Reject these
|
|
24
|
+
// outright — silently accepting them produces wildly inflated cost telemetry.
|
|
25
|
+
const MAX_REASONABLE_RATE_PER_MTOK = 10_000;
|
|
26
|
+
// Maximum allowed size of a custom pricing JSON file.
|
|
27
|
+
// A pricing table is a small dictionary; even with hundreds of model entries
|
|
28
|
+
// the file is well under 100 KB. The 1 MB cap is a defensive ceiling that
|
|
29
|
+
// prevents `readFileSync` from happily slurping a multi-gigabyte file the
|
|
30
|
+
// caller mis-pointed us at, which would then OOM `JSON.parse`.
|
|
31
|
+
const MAX_PRICING_FILE_BYTES = 1_000_000;
|
|
32
|
+
function isFiniteNonNegative(v) {
|
|
33
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0;
|
|
34
|
+
}
|
|
35
|
+
function isWithinRateCeiling(v) {
|
|
36
|
+
return v <= MAX_REASONABLE_RATE_PER_MTOK;
|
|
37
|
+
}
|
|
38
|
+
function validatePricingEntry(model, entry) {
|
|
39
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
40
|
+
logger.warn('Custom pricing entry is not an object — skipped', { model });
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const e = entry;
|
|
44
|
+
if (!isFiniteNonNegative(e.inputPerMTok)) {
|
|
45
|
+
logger.warn('Custom pricing entry has invalid inputPerMTok — skipped', {
|
|
46
|
+
model,
|
|
47
|
+
value: e.inputPerMTok,
|
|
48
|
+
});
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (!isWithinRateCeiling(e.inputPerMTok)) {
|
|
52
|
+
logger.warn('Custom pricing entry has implausibly large inputPerMTok — skipped', {
|
|
53
|
+
model,
|
|
54
|
+
value: e.inputPerMTok,
|
|
55
|
+
ceiling: MAX_REASONABLE_RATE_PER_MTOK,
|
|
56
|
+
});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (!isFiniteNonNegative(e.outputPerMTok)) {
|
|
60
|
+
logger.warn('Custom pricing entry has invalid outputPerMTok — skipped', {
|
|
61
|
+
model,
|
|
62
|
+
value: e.outputPerMTok,
|
|
63
|
+
});
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!isWithinRateCeiling(e.outputPerMTok)) {
|
|
67
|
+
logger.warn('Custom pricing entry has implausibly large outputPerMTok — skipped', {
|
|
68
|
+
model,
|
|
69
|
+
value: e.outputPerMTok,
|
|
70
|
+
ceiling: MAX_REASONABLE_RATE_PER_MTOK,
|
|
71
|
+
});
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (typeof e.contextWindow !== 'number' ||
|
|
75
|
+
!Number.isFinite(e.contextWindow) ||
|
|
76
|
+
e.contextWindow <= 0 ||
|
|
77
|
+
!Number.isInteger(e.contextWindow)) {
|
|
78
|
+
logger.warn('Custom pricing entry has invalid contextWindow (must be a positive integer) — skipped', { model, value: e.contextWindow });
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const optionalRateFields = [
|
|
82
|
+
'thinkingPerMTok',
|
|
83
|
+
'cacheReadPerMTok',
|
|
84
|
+
'cacheCreationPerMTok',
|
|
85
|
+
'tierInputPerMTok',
|
|
86
|
+
'tierOutputPerMTok',
|
|
87
|
+
'tierThinkingPerMTok',
|
|
88
|
+
];
|
|
89
|
+
for (const field of optionalRateFields) {
|
|
90
|
+
const value = e[field];
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
if (!isFiniteNonNegative(value)) {
|
|
94
|
+
logger.warn(`Custom pricing entry has invalid ${field} — skipped`, { model, value });
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (!isWithinRateCeiling(value)) {
|
|
98
|
+
logger.warn(`Custom pricing entry has implausibly large ${field} — skipped`, {
|
|
99
|
+
model,
|
|
100
|
+
value,
|
|
101
|
+
ceiling: MAX_REASONABLE_RATE_PER_MTOK,
|
|
102
|
+
});
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (e.tierThreshold !== undefined &&
|
|
107
|
+
(typeof e.tierThreshold !== 'number' ||
|
|
108
|
+
!Number.isFinite(e.tierThreshold) ||
|
|
109
|
+
e.tierThreshold <= 0 ||
|
|
110
|
+
!Number.isInteger(e.tierThreshold))) {
|
|
111
|
+
logger.warn('Custom pricing entry has invalid tierThreshold (must be a positive integer) — skipped', { model, value: e.tierThreshold });
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (e.tierMode !== undefined && e.tierMode !== 'flat' && e.tierMode !== 'marginal') {
|
|
115
|
+
logger.warn('Custom pricing entry has invalid tierMode (must be "flat" or "marginal") — skipped', {
|
|
116
|
+
model,
|
|
117
|
+
value: e.tierMode,
|
|
118
|
+
});
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
// Relational sanity checks — accept the entry but warn on configurations
|
|
122
|
+
// that almost always indicate a misconfiguration. These are warnings, not
|
|
123
|
+
// rejections: unusual but legitimate price structures (e.g. an experimental
|
|
124
|
+
// discount where cache reads cost more than fresh input) should still be
|
|
125
|
+
// representable without forcing the user to fork the validator.
|
|
126
|
+
const inputRate = e.inputPerMTok;
|
|
127
|
+
if (e.cacheReadPerMTok !== undefined && e.cacheReadPerMTok > inputRate) {
|
|
128
|
+
logger.warn('Custom pricing entry has cacheReadPerMTok above inputPerMTok — accepted but unusual', {
|
|
129
|
+
model,
|
|
130
|
+
cacheReadPerMTok: e.cacheReadPerMTok,
|
|
131
|
+
inputPerMTok: inputRate,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (e.tierInputPerMTok !== undefined && e.tierInputPerMTok < inputRate) {
|
|
135
|
+
logger.warn('Custom pricing entry has tierInputPerMTok below inputPerMTok — accepted but unusual (tier rates are typically higher)', {
|
|
136
|
+
model,
|
|
137
|
+
tierInputPerMTok: e.tierInputPerMTok,
|
|
138
|
+
inputPerMTok: inputRate,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Construct a fresh `ModelPricing` object that contains ONLY recognized
|
|
142
|
+
// fields. Returning `e` directly leaks any typo'd or
|
|
143
|
+
// future / unknown keys from the user's JSON into the merged table, where
|
|
144
|
+
// they would surface in serialization (`Object.entries(...)`) and risk
|
|
145
|
+
// future code mistaking them for valid pricing fields. Built as a single
|
|
146
|
+
// literal to satisfy the readonly interface (§PR5).
|
|
147
|
+
return {
|
|
148
|
+
inputPerMTok: e.inputPerMTok,
|
|
149
|
+
outputPerMTok: e.outputPerMTok,
|
|
150
|
+
contextWindow: e.contextWindow,
|
|
151
|
+
...(typeof e.thinkingPerMTok === 'number' && { thinkingPerMTok: e.thinkingPerMTok }),
|
|
152
|
+
...(typeof e.cacheReadPerMTok === 'number' && { cacheReadPerMTok: e.cacheReadPerMTok }),
|
|
153
|
+
...(typeof e.cacheCreationPerMTok === 'number' && {
|
|
154
|
+
cacheCreationPerMTok: e.cacheCreationPerMTok,
|
|
155
|
+
}),
|
|
156
|
+
...(typeof e.tierThreshold === 'number' && { tierThreshold: e.tierThreshold }),
|
|
157
|
+
...(typeof e.tierInputPerMTok === 'number' && { tierInputPerMTok: e.tierInputPerMTok }),
|
|
158
|
+
...(typeof e.tierOutputPerMTok === 'number' && { tierOutputPerMTok: e.tierOutputPerMTok }),
|
|
159
|
+
...(typeof e.tierThinkingPerMTok === 'number' && {
|
|
160
|
+
tierThinkingPerMTok: e.tierThinkingPerMTok,
|
|
161
|
+
}),
|
|
162
|
+
...((e.tierMode === 'flat' || e.tierMode === 'marginal') && { tierMode: e.tierMode }),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Load a custom pricing override file from disk.
|
|
167
|
+
*
|
|
168
|
+
* **This function is synchronous (`readFileSync` / `statSync`) and is intended
|
|
169
|
+
* for startup-time use only**. Calling it from a hot path
|
|
170
|
+
* — for example, a SIGHUP-triggered reload while the process is also serving
|
|
171
|
+
* inference traffic — will block the event loop for the duration of the read
|
|
172
|
+
* and parse. If you need reload-on-signal, schedule it from a worker thread
|
|
173
|
+
* or wrap it in `setImmediate` so the surrounding tick can complete first.
|
|
174
|
+
*
|
|
175
|
+
* Files larger than {@link MAX_PRICING_FILE_BYTES} are rejected without being
|
|
176
|
+
* read — a pricing table is a small dictionary, and a
|
|
177
|
+
* mis-pointed multi-GB file would OOM `JSON.parse`.
|
|
178
|
+
*
|
|
179
|
+
* Returns the parsed override map on success, or `null` when the file is
|
|
180
|
+
* missing, the wrong shape, too large, or contains no valid entries (§PR7).
|
|
181
|
+
*
|
|
182
|
+
* **Note:** `null` is returned for both "file not found" and "all entries
|
|
183
|
+
* invalid" — callers cannot distinguish the two cases from the return value
|
|
184
|
+
* alone. Both emit a `logger.warn` with a distinguishing message so the
|
|
185
|
+
* difference is visible in stderr. A future improvement would be to return a
|
|
186
|
+
* discriminated union `{ status: 'ok' | 'not-found' | 'all-invalid', ... }`.
|
|
187
|
+
*/
|
|
188
|
+
export function loadCustomPricing(filePath) {
|
|
189
|
+
const resolvedPath = resolve(filePath);
|
|
190
|
+
if (extname(resolvedPath).toLowerCase() !== '.json') {
|
|
191
|
+
logger.warn('Custom pricing file must have a .json extension', { filePath: resolvedPath });
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
// Reject oversized files before reading — `readFileSync` will happily
|
|
196
|
+
// slurp gigabytes into memory and `JSON.parse` will then OOM.
|
|
197
|
+
const stat = statSync(resolvedPath);
|
|
198
|
+
if (stat.size > MAX_PRICING_FILE_BYTES) {
|
|
199
|
+
logger.warn('Custom pricing file exceeds size limit — skipped', {
|
|
200
|
+
filePath: resolvedPath,
|
|
201
|
+
sizeBytes: stat.size,
|
|
202
|
+
maxBytes: MAX_PRICING_FILE_BYTES,
|
|
203
|
+
});
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const raw = readFileSync(resolvedPath, 'utf-8');
|
|
207
|
+
const parsed = JSON.parse(raw);
|
|
208
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
209
|
+
logger.warn('Custom pricing file is not a JSON object', { filePath: resolvedPath });
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
// Null-prototype object prevents __proto__ assignment from polluting
|
|
213
|
+
// Object.prototype even if a reserved key slips through (§P1).
|
|
214
|
+
const result = Object.create(null);
|
|
215
|
+
const RESERVED = new Set(['__proto__', 'constructor', 'prototype']);
|
|
216
|
+
for (const [model, entry] of Object.entries(parsed)) {
|
|
217
|
+
if (RESERVED.has(model)) {
|
|
218
|
+
logger.warn('Custom pricing entry uses a reserved key — skipped', { model });
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const validated = validatePricingEntry(model, entry);
|
|
222
|
+
if (validated !== null) {
|
|
223
|
+
result[model] = validated;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Return null when no valid entries were found so callers can distinguish
|
|
227
|
+
// "applied custom pricing" (truthy) from "file present but all invalid" (§PR1).
|
|
228
|
+
if (Object.keys(result).length === 0) {
|
|
229
|
+
logger.warn('Custom pricing file contained no valid entries', { filePath: resolvedPath });
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
logger.warn('Failed to load custom pricing file', {
|
|
236
|
+
filePath: resolvedPath,
|
|
237
|
+
error: err instanceof Error ? err.message : String(err),
|
|
238
|
+
});
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Deep-clone a pricing table.
|
|
244
|
+
*
|
|
245
|
+
* `{ ...DEFAULT_PRICING_TABLE }` is a shallow copy — the inner `ModelPricing`
|
|
246
|
+
* objects share references. With the default-singleton pattern, that means a
|
|
247
|
+
* caller doing
|
|
248
|
+
*
|
|
249
|
+
* ```ts
|
|
250
|
+
* import { DEFAULT_PRICING_TABLE } from '@newrelic/ai-telemetry';
|
|
251
|
+
* DEFAULT_PRICING_TABLE['claude-opus-4-7'].outputPerMTok = 99999;
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* would mutate the live pricing data backing every active `PricingTable`
|
|
255
|
+
* instance — including the process-wide default — until the next `reset()`.
|
|
256
|
+
* Deep-cloning at construction breaks the shared-reference path so consumer
|
|
257
|
+
* code can't accidentally rewrite the canonical rates.
|
|
258
|
+
*
|
|
259
|
+
* `structuredClone` (Node 17+) is preferred over `JSON.parse(JSON.stringify())`
|
|
260
|
+
* because it preserves the value-type fidelity of `ModelPricing` (numeric
|
|
261
|
+
* fields stay numbers; an undefined optional field stays undefined rather
|
|
262
|
+
* than being silently dropped). The pricing table contains only plain
|
|
263
|
+
* objects with primitive fields, so neither path's edge cases (cycles,
|
|
264
|
+
* functions, Date) apply here, but `structuredClone` is also faster on
|
|
265
|
+
* modern V8.
|
|
266
|
+
*/
|
|
267
|
+
function clonePricingTable(source) {
|
|
268
|
+
return structuredClone(source);
|
|
269
|
+
}
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// PricingTable — instance-based pricing
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
/**
|
|
274
|
+
* Encapsulated pricing table for a single agent / tenant.
|
|
275
|
+
*
|
|
276
|
+
* Each `PricingTable` instance owns its own merged table and resolution state.
|
|
277
|
+
* Construct one per agent when a single Node process needs to serve multiple
|
|
278
|
+
* tenants with different custom pricing overrides; otherwise the module-level
|
|
279
|
+
* functions (`resolveModelPricing`, `calculateCost`, `initPricing`) operate on
|
|
280
|
+
* a process-wide default singleton and are sufficient for the common case.
|
|
281
|
+
*
|
|
282
|
+
* Multi-tenant example:
|
|
283
|
+
* ```ts
|
|
284
|
+
* const tenantA = new PricingTable('/etc/agent-a/pricing.json');
|
|
285
|
+
* const tenantB = new PricingTable('/etc/agent-b/pricing.json');
|
|
286
|
+
* tenantA.resolve('claude-opus-4-7'); // sees agent-a overrides
|
|
287
|
+
* tenantB.resolve('claude-opus-4-7'); // sees agent-b overrides
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export class PricingTable {
|
|
291
|
+
table;
|
|
292
|
+
constructor(customFilePath) {
|
|
293
|
+
// Null-prototype base: Object.assign to a null-prototype target does not
|
|
294
|
+
// invoke the __proto__ setter, so prototype pollution is defused even if
|
|
295
|
+
// loadCustomPricing returns a result containing that key (§P1).
|
|
296
|
+
this.table = Object.assign(Object.create(null), clonePricingTable(DEFAULT_PRICING_TABLE));
|
|
297
|
+
if (customFilePath) {
|
|
298
|
+
const custom = loadCustomPricing(customFilePath);
|
|
299
|
+
if (custom) {
|
|
300
|
+
Object.assign(this.table, custom);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Reset the table to built-in defaults, optionally re-overlaying a custom
|
|
306
|
+
* pricing file. Equivalent to `new PricingTable(customFilePath)` but reuses
|
|
307
|
+
* the existing instance.
|
|
308
|
+
*/
|
|
309
|
+
reset(customFilePath) {
|
|
310
|
+
this.table = Object.assign(Object.create(null), clonePricingTable(DEFAULT_PRICING_TABLE));
|
|
311
|
+
if (customFilePath) {
|
|
312
|
+
const custom = loadCustomPricing(customFilePath);
|
|
313
|
+
if (custom) {
|
|
314
|
+
Object.assign(this.table, custom);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Resolve a model name to its pricing entry. See module-level
|
|
320
|
+
* `resolveModelPricing` for the resolution algorithm.
|
|
321
|
+
*/
|
|
322
|
+
resolve(modelName) {
|
|
323
|
+
// Returns shallow copies so caller mutation cannot corrupt the instance
|
|
324
|
+
// table — resolved entries are values, not live references (§PRC1).
|
|
325
|
+
// 1. Exact match — Object.hasOwn guards against inherited prototype values
|
|
326
|
+
// for non-null-prototype tables and makes intent explicit (§P2).
|
|
327
|
+
if (Object.hasOwn(this.table, modelName)) {
|
|
328
|
+
return { ...this.table[modelName] };
|
|
329
|
+
}
|
|
330
|
+
// 2. Family-name alias
|
|
331
|
+
const aliasTarget = MODEL_ALIASES[modelName];
|
|
332
|
+
if (aliasTarget && Object.hasOwn(this.table, aliasTarget)) {
|
|
333
|
+
return { ...this.table[aliasTarget] };
|
|
334
|
+
}
|
|
335
|
+
// Forward prefix: find table keys that start with the given name followed
|
|
336
|
+
// by a digit-led suffix. Longest key wins on tie — but two same-length
|
|
337
|
+
// candidates would be non-deterministic (§PR1). Log a warning in that case
|
|
338
|
+
// so future table additions that create ambiguity are visible.
|
|
339
|
+
let bestKey = null;
|
|
340
|
+
let ambiguous = false;
|
|
341
|
+
for (const key of Object.keys(this.table)) {
|
|
342
|
+
const suffix = key.slice(modelName.length);
|
|
343
|
+
if (key.startsWith(modelName) && /^-\d/.test(suffix)) {
|
|
344
|
+
if (bestKey === null || key.length > bestKey.length) {
|
|
345
|
+
bestKey = key;
|
|
346
|
+
ambiguous = false;
|
|
347
|
+
}
|
|
348
|
+
else if (key.length === bestKey.length) {
|
|
349
|
+
ambiguous = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (ambiguous && bestKey !== null) {
|
|
354
|
+
// §11.5: returning a non-deterministic result is worse than returning null,
|
|
355
|
+
// because the caller cannot distinguish "pricing found" from "pricing guessed".
|
|
356
|
+
// Returning null forces the table maintainer to add an explicit alias entry
|
|
357
|
+
// rather than silently accepting iteration-order-dependent pricing.
|
|
358
|
+
logger.warn('Ambiguous forward-prefix match — two same-length candidates; returning null. ' +
|
|
359
|
+
'Add an explicit MODEL_ALIASES entry to resolve.', {
|
|
360
|
+
model: modelName,
|
|
361
|
+
});
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
if (bestKey && Object.hasOwn(this.table, bestKey)) {
|
|
365
|
+
return { ...this.table[bestKey] };
|
|
366
|
+
}
|
|
367
|
+
// Reverse prefix: strip date suffix from table keys and check if modelName
|
|
368
|
+
// starts with the resulting base. When the matched base has an alias,
|
|
369
|
+
// route through the alias to the *current-generation* entry rather than
|
|
370
|
+
// the legacy dated key.
|
|
371
|
+
let bestBase = null;
|
|
372
|
+
let bestBaseKey = null;
|
|
373
|
+
for (const key of Object.keys(this.table)) {
|
|
374
|
+
const base = key.replace(DATED_SUFFIX_RE, '');
|
|
375
|
+
if (base !== key && modelName.startsWith(base)) {
|
|
376
|
+
if (bestBase === null || base.length > bestBase.length) {
|
|
377
|
+
bestBase = base;
|
|
378
|
+
bestBaseKey = key;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (bestBase && bestBaseKey) {
|
|
383
|
+
const aliasedTarget = MODEL_ALIASES[bestBase];
|
|
384
|
+
if (aliasedTarget && Object.hasOwn(this.table, aliasedTarget)) {
|
|
385
|
+
return { ...this.table[aliasedTarget] };
|
|
386
|
+
}
|
|
387
|
+
return Object.hasOwn(this.table, bestBaseKey) ? { ...this.table[bestBaseKey] } : null;
|
|
388
|
+
}
|
|
389
|
+
logger.warn('Unknown model, pricing not available', { model: modelName });
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Calculate a cost breakdown for the given model and token usage. See
|
|
394
|
+
* module-level `calculateCost` for tiered-pricing semantics.
|
|
395
|
+
*/
|
|
396
|
+
calculateCost(model, usage) {
|
|
397
|
+
const pricing = this.resolve(model);
|
|
398
|
+
if (!pricing) {
|
|
399
|
+
return { ...ZERO_COST };
|
|
400
|
+
}
|
|
401
|
+
return computeCost(pricing, usage);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
// Process-wide default singleton + back-compat module API
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
const defaultTable = new PricingTable();
|
|
408
|
+
/**
|
|
409
|
+
* (Re-)initialize the **default singleton** pricing table. Call with a custom
|
|
410
|
+
* file path to overlay user-provided prices on top of the built-in table.
|
|
411
|
+
* Call with `null`/`undefined` to reset to the built-in defaults.
|
|
412
|
+
*
|
|
413
|
+
* **Synchronous I/O — startup-only.** Reads the custom file with `readFileSync`
|
|
414
|
+
*; calling this on a hot path (e.g. a SIGHUP-triggered
|
|
415
|
+
* reload while the process is serving inference traffic) blocks the event
|
|
416
|
+
* loop. Schedule reloads from a worker thread or wrap in `setImmediate`.
|
|
417
|
+
*
|
|
418
|
+
* **Note:** This mutates a process-wide singleton. If your process serves
|
|
419
|
+
* multiple agents with distinct pricing files, instantiate a `PricingTable`
|
|
420
|
+
* per agent instead — calling `initPricing` again will overwrite the
|
|
421
|
+
* previously-loaded prices and the first agent will silently see the second's.
|
|
422
|
+
*/
|
|
423
|
+
export function initPricing(customFilePath) {
|
|
424
|
+
defaultTable.reset(customFilePath);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Resolve a model name against the default singleton pricing table.
|
|
428
|
+
*
|
|
429
|
+
* 1. Exact match (e.g. `claude-sonnet-4-20250514`)
|
|
430
|
+
* 2. Family-name alias (e.g. `claude-opus-4` → `claude-opus-4-7`) — see
|
|
431
|
+
* MODEL_ALIASES in pricing-data.ts. Aliases are the *primary* mechanism
|
|
432
|
+
* for routing family names to current-generation pricing.
|
|
433
|
+
* 3. Forward prefix — table key starts with modelName
|
|
434
|
+
* (e.g. an unaliased `gemini-2.5-flash` would match itself; only used for
|
|
435
|
+
* coverage of new keys not yet in the alias map)
|
|
436
|
+
* 4. Reverse prefix — modelName starts with table key's base (date stripped)
|
|
437
|
+
* (e.g. `claude-opus-4-99` matches base `claude-opus-4` from a dated key)
|
|
438
|
+
* 5. Return `null` and log a warning if nothing matches.
|
|
439
|
+
*/
|
|
440
|
+
export function resolveModelPricing(modelName) {
|
|
441
|
+
return defaultTable.resolve(modelName);
|
|
442
|
+
}
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
// Cost calculation helpers
|
|
445
|
+
// ---------------------------------------------------------------------------
|
|
446
|
+
function tokensToUsd(tokens, ratePerMTok) {
|
|
447
|
+
return (tokens * ratePerMTok) / 1_000_000;
|
|
448
|
+
}
|
|
449
|
+
function computeCost(pricing, usage) {
|
|
450
|
+
const tierMode = pricing.tierMode ?? 'flat';
|
|
451
|
+
const useTier = pricing.tierThreshold !== undefined && usage.inputTokens > pricing.tierThreshold;
|
|
452
|
+
// Output / thinking rates: only flat mode uses the tier overrides; in
|
|
453
|
+
// marginal mode they always use the base rates.
|
|
454
|
+
const outputRate = useTier && tierMode === 'flat' && pricing.tierOutputPerMTok !== undefined
|
|
455
|
+
? pricing.tierOutputPerMTok
|
|
456
|
+
: pricing.outputPerMTok;
|
|
457
|
+
const thinkingRate = useTier && tierMode === 'flat' && pricing.tierThinkingPerMTok !== undefined
|
|
458
|
+
? pricing.tierThinkingPerMTok
|
|
459
|
+
: (pricing.thinkingPerMTok ?? 0);
|
|
460
|
+
const cacheReadRate = pricing.cacheReadPerMTok ?? 0;
|
|
461
|
+
const cacheCreationRate = pricing.cacheCreationPerMTok ?? 0;
|
|
462
|
+
// Resolve the "billing rate" for input. In flat mode this single rate
|
|
463
|
+
// covers all input tokens; in marginal mode the rate that would have been
|
|
464
|
+
// applied if no caching had happened — used for the savings comparison.
|
|
465
|
+
const inputRate = useTier && tierMode === 'flat' && pricing.tierInputPerMTok !== undefined
|
|
466
|
+
? pricing.tierInputPerMTok
|
|
467
|
+
: pricing.inputPerMTok;
|
|
468
|
+
// Input cost: marginal mode splits at the threshold; flat mode uses inputRate.
|
|
469
|
+
let inputUsd;
|
|
470
|
+
if (useTier && tierMode === 'marginal' && pricing.tierInputPerMTok !== undefined) {
|
|
471
|
+
const threshold = pricing.tierThreshold;
|
|
472
|
+
const baseTokens = Math.min(usage.inputTokens, threshold);
|
|
473
|
+
const excessTokens = usage.inputTokens - baseTokens;
|
|
474
|
+
inputUsd =
|
|
475
|
+
tokensToUsd(baseTokens, pricing.inputPerMTok) +
|
|
476
|
+
tokensToUsd(excessTokens, pricing.tierInputPerMTok);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
inputUsd = tokensToUsd(usage.inputTokens, inputRate);
|
|
480
|
+
}
|
|
481
|
+
const outputUsd = tokensToUsd(usage.outputTokens, outputRate);
|
|
482
|
+
const thinkingUsd = tokensToUsd(usage.thinkingTokens, thinkingRate);
|
|
483
|
+
const cacheReadUsd = tokensToUsd(usage.cacheReadTokens, cacheReadRate);
|
|
484
|
+
const cacheCreationUsd = tokensToUsd(usage.cacheCreationTokens, cacheCreationRate);
|
|
485
|
+
const totalUsd = inputUsd + outputUsd + thinkingUsd + cacheReadUsd + cacheCreationUsd;
|
|
486
|
+
// Savings: what the cache-read tokens would have cost at the full input rate.
|
|
487
|
+
// In marginal mode, the savings rate depends on whether the fresh input
|
|
488
|
+
// exceeded the tier threshold — above-threshold tokens save at the tier rate,
|
|
489
|
+
// not the base rate (§PRC3).
|
|
490
|
+
const savingsInputRate = useTier &&
|
|
491
|
+
tierMode === 'marginal' &&
|
|
492
|
+
pricing.tierInputPerMTok !== undefined &&
|
|
493
|
+
usage.inputTokens > (pricing.tierThreshold ?? Infinity)
|
|
494
|
+
? pricing.tierInputPerMTok
|
|
495
|
+
: inputRate;
|
|
496
|
+
// Clamp to >= 0 — a misconfigured custom pricing entry where cacheReadRate
|
|
497
|
+
// exceeds inputRate would otherwise produce a negative "savings" number that
|
|
498
|
+
// gets reported as a positive cost benefit downstream.
|
|
499
|
+
const savingsFromCacheUsd = Math.max(0, tokensToUsd(usage.cacheReadTokens, savingsInputRate - cacheReadRate));
|
|
500
|
+
return {
|
|
501
|
+
inputUsd,
|
|
502
|
+
outputUsd,
|
|
503
|
+
thinkingUsd,
|
|
504
|
+
cacheReadUsd,
|
|
505
|
+
cacheCreationUsd,
|
|
506
|
+
totalUsd,
|
|
507
|
+
savingsFromCacheUsd,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Calculate a cost breakdown for the given model and token usage against the
|
|
512
|
+
* default singleton pricing table.
|
|
513
|
+
*
|
|
514
|
+
* If the model is unknown, returns an all-zero breakdown and logs a warning.
|
|
515
|
+
*
|
|
516
|
+
* Tiered pricing semantics (when `inputTokens > tierThreshold`):
|
|
517
|
+
*
|
|
518
|
+
* - `tierMode: 'flat'` (default) — the entire request (input, output, thinking)
|
|
519
|
+
* is billed at the configured tier rates. Matches Gemini 1.5/2.5 Pro.
|
|
520
|
+
* - `tierMode: 'marginal'` — only the input tokens above the threshold are
|
|
521
|
+
* billed at `tierInputPerMTok`; tokens up to the threshold use `inputPerMTok`.
|
|
522
|
+
* Output and thinking always use their base rates (the `tierOutput*` /
|
|
523
|
+
* `tierThinking*` fields are ignored in this mode).
|
|
524
|
+
*/
|
|
525
|
+
export function calculateCost(model, usage) {
|
|
526
|
+
return defaultTable.calculateCost(model, usage);
|
|
527
|
+
}
|
|
528
|
+
//# sourceMappingURL=pricing.js.map
|