palaryn 0.1.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 +21 -0
- package/README.md +716 -0
- package/dist/sdk/typescript/src/client.d.ts +71 -0
- package/dist/sdk/typescript/src/client.d.ts.map +1 -0
- package/dist/sdk/typescript/src/client.js +176 -0
- package/dist/sdk/typescript/src/client.js.map +1 -0
- package/dist/sdk/typescript/src/errors.d.ts +50 -0
- package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
- package/dist/sdk/typescript/src/errors.js +103 -0
- package/dist/sdk/typescript/src/errors.js.map +1 -0
- package/dist/sdk/typescript/src/index.d.ts +4 -0
- package/dist/sdk/typescript/src/index.d.ts.map +1 -0
- package/dist/sdk/typescript/src/index.js +15 -0
- package/dist/sdk/typescript/src/index.js.map +1 -0
- package/dist/sdk/typescript/src/types.d.ts +101 -0
- package/dist/sdk/typescript/src/types.d.ts.map +1 -0
- package/dist/sdk/typescript/src/types.js +6 -0
- package/dist/sdk/typescript/src/types.js.map +1 -0
- package/dist/src/admin/index.d.ts +2 -0
- package/dist/src/admin/index.d.ts.map +1 -0
- package/dist/src/admin/index.js +6 -0
- package/dist/src/admin/index.js.map +1 -0
- package/dist/src/admin/routes.d.ts +5 -0
- package/dist/src/admin/routes.d.ts.map +1 -0
- package/dist/src/admin/routes.js +471 -0
- package/dist/src/admin/routes.js.map +1 -0
- package/dist/src/admin/templates.d.ts +51 -0
- package/dist/src/admin/templates.d.ts.map +1 -0
- package/dist/src/admin/templates.js +500 -0
- package/dist/src/admin/templates.js.map +1 -0
- package/dist/src/anomaly/detector.d.ts +141 -0
- package/dist/src/anomaly/detector.d.ts.map +1 -0
- package/dist/src/anomaly/detector.js +554 -0
- package/dist/src/anomaly/detector.js.map +1 -0
- package/dist/src/anomaly/index.d.ts +2 -0
- package/dist/src/anomaly/index.d.ts.map +1 -0
- package/dist/src/anomaly/index.js +7 -0
- package/dist/src/anomaly/index.js.map +1 -0
- package/dist/src/approval/manager.d.ts +147 -0
- package/dist/src/approval/manager.d.ts.map +1 -0
- package/dist/src/approval/manager.js +511 -0
- package/dist/src/approval/manager.js.map +1 -0
- package/dist/src/approval/webhook.d.ts +36 -0
- package/dist/src/approval/webhook.d.ts.map +1 -0
- package/dist/src/approval/webhook.js +135 -0
- package/dist/src/approval/webhook.js.map +1 -0
- package/dist/src/audit/logger.d.ts +70 -0
- package/dist/src/audit/logger.d.ts.map +1 -0
- package/dist/src/audit/logger.js +440 -0
- package/dist/src/audit/logger.js.map +1 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +22 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/password.d.ts +3 -0
- package/dist/src/auth/password.d.ts.map +1 -0
- package/dist/src/auth/password.js +25 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/auth/pkce.d.ts +13 -0
- package/dist/src/auth/pkce.d.ts.map +1 -0
- package/dist/src/auth/pkce.js +58 -0
- package/dist/src/auth/pkce.js.map +1 -0
- package/dist/src/auth/providers.d.ts +28 -0
- package/dist/src/auth/providers.d.ts.map +1 -0
- package/dist/src/auth/providers.js +198 -0
- package/dist/src/auth/providers.js.map +1 -0
- package/dist/src/auth/routes.d.ts +14 -0
- package/dist/src/auth/routes.d.ts.map +1 -0
- package/dist/src/auth/routes.js +431 -0
- package/dist/src/auth/routes.js.map +1 -0
- package/dist/src/auth/session.d.ts +24 -0
- package/dist/src/auth/session.d.ts.map +1 -0
- package/dist/src/auth/session.js +105 -0
- package/dist/src/auth/session.js.map +1 -0
- package/dist/src/billing/index.d.ts +7 -0
- package/dist/src/billing/index.d.ts.map +1 -0
- package/dist/src/billing/index.js +14 -0
- package/dist/src/billing/index.js.map +1 -0
- package/dist/src/billing/plan-enforcer.d.ts +44 -0
- package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
- package/dist/src/billing/plan-enforcer.js +110 -0
- package/dist/src/billing/plan-enforcer.js.map +1 -0
- package/dist/src/billing/routes.d.ts +15 -0
- package/dist/src/billing/routes.d.ts.map +1 -0
- package/dist/src/billing/routes.js +193 -0
- package/dist/src/billing/routes.js.map +1 -0
- package/dist/src/billing/stripe-client.d.ts +14 -0
- package/dist/src/billing/stripe-client.d.ts.map +1 -0
- package/dist/src/billing/stripe-client.js +51 -0
- package/dist/src/billing/stripe-client.js.map +1 -0
- package/dist/src/billing/webhook-handler.d.ts +19 -0
- package/dist/src/billing/webhook-handler.d.ts.map +1 -0
- package/dist/src/billing/webhook-handler.js +169 -0
- package/dist/src/billing/webhook-handler.js.map +1 -0
- package/dist/src/billing/webhook-routes.d.ts +5 -0
- package/dist/src/billing/webhook-routes.d.ts.map +1 -0
- package/dist/src/billing/webhook-routes.js +30 -0
- package/dist/src/billing/webhook-routes.js.map +1 -0
- package/dist/src/budget/manager.d.ts +95 -0
- package/dist/src/budget/manager.d.ts.map +1 -0
- package/dist/src/budget/manager.js +547 -0
- package/dist/src/budget/manager.js.map +1 -0
- package/dist/src/budget/usage-extractor.d.ts +38 -0
- package/dist/src/budget/usage-extractor.d.ts.map +1 -0
- package/dist/src/budget/usage-extractor.js +165 -0
- package/dist/src/budget/usage-extractor.js.map +1 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +115 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config/defaults.d.ts +3 -0
- package/dist/src/config/defaults.d.ts.map +1 -0
- package/dist/src/config/defaults.js +243 -0
- package/dist/src/config/defaults.js.map +1 -0
- package/dist/src/config/validate.d.ts +15 -0
- package/dist/src/config/validate.d.ts.map +1 -0
- package/dist/src/config/validate.js +105 -0
- package/dist/src/config/validate.js.map +1 -0
- package/dist/src/dlp/composite-scanner.d.ts +47 -0
- package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
- package/dist/src/dlp/composite-scanner.js +186 -0
- package/dist/src/dlp/composite-scanner.js.map +1 -0
- package/dist/src/dlp/index.d.ts +10 -0
- package/dist/src/dlp/index.d.ts.map +1 -0
- package/dist/src/dlp/index.js +26 -0
- package/dist/src/dlp/index.js.map +1 -0
- package/dist/src/dlp/interfaces.d.ts +33 -0
- package/dist/src/dlp/interfaces.d.ts.map +1 -0
- package/dist/src/dlp/interfaces.js +3 -0
- package/dist/src/dlp/interfaces.js.map +1 -0
- package/dist/src/dlp/patterns.d.ts +9 -0
- package/dist/src/dlp/patterns.d.ts.map +1 -0
- package/dist/src/dlp/patterns.js +25 -0
- package/dist/src/dlp/patterns.js.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.js +148 -0
- package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.js +290 -0
- package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
- package/dist/src/dlp/regex-backend.d.ts +32 -0
- package/dist/src/dlp/regex-backend.d.ts.map +1 -0
- package/dist/src/dlp/regex-backend.js +153 -0
- package/dist/src/dlp/regex-backend.js.map +1 -0
- package/dist/src/dlp/scanner.d.ts +122 -0
- package/dist/src/dlp/scanner.d.ts.map +1 -0
- package/dist/src/dlp/scanner.js +444 -0
- package/dist/src/dlp/scanner.js.map +1 -0
- package/dist/src/dlp/text-normalizer.d.ts +41 -0
- package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
- package/dist/src/dlp/text-normalizer.js +203 -0
- package/dist/src/dlp/text-normalizer.js.map +1 -0
- package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
- package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
- package/dist/src/dlp/trufflehog-backend.js +151 -0
- package/dist/src/dlp/trufflehog-backend.js.map +1 -0
- package/dist/src/executor/http-executor.d.ts +25 -0
- package/dist/src/executor/http-executor.d.ts.map +1 -0
- package/dist/src/executor/http-executor.js +333 -0
- package/dist/src/executor/http-executor.js.map +1 -0
- package/dist/src/executor/index.d.ts +6 -0
- package/dist/src/executor/index.d.ts.map +1 -0
- package/dist/src/executor/index.js +12 -0
- package/dist/src/executor/index.js.map +1 -0
- package/dist/src/executor/interfaces.d.ts +11 -0
- package/dist/src/executor/interfaces.d.ts.map +1 -0
- package/dist/src/executor/interfaces.js +3 -0
- package/dist/src/executor/interfaces.js.map +1 -0
- package/dist/src/executor/noop-executor.d.ts +13 -0
- package/dist/src/executor/noop-executor.d.ts.map +1 -0
- package/dist/src/executor/noop-executor.js +21 -0
- package/dist/src/executor/noop-executor.js.map +1 -0
- package/dist/src/executor/registry.d.ts +30 -0
- package/dist/src/executor/registry.d.ts.map +1 -0
- package/dist/src/executor/registry.js +62 -0
- package/dist/src/executor/registry.js.map +1 -0
- package/dist/src/executor/slack-executor.d.ts +24 -0
- package/dist/src/executor/slack-executor.d.ts.map +1 -0
- package/dist/src/executor/slack-executor.js +147 -0
- package/dist/src/executor/slack-executor.js.map +1 -0
- package/dist/src/index.d.ts +25 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +74 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/auth-verifier.d.ts +23 -0
- package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
- package/dist/src/mcp/auth-verifier.js +162 -0
- package/dist/src/mcp/auth-verifier.js.map +1 -0
- package/dist/src/mcp/bridge.d.ts +132 -0
- package/dist/src/mcp/bridge.d.ts.map +1 -0
- package/dist/src/mcp/bridge.js +734 -0
- package/dist/src/mcp/bridge.js.map +1 -0
- package/dist/src/mcp/http-transport.d.ts +32 -0
- package/dist/src/mcp/http-transport.d.ts.map +1 -0
- package/dist/src/mcp/http-transport.js +538 -0
- package/dist/src/mcp/http-transport.js.map +1 -0
- package/dist/src/mcp/index.d.ts +10 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +17 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/oauth-pages.d.ts +23 -0
- package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
- package/dist/src/mcp/oauth-pages.js +121 -0
- package/dist/src/mcp/oauth-pages.js.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.js +226 -0
- package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
- package/dist/src/mcp/oauth-provider.d.ts +95 -0
- package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
- package/dist/src/mcp/oauth-provider.js +360 -0
- package/dist/src/mcp/oauth-provider.js.map +1 -0
- package/dist/src/mcp/oauth-stores.d.ts +62 -0
- package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-stores.js +154 -0
- package/dist/src/mcp/oauth-stores.js.map +1 -0
- package/dist/src/mcp/server.d.ts +18 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +51 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/metrics/collector.d.ts +106 -0
- package/dist/src/metrics/collector.d.ts.map +1 -0
- package/dist/src/metrics/collector.js +311 -0
- package/dist/src/metrics/collector.js.map +1 -0
- package/dist/src/metrics/index.d.ts +2 -0
- package/dist/src/metrics/index.d.ts.map +1 -0
- package/dist/src/metrics/index.js +6 -0
- package/dist/src/metrics/index.js.map +1 -0
- package/dist/src/middleware/auth.d.ts +77 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +720 -0
- package/dist/src/middleware/auth.js.map +1 -0
- package/dist/src/middleware/session.d.ts +18 -0
- package/dist/src/middleware/session.d.ts.map +1 -0
- package/dist/src/middleware/session.js +67 -0
- package/dist/src/middleware/session.js.map +1 -0
- package/dist/src/middleware/validate.d.ts +3 -0
- package/dist/src/middleware/validate.d.ts.map +1 -0
- package/dist/src/middleware/validate.js +85 -0
- package/dist/src/middleware/validate.js.map +1 -0
- package/dist/src/policy/engine.d.ts +107 -0
- package/dist/src/policy/engine.d.ts.map +1 -0
- package/dist/src/policy/engine.js +646 -0
- package/dist/src/policy/engine.js.map +1 -0
- package/dist/src/policy/index.d.ts +3 -0
- package/dist/src/policy/index.d.ts.map +1 -0
- package/dist/src/policy/index.js +8 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/opa-engine.d.ts +176 -0
- package/dist/src/policy/opa-engine.d.ts.map +1 -0
- package/dist/src/policy/opa-engine.js +790 -0
- package/dist/src/policy/opa-engine.js.map +1 -0
- package/dist/src/proxy/forward-proxy.d.ts +30 -0
- package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
- package/dist/src/proxy/forward-proxy.js +580 -0
- package/dist/src/proxy/forward-proxy.js.map +1 -0
- package/dist/src/proxy/index.d.ts +2 -0
- package/dist/src/proxy/index.d.ts.map +1 -0
- package/dist/src/proxy/index.js +8 -0
- package/dist/src/proxy/index.js.map +1 -0
- package/dist/src/ratelimit/limiter.d.ts +45 -0
- package/dist/src/ratelimit/limiter.d.ts.map +1 -0
- package/dist/src/ratelimit/limiter.js +158 -0
- package/dist/src/ratelimit/limiter.js.map +1 -0
- package/dist/src/replay/engine.d.ts +40 -0
- package/dist/src/replay/engine.d.ts.map +1 -0
- package/dist/src/replay/engine.js +106 -0
- package/dist/src/replay/engine.js.map +1 -0
- package/dist/src/replay/index.d.ts +2 -0
- package/dist/src/replay/index.d.ts.map +1 -0
- package/dist/src/replay/index.js +6 -0
- package/dist/src/replay/index.js.map +1 -0
- package/dist/src/saas/index.d.ts +2 -0
- package/dist/src/saas/index.d.ts.map +1 -0
- package/dist/src/saas/index.js +18 -0
- package/dist/src/saas/index.js.map +1 -0
- package/dist/src/saas/routes.d.ts +18 -0
- package/dist/src/saas/routes.d.ts.map +1 -0
- package/dist/src/saas/routes.js +1566 -0
- package/dist/src/saas/routes.js.map +1 -0
- package/dist/src/server/app.d.ts +44 -0
- package/dist/src/server/app.d.ts.map +1 -0
- package/dist/src/server/app.js +854 -0
- package/dist/src/server/app.js.map +1 -0
- package/dist/src/server/errors.d.ts +32 -0
- package/dist/src/server/errors.d.ts.map +1 -0
- package/dist/src/server/errors.js +39 -0
- package/dist/src/server/errors.js.map +1 -0
- package/dist/src/server/gateway.d.ts +165 -0
- package/dist/src/server/gateway.d.ts.map +1 -0
- package/dist/src/server/gateway.js +964 -0
- package/dist/src/server/gateway.js.map +1 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +295 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/logger.d.ts +33 -0
- package/dist/src/server/logger.d.ts.map +1 -0
- package/dist/src/server/logger.js +230 -0
- package/dist/src/server/logger.js.map +1 -0
- package/dist/src/server/stream-proxy.d.ts +32 -0
- package/dist/src/server/stream-proxy.d.ts.map +1 -0
- package/dist/src/server/stream-proxy.js +184 -0
- package/dist/src/server/stream-proxy.js.map +1 -0
- package/dist/src/storage/file-persistence.d.ts +48 -0
- package/dist/src/storage/file-persistence.d.ts.map +1 -0
- package/dist/src/storage/file-persistence.js +280 -0
- package/dist/src/storage/file-persistence.js.map +1 -0
- package/dist/src/storage/index.d.ts +5 -0
- package/dist/src/storage/index.d.ts.map +1 -0
- package/dist/src/storage/index.js +21 -0
- package/dist/src/storage/index.js.map +1 -0
- package/dist/src/storage/interfaces.d.ts +237 -0
- package/dist/src/storage/interfaces.d.ts.map +1 -0
- package/dist/src/storage/interfaces.js +3 -0
- package/dist/src/storage/interfaces.js.map +1 -0
- package/dist/src/storage/memory.d.ts +162 -0
- package/dist/src/storage/memory.d.ts.map +1 -0
- package/dist/src/storage/memory.js +603 -0
- package/dist/src/storage/memory.js.map +1 -0
- package/dist/src/storage/postgres.d.ts +267 -0
- package/dist/src/storage/postgres.d.ts.map +1 -0
- package/dist/src/storage/postgres.js +1555 -0
- package/dist/src/storage/postgres.js.map +1 -0
- package/dist/src/storage/redis.d.ts +202 -0
- package/dist/src/storage/redis.d.ts.map +1 -0
- package/dist/src/storage/redis.js +629 -0
- package/dist/src/storage/redis.js.map +1 -0
- package/dist/src/tracing/index.d.ts +2 -0
- package/dist/src/tracing/index.d.ts.map +1 -0
- package/dist/src/tracing/index.js +6 -0
- package/dist/src/tracing/index.js.map +1 -0
- package/dist/src/tracing/provider.d.ts +43 -0
- package/dist/src/tracing/provider.d.ts.map +1 -0
- package/dist/src/tracing/provider.js +74 -0
- package/dist/src/tracing/provider.js.map +1 -0
- package/dist/src/trust/calculator.d.ts +54 -0
- package/dist/src/trust/calculator.d.ts.map +1 -0
- package/dist/src/trust/calculator.js +102 -0
- package/dist/src/trust/calculator.js.map +1 -0
- package/dist/src/trust/index.d.ts +2 -0
- package/dist/src/trust/index.d.ts.map +1 -0
- package/dist/src/trust/index.js +7 -0
- package/dist/src/trust/index.js.map +1 -0
- package/dist/src/types/budget.d.ts +30 -0
- package/dist/src/types/budget.d.ts.map +1 -0
- package/dist/src/types/budget.js +3 -0
- package/dist/src/types/budget.js.map +1 -0
- package/dist/src/types/config.d.ts +176 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +3 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/events.d.ts +24 -0
- package/dist/src/types/events.d.ts.map +1 -0
- package/dist/src/types/events.js +3 -0
- package/dist/src/types/events.js.map +1 -0
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +24 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/policy.d.ts +60 -0
- package/dist/src/types/policy.d.ts.map +1 -0
- package/dist/src/types/policy.js +3 -0
- package/dist/src/types/policy.js.map +1 -0
- package/dist/src/types/stripe-config.d.ts +12 -0
- package/dist/src/types/stripe-config.d.ts.map +1 -0
- package/dist/src/types/stripe-config.js +3 -0
- package/dist/src/types/stripe-config.js.map +1 -0
- package/dist/src/types/subscription.d.ts +24 -0
- package/dist/src/types/subscription.d.ts.map +1 -0
- package/dist/src/types/subscription.js +38 -0
- package/dist/src/types/subscription.js.map +1 -0
- package/dist/src/types/tool-call.d.ts +42 -0
- package/dist/src/types/tool-call.d.ts.map +1 -0
- package/dist/src/types/tool-call.js +3 -0
- package/dist/src/types/tool-call.js.map +1 -0
- package/dist/src/types/tool-result.d.ts +58 -0
- package/dist/src/types/tool-result.d.ts.map +1 -0
- package/dist/src/types/tool-result.js +3 -0
- package/dist/src/types/tool-result.js.map +1 -0
- package/dist/src/types/user.d.ts +101 -0
- package/dist/src/types/user.d.ts.map +1 -0
- package/dist/src/types/user.js +6 -0
- package/dist/src/types/user.js.map +1 -0
- package/dist/tests/integration/api.test.d.ts +2 -0
- package/dist/tests/integration/api.test.d.ts.map +1 -0
- package/dist/tests/integration/api.test.js +1199 -0
- package/dist/tests/integration/api.test.js.map +1 -0
- package/dist/tests/integration/proxy.test.d.ts +2 -0
- package/dist/tests/integration/proxy.test.d.ts.map +1 -0
- package/dist/tests/integration/proxy.test.js +251 -0
- package/dist/tests/integration/proxy.test.js.map +1 -0
- package/dist/tests/integration/storage.test.d.ts +16 -0
- package/dist/tests/integration/storage.test.d.ts.map +1 -0
- package/dist/tests/integration/storage.test.js +826 -0
- package/dist/tests/integration/storage.test.js.map +1 -0
- package/dist/tests/unit/admin.test.d.ts +2 -0
- package/dist/tests/unit/admin.test.d.ts.map +1 -0
- package/dist/tests/unit/admin.test.js +698 -0
- package/dist/tests/unit/admin.test.js.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.js +903 -0
- package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
- package/dist/tests/unit/approval-manager.test.d.ts +2 -0
- package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-manager.test.js +528 -0
- package/dist/tests/unit/approval-manager.test.js.map +1 -0
- package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
- package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-webhook.test.js +355 -0
- package/dist/tests/unit/approval-webhook.test.js.map +1 -0
- package/dist/tests/unit/audit-logger.test.d.ts +2 -0
- package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
- package/dist/tests/unit/audit-logger.test.js +635 -0
- package/dist/tests/unit/audit-logger.test.js.map +1 -0
- package/dist/tests/unit/auth-routes.test.d.ts +2 -0
- package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-routes.test.js +281 -0
- package/dist/tests/unit/auth-routes.test.js.map +1 -0
- package/dist/tests/unit/auth.test.d.ts +2 -0
- package/dist/tests/unit/auth.test.d.ts.map +1 -0
- package/dist/tests/unit/auth.test.js +1382 -0
- package/dist/tests/unit/auth.test.js.map +1 -0
- package/dist/tests/unit/billing.test.d.ts +2 -0
- package/dist/tests/unit/billing.test.d.ts.map +1 -0
- package/dist/tests/unit/billing.test.js +579 -0
- package/dist/tests/unit/billing.test.js.map +1 -0
- package/dist/tests/unit/budget-manager.test.d.ts +2 -0
- package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-manager.test.js +778 -0
- package/dist/tests/unit/budget-manager.test.js.map +1 -0
- package/dist/tests/unit/budget-race.test.d.ts +2 -0
- package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-race.test.js +58 -0
- package/dist/tests/unit/budget-race.test.js.map +1 -0
- package/dist/tests/unit/cli.test.d.ts +2 -0
- package/dist/tests/unit/cli.test.d.ts.map +1 -0
- package/dist/tests/unit/cli.test.js +93 -0
- package/dist/tests/unit/cli.test.js.map +1 -0
- package/dist/tests/unit/concurrency.test.d.ts +2 -0
- package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
- package/dist/tests/unit/concurrency.test.js +1270 -0
- package/dist/tests/unit/concurrency.test.js.map +1 -0
- package/dist/tests/unit/config-validate.test.d.ts +2 -0
- package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
- package/dist/tests/unit/config-validate.test.js +230 -0
- package/dist/tests/unit/config-validate.test.js.map +1 -0
- package/dist/tests/unit/defaults.test.d.ts +2 -0
- package/dist/tests/unit/defaults.test.d.ts.map +1 -0
- package/dist/tests/unit/defaults.test.js +364 -0
- package/dist/tests/unit/defaults.test.js.map +1 -0
- package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
- package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-backends.test.js +563 -0
- package/dist/tests/unit/dlp-backends.test.js.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.js +739 -0
- package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
- package/dist/tests/unit/error-responses.test.d.ts +2 -0
- package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
- package/dist/tests/unit/error-responses.test.js +101 -0
- package/dist/tests/unit/error-responses.test.js.map +1 -0
- package/dist/tests/unit/executor-registry.test.d.ts +2 -0
- package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
- package/dist/tests/unit/executor-registry.test.js +390 -0
- package/dist/tests/unit/executor-registry.test.js.map +1 -0
- package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
- package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
- package/dist/tests/unit/forward-proxy.test.js +621 -0
- package/dist/tests/unit/forward-proxy.test.js.map +1 -0
- package/dist/tests/unit/gateway-features.test.d.ts +2 -0
- package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
- package/dist/tests/unit/gateway-features.test.js +753 -0
- package/dist/tests/unit/gateway-features.test.js.map +1 -0
- package/dist/tests/unit/http-executor.test.d.ts +2 -0
- package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/http-executor.test.js +310 -0
- package/dist/tests/unit/http-executor.test.js.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.js +1136 -0
- package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.js +899 -0
- package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.js +759 -0
- package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
- package/dist/tests/unit/mcp-server.test.d.ts +15 -0
- package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-server.test.js +158 -0
- package/dist/tests/unit/mcp-server.test.js.map +1 -0
- package/dist/tests/unit/metrics.test.d.ts +2 -0
- package/dist/tests/unit/metrics.test.d.ts.map +1 -0
- package/dist/tests/unit/metrics.test.js +208 -0
- package/dist/tests/unit/metrics.test.js.map +1 -0
- package/dist/tests/unit/oauth.test.d.ts +2 -0
- package/dist/tests/unit/oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/oauth.test.js +281 -0
- package/dist/tests/unit/oauth.test.js.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
- package/dist/tests/unit/opa-engine.test.d.ts +2 -0
- package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-engine.test.js +1813 -0
- package/dist/tests/unit/opa-engine.test.js.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.js +528 -0
- package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.d.ts +2 -0
- package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +1345 -0
- package/dist/tests/unit/policy-engine.test.js.map +1 -0
- package/dist/tests/unit/policy-store.test.d.ts +2 -0
- package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-store.test.js +60 -0
- package/dist/tests/unit/policy-store.test.js.map +1 -0
- package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
- package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/postgres-storage.test.js +614 -0
- package/dist/tests/unit/postgres-storage.test.js.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
- package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.js +166 -0
- package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
- package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
- package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
- package/dist/tests/unit/rate-limiter.test.js +443 -0
- package/dist/tests/unit/rate-limiter.test.js.map +1 -0
- package/dist/tests/unit/redis-storage.test.d.ts +2 -0
- package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/redis-storage.test.js +766 -0
- package/dist/tests/unit/redis-storage.test.js.map +1 -0
- package/dist/tests/unit/replay-engine.test.d.ts +2 -0
- package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/replay-engine.test.js +371 -0
- package/dist/tests/unit/replay-engine.test.js.map +1 -0
- package/dist/tests/unit/saas-routes.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes.test.js +1399 -0
- package/dist/tests/unit/saas-routes.test.js.map +1 -0
- package/dist/tests/unit/session.test.d.ts +2 -0
- package/dist/tests/unit/session.test.d.ts.map +1 -0
- package/dist/tests/unit/session.test.js +532 -0
- package/dist/tests/unit/session.test.js.map +1 -0
- package/dist/tests/unit/slack-executor.test.d.ts +2 -0
- package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/slack-executor.test.js +209 -0
- package/dist/tests/unit/slack-executor.test.js.map +1 -0
- package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
- package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/storage-hardening.test.js +165 -0
- package/dist/tests/unit/storage-hardening.test.js.map +1 -0
- package/dist/tests/unit/storage.test.d.ts +2 -0
- package/dist/tests/unit/storage.test.d.ts.map +1 -0
- package/dist/tests/unit/storage.test.js +698 -0
- package/dist/tests/unit/storage.test.js.map +1 -0
- package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
- package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
- package/dist/tests/unit/text-normalizer.test.js +229 -0
- package/dist/tests/unit/text-normalizer.test.js.map +1 -0
- package/dist/tests/unit/tracing.test.d.ts +2 -0
- package/dist/tests/unit/tracing.test.d.ts.map +1 -0
- package/dist/tests/unit/tracing.test.js +611 -0
- package/dist/tests/unit/tracing.test.js.map +1 -0
- package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
- package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
- package/dist/tests/unit/trust-calculator.test.js +497 -0
- package/dist/tests/unit/trust-calculator.test.js.map +1 -0
- package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
- package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
- package/dist/tests/unit/ts-sdk.test.js +421 -0
- package/dist/tests/unit/ts-sdk.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
- package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor.test.js +271 -0
- package/dist/tests/unit/usage-extractor.test.js.map +1 -0
- package/dist/tests/unit/user-stores.test.d.ts +2 -0
- package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
- package/dist/tests/unit/user-stores.test.js +687 -0
- package/dist/tests/unit/user-stores.test.js.map +1 -0
- package/dist/tests/unit/validate.test.d.ts +2 -0
- package/dist/tests/unit/validate.test.d.ts.map +1 -0
- package/dist/tests/unit/validate.test.js +545 -0
- package/dist/tests/unit/validate.test.js.map +1 -0
- package/package.json +86 -0
- package/policy-packs/README.md +42 -0
- package/policy-packs/default.yaml +46 -0
- package/policy-packs/dev_fast.yaml +54 -0
- package/policy-packs/prod_strict.yaml +83 -0
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const manager_1 = require("../../src/budget/manager");
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Helper: create a ToolCall with sensible defaults and optional overrides
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
function createTestToolCall(overrides) {
|
|
8
|
+
return {
|
|
9
|
+
tool_call_id: 'tc-001',
|
|
10
|
+
task_id: 'task-001',
|
|
11
|
+
workspace_id: 'ws-001',
|
|
12
|
+
actor: { type: 'agent', id: 'agent-001' },
|
|
13
|
+
source: { platform: 'test' },
|
|
14
|
+
tool: { name: 'http.request', capability: 'read' },
|
|
15
|
+
args: { method: 'GET', url: 'https://example.com' },
|
|
16
|
+
timestamp: new Date().toISOString(),
|
|
17
|
+
...overrides,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Default test config (generous limits so individual tests can tighten them)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function defaultConfig(overrides) {
|
|
24
|
+
return {
|
|
25
|
+
task_budget_usd: 2.0,
|
|
26
|
+
max_steps_per_task: 50,
|
|
27
|
+
max_retries_per_call: 3,
|
|
28
|
+
max_wall_clock_ms: 300000,
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// ===========================================================================
|
|
33
|
+
// Tests
|
|
34
|
+
// ===========================================================================
|
|
35
|
+
describe('BudgetManager', () => {
|
|
36
|
+
// -----------------------------------------------------------------------
|
|
37
|
+
// Cost Estimation
|
|
38
|
+
// -----------------------------------------------------------------------
|
|
39
|
+
describe('estimateCost', () => {
|
|
40
|
+
let manager;
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
manager = new manager_1.BudgetManager(defaultConfig());
|
|
43
|
+
});
|
|
44
|
+
it('should return cost based on tool name from cost table', () => {
|
|
45
|
+
const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'read' } });
|
|
46
|
+
const estimate = manager.estimateCost(toolCall);
|
|
47
|
+
expect(estimate.tool_name).toBe('http.request');
|
|
48
|
+
expect(estimate.capability).toBe('read');
|
|
49
|
+
expect(estimate.estimated_cost_usd).toBe(0.001);
|
|
50
|
+
});
|
|
51
|
+
it('should return higher cost for known write tool entry', () => {
|
|
52
|
+
// http.request with write capability should match http.request.write (0.002)
|
|
53
|
+
const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'write' } });
|
|
54
|
+
const estimate = manager.estimateCost(toolCall);
|
|
55
|
+
expect(estimate.estimated_cost_usd).toBe(0.002);
|
|
56
|
+
});
|
|
57
|
+
it('should return correct cost for slack.post', () => {
|
|
58
|
+
const toolCall = createTestToolCall({ tool: { name: 'slack.post', capability: 'read' } });
|
|
59
|
+
const estimate = manager.estimateCost(toolCall);
|
|
60
|
+
expect(estimate.estimated_cost_usd).toBe(0.003);
|
|
61
|
+
});
|
|
62
|
+
it('should return correct cost for git.operation', () => {
|
|
63
|
+
const toolCall = createTestToolCall({ tool: { name: 'git.operation', capability: 'read' } });
|
|
64
|
+
const estimate = manager.estimateCost(toolCall);
|
|
65
|
+
expect(estimate.estimated_cost_usd).toBe(0.002);
|
|
66
|
+
});
|
|
67
|
+
it('should return correct cost for db.query', () => {
|
|
68
|
+
const toolCall = createTestToolCall({ tool: { name: 'db.query', capability: 'read' } });
|
|
69
|
+
const estimate = manager.estimateCost(toolCall);
|
|
70
|
+
expect(estimate.estimated_cost_usd).toBe(0.005);
|
|
71
|
+
});
|
|
72
|
+
it('should return correct cost for browser.navigate', () => {
|
|
73
|
+
const toolCall = createTestToolCall({ tool: { name: 'browser.navigate', capability: 'read' } });
|
|
74
|
+
const estimate = manager.estimateCost(toolCall);
|
|
75
|
+
expect(estimate.estimated_cost_usd).toBe(0.01);
|
|
76
|
+
});
|
|
77
|
+
it('should return default cost for unknown tools', () => {
|
|
78
|
+
const toolCall = createTestToolCall({ tool: { name: 'custom.unknown_tool', capability: 'read' } });
|
|
79
|
+
const estimate = manager.estimateCost(toolCall);
|
|
80
|
+
expect(estimate.tool_name).toBe('custom.unknown_tool');
|
|
81
|
+
expect(estimate.estimated_cost_usd).toBe(0.001); // default
|
|
82
|
+
});
|
|
83
|
+
it('should apply 1.5x multiplier for delete capability', () => {
|
|
84
|
+
// db.query base = 0.005, delete => 0.005 * 1.5 = 0.0075
|
|
85
|
+
const toolCall = createTestToolCall({ tool: { name: 'db.query', capability: 'delete' } });
|
|
86
|
+
const estimate = manager.estimateCost(toolCall);
|
|
87
|
+
expect(estimate.estimated_cost_usd).toBeCloseTo(0.0075, 6);
|
|
88
|
+
});
|
|
89
|
+
it('should apply 2.0x multiplier for admin capability', () => {
|
|
90
|
+
// db.query base = 0.005, admin => 0.005 * 2.0 = 0.01
|
|
91
|
+
const toolCall = createTestToolCall({ tool: { name: 'db.query', capability: 'admin' } });
|
|
92
|
+
const estimate = manager.estimateCost(toolCall);
|
|
93
|
+
expect(estimate.estimated_cost_usd).toBeCloseTo(0.01, 6);
|
|
94
|
+
});
|
|
95
|
+
it('should respect per-call max_cost_usd constraint as upper bound', () => {
|
|
96
|
+
// browser.navigate base = 0.01, but constraint limits to 0.005
|
|
97
|
+
const toolCall = createTestToolCall({
|
|
98
|
+
tool: { name: 'browser.navigate', capability: 'read' },
|
|
99
|
+
constraints: { max_cost_usd: 0.005 },
|
|
100
|
+
});
|
|
101
|
+
const estimate = manager.estimateCost(toolCall);
|
|
102
|
+
expect(estimate.estimated_cost_usd).toBe(0.005);
|
|
103
|
+
});
|
|
104
|
+
it('should not reduce cost when max_cost_usd is higher than estimated', () => {
|
|
105
|
+
// http.request base = 0.001, constraint = 1.0 (way above)
|
|
106
|
+
const toolCall = createTestToolCall({
|
|
107
|
+
tool: { name: 'http.request', capability: 'read' },
|
|
108
|
+
constraints: { max_cost_usd: 1.0 },
|
|
109
|
+
});
|
|
110
|
+
const estimate = manager.estimateCost(toolCall);
|
|
111
|
+
expect(estimate.estimated_cost_usd).toBe(0.001);
|
|
112
|
+
});
|
|
113
|
+
it('should not apply constraint when max_cost_usd is 0', () => {
|
|
114
|
+
// max_cost_usd: 0 should be ignored (the condition checks > 0)
|
|
115
|
+
const toolCall = createTestToolCall({
|
|
116
|
+
tool: { name: 'http.request', capability: 'read' },
|
|
117
|
+
constraints: { max_cost_usd: 0 },
|
|
118
|
+
});
|
|
119
|
+
const estimate = manager.estimateCost(toolCall);
|
|
120
|
+
expect(estimate.estimated_cost_usd).toBe(0.001);
|
|
121
|
+
});
|
|
122
|
+
it('should not apply constraint when constraints is undefined', () => {
|
|
123
|
+
const toolCall = createTestToolCall({ constraints: undefined });
|
|
124
|
+
const estimate = manager.estimateCost(toolCall);
|
|
125
|
+
expect(estimate.estimated_cost_usd).toBe(0.001);
|
|
126
|
+
});
|
|
127
|
+
it('should look up capability-specific key for write on http.request', () => {
|
|
128
|
+
// http.request.write exists in COST_TABLE with value 0.002
|
|
129
|
+
const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'write' } });
|
|
130
|
+
const estimate = manager.estimateCost(toolCall);
|
|
131
|
+
expect(estimate.estimated_cost_usd).toBe(0.002);
|
|
132
|
+
});
|
|
133
|
+
it('should fall back to base tool cost for write when no .write key exists', () => {
|
|
134
|
+
// slack.post with write capability: no slack.post.write entry, so falls back to slack.post (0.003)
|
|
135
|
+
const toolCall = createTestToolCall({ tool: { name: 'slack.post', capability: 'write' } });
|
|
136
|
+
const estimate = manager.estimateCost(toolCall);
|
|
137
|
+
expect(estimate.estimated_cost_usd).toBe(0.003);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
// -----------------------------------------------------------------------
|
|
141
|
+
// Budget Check
|
|
142
|
+
// -----------------------------------------------------------------------
|
|
143
|
+
describe('check', () => {
|
|
144
|
+
it('should allow call when within all limits', () => {
|
|
145
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
146
|
+
const toolCall = createTestToolCall();
|
|
147
|
+
const result = manager.check(toolCall);
|
|
148
|
+
expect(result.allowed).toBe(true);
|
|
149
|
+
expect(result.reason).toBeUndefined();
|
|
150
|
+
expect(result.report).toBeDefined();
|
|
151
|
+
expect(result.report.estimated_cost_usd).toBe(0.001);
|
|
152
|
+
expect(result.report.spent_cost_usd_task).toBe(0);
|
|
153
|
+
expect(result.report.remaining_cost_usd_task).toBe(2.0);
|
|
154
|
+
});
|
|
155
|
+
it('should deny when task budget would be exceeded', () => {
|
|
156
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.005 }));
|
|
157
|
+
const toolCall = createTestToolCall();
|
|
158
|
+
// Record enough spend to nearly exhaust budget
|
|
159
|
+
manager.record(toolCall, 0.005);
|
|
160
|
+
const result = manager.check(toolCall);
|
|
161
|
+
expect(result.allowed).toBe(false);
|
|
162
|
+
expect(result.reason).toContain('Task budget exceeded');
|
|
163
|
+
});
|
|
164
|
+
it('should deny when step limit exceeded', () => {
|
|
165
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ max_steps_per_task: 2 }));
|
|
166
|
+
const toolCall = createTestToolCall();
|
|
167
|
+
// Record 2 steps
|
|
168
|
+
manager.record(toolCall, 0.0001);
|
|
169
|
+
manager.record(toolCall, 0.0001);
|
|
170
|
+
const result = manager.check(toolCall);
|
|
171
|
+
expect(result.allowed).toBe(false);
|
|
172
|
+
expect(result.reason).toContain('Step limit exceeded');
|
|
173
|
+
});
|
|
174
|
+
it('should deny when wall clock time exceeded', () => {
|
|
175
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ max_wall_clock_ms: 100 }));
|
|
176
|
+
// Create a tool call with a timestamp far in the past
|
|
177
|
+
const pastTimestamp = new Date(Date.now() - 200).toISOString();
|
|
178
|
+
const toolCall = createTestToolCall({ timestamp: pastTimestamp });
|
|
179
|
+
// Force creation of task state with the old timestamp
|
|
180
|
+
// by issuing a check (which calls getTaskState internally)
|
|
181
|
+
// The first check initialises state with toolCall.timestamp
|
|
182
|
+
const result = manager.check(toolCall);
|
|
183
|
+
expect(result.allowed).toBe(false);
|
|
184
|
+
expect(result.reason).toContain('Wall clock time exceeded');
|
|
185
|
+
});
|
|
186
|
+
it('should deny when user daily budget exceeded', () => {
|
|
187
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 0.003 }));
|
|
188
|
+
const toolCall = createTestToolCall();
|
|
189
|
+
// Record enough to hit user daily limit
|
|
190
|
+
manager.record(toolCall, 0.003);
|
|
191
|
+
const result = manager.check(toolCall);
|
|
192
|
+
expect(result.allowed).toBe(false);
|
|
193
|
+
expect(result.reason).toContain('User daily budget exceeded');
|
|
194
|
+
});
|
|
195
|
+
it('should deny when user monthly budget exceeded', () => {
|
|
196
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ user_monthly_budget_usd: 0.003 }));
|
|
197
|
+
const toolCall = createTestToolCall();
|
|
198
|
+
manager.record(toolCall, 0.003);
|
|
199
|
+
const result = manager.check(toolCall);
|
|
200
|
+
expect(result.allowed).toBe(false);
|
|
201
|
+
expect(result.reason).toContain('User monthly budget exceeded');
|
|
202
|
+
});
|
|
203
|
+
it('should deny when workspace daily budget exceeded', () => {
|
|
204
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ workspace_daily_budget_usd: 0.003 }));
|
|
205
|
+
const toolCall = createTestToolCall();
|
|
206
|
+
manager.record(toolCall, 0.003);
|
|
207
|
+
const result = manager.check(toolCall);
|
|
208
|
+
expect(result.allowed).toBe(false);
|
|
209
|
+
expect(result.reason).toContain('Workspace daily budget exceeded');
|
|
210
|
+
});
|
|
211
|
+
it('should deny when workspace monthly budget exceeded', () => {
|
|
212
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ workspace_monthly_budget_usd: 0.003 }));
|
|
213
|
+
const toolCall = createTestToolCall();
|
|
214
|
+
manager.record(toolCall, 0.003);
|
|
215
|
+
const result = manager.check(toolCall);
|
|
216
|
+
expect(result.allowed).toBe(false);
|
|
217
|
+
expect(result.reason).toContain('Workspace monthly budget exceeded');
|
|
218
|
+
});
|
|
219
|
+
it('should still allow when exactly at task budget minus estimated cost', () => {
|
|
220
|
+
// task_budget = 0.010, spent = 0.009, estimated = 0.001 => 0.009 + 0.001 = 0.010 => equal, not exceeded
|
|
221
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.010 }));
|
|
222
|
+
const toolCall = createTestToolCall();
|
|
223
|
+
manager.record(toolCall, 0.009);
|
|
224
|
+
const result = manager.check(toolCall);
|
|
225
|
+
expect(result.allowed).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
it('should deny when just over task budget', () => {
|
|
228
|
+
// task_budget = 0.010, spent = 0.0091, estimated = 0.001 => 0.0101 > 0.010
|
|
229
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.010 }));
|
|
230
|
+
const toolCall = createTestToolCall();
|
|
231
|
+
manager.record(toolCall, 0.0091);
|
|
232
|
+
const result = manager.check(toolCall);
|
|
233
|
+
expect(result.allowed).toBe(false);
|
|
234
|
+
expect(result.reason).toContain('Task budget exceeded');
|
|
235
|
+
});
|
|
236
|
+
it('should still allow when at exactly max_steps minus one', () => {
|
|
237
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ max_steps_per_task: 3 }));
|
|
238
|
+
const toolCall = createTestToolCall();
|
|
239
|
+
// Record 2 steps, so next would be step 3 (exactly max)
|
|
240
|
+
manager.record(toolCall, 0.0001);
|
|
241
|
+
manager.record(toolCall, 0.0001);
|
|
242
|
+
const result = manager.check(toolCall);
|
|
243
|
+
// steps = 2, steps + 1 = 3, max_steps = 3 => 3 > 3 is false => allowed
|
|
244
|
+
expect(result.allowed).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
// -----------------------------------------------------------------------
|
|
248
|
+
// Recording Costs
|
|
249
|
+
// -----------------------------------------------------------------------
|
|
250
|
+
describe('record', () => {
|
|
251
|
+
let manager;
|
|
252
|
+
beforeEach(() => {
|
|
253
|
+
manager = new manager_1.BudgetManager(defaultConfig());
|
|
254
|
+
});
|
|
255
|
+
it('should update task spent_usd after recording', () => {
|
|
256
|
+
const toolCall = createTestToolCall();
|
|
257
|
+
manager.record(toolCall, 0.005);
|
|
258
|
+
const report = manager.getReport(toolCall, 0);
|
|
259
|
+
expect(report.spent_cost_usd_task).toBe(0.005);
|
|
260
|
+
});
|
|
261
|
+
it('should update task steps after recording', () => {
|
|
262
|
+
const toolCall = createTestToolCall();
|
|
263
|
+
manager.record(toolCall, 0.001);
|
|
264
|
+
manager.record(toolCall, 0.001);
|
|
265
|
+
// After 2 records, spent should be 0.002 and remaining = 2.0 - 0.002 = 1.998
|
|
266
|
+
const report = manager.getReport(toolCall, 0);
|
|
267
|
+
expect(report.spent_cost_usd_task).toBeCloseTo(0.002, 6);
|
|
268
|
+
expect(report.remaining_cost_usd_task).toBeCloseTo(1.998, 6);
|
|
269
|
+
});
|
|
270
|
+
it('should update user daily spend', () => {
|
|
271
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 1.0 }));
|
|
272
|
+
const toolCall = createTestToolCall();
|
|
273
|
+
manager.record(toolCall, 0.5);
|
|
274
|
+
// After recording 0.5, a subsequent check should show 0.5 spent for user daily
|
|
275
|
+
// We can verify indirectly: record more, then check if the total triggers denial
|
|
276
|
+
manager.record(toolCall, 0.5);
|
|
277
|
+
const result = manager.check(toolCall);
|
|
278
|
+
// user daily spend now 1.0 + estimated 0.001 > 1.0 => denied
|
|
279
|
+
expect(result.allowed).toBe(false);
|
|
280
|
+
expect(result.reason).toContain('User daily budget exceeded');
|
|
281
|
+
});
|
|
282
|
+
it('should update workspace daily spend', () => {
|
|
283
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ workspace_daily_budget_usd: 0.01 }));
|
|
284
|
+
const toolCall = createTestToolCall();
|
|
285
|
+
manager.record(toolCall, 0.01);
|
|
286
|
+
const result = manager.check(toolCall);
|
|
287
|
+
expect(result.allowed).toBe(false);
|
|
288
|
+
expect(result.reason).toContain('Workspace daily budget exceeded');
|
|
289
|
+
});
|
|
290
|
+
it('should update user monthly spend', () => {
|
|
291
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ user_monthly_budget_usd: 0.01 }));
|
|
292
|
+
const toolCall = createTestToolCall();
|
|
293
|
+
manager.record(toolCall, 0.01);
|
|
294
|
+
const result = manager.check(toolCall);
|
|
295
|
+
expect(result.allowed).toBe(false);
|
|
296
|
+
expect(result.reason).toContain('User monthly budget exceeded');
|
|
297
|
+
});
|
|
298
|
+
it('should update workspace monthly spend', () => {
|
|
299
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ workspace_monthly_budget_usd: 0.01 }));
|
|
300
|
+
const toolCall = createTestToolCall();
|
|
301
|
+
manager.record(toolCall, 0.01);
|
|
302
|
+
const result = manager.check(toolCall);
|
|
303
|
+
expect(result.allowed).toBe(false);
|
|
304
|
+
expect(result.reason).toContain('Workspace monthly budget exceeded');
|
|
305
|
+
});
|
|
306
|
+
it('should accumulate costs across multiple records', () => {
|
|
307
|
+
const toolCall = createTestToolCall();
|
|
308
|
+
manager.record(toolCall, 0.001);
|
|
309
|
+
manager.record(toolCall, 0.002);
|
|
310
|
+
manager.record(toolCall, 0.003);
|
|
311
|
+
const report = manager.getReport(toolCall, 0);
|
|
312
|
+
expect(report.spent_cost_usd_task).toBeCloseTo(0.006, 6);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
// -----------------------------------------------------------------------
|
|
316
|
+
// Retry Tracking
|
|
317
|
+
// -----------------------------------------------------------------------
|
|
318
|
+
describe('recordRetry', () => {
|
|
319
|
+
it('should allow retries within limit', () => {
|
|
320
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ max_retries_per_call: 3 }));
|
|
321
|
+
const r1 = manager.recordRetry('tc-001');
|
|
322
|
+
expect(r1.allowed).toBe(true);
|
|
323
|
+
expect(r1.count).toBe(1);
|
|
324
|
+
const r2 = manager.recordRetry('tc-001');
|
|
325
|
+
expect(r2.allowed).toBe(true);
|
|
326
|
+
expect(r2.count).toBe(2);
|
|
327
|
+
const r3 = manager.recordRetry('tc-001');
|
|
328
|
+
expect(r3.allowed).toBe(true);
|
|
329
|
+
expect(r3.count).toBe(3);
|
|
330
|
+
});
|
|
331
|
+
it('should deny retries when limit exceeded', () => {
|
|
332
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ max_retries_per_call: 2 }));
|
|
333
|
+
manager.recordRetry('tc-001');
|
|
334
|
+
manager.recordRetry('tc-001');
|
|
335
|
+
const r3 = manager.recordRetry('tc-001');
|
|
336
|
+
expect(r3.allowed).toBe(false);
|
|
337
|
+
expect(r3.count).toBe(3);
|
|
338
|
+
});
|
|
339
|
+
it('should track retries independently per tool call', () => {
|
|
340
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ max_retries_per_call: 2 }));
|
|
341
|
+
manager.recordRetry('tc-001');
|
|
342
|
+
manager.recordRetry('tc-001');
|
|
343
|
+
// tc-002 has its own counter
|
|
344
|
+
const r1 = manager.recordRetry('tc-002');
|
|
345
|
+
expect(r1.allowed).toBe(true);
|
|
346
|
+
expect(r1.count).toBe(1);
|
|
347
|
+
// tc-001 is at its limit
|
|
348
|
+
const r3 = manager.recordRetry('tc-001');
|
|
349
|
+
expect(r3.allowed).toBe(false);
|
|
350
|
+
expect(r3.count).toBe(3);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
// -----------------------------------------------------------------------
|
|
354
|
+
// Budget Report
|
|
355
|
+
// -----------------------------------------------------------------------
|
|
356
|
+
describe('getReport', () => {
|
|
357
|
+
it('should calculate correct report for fresh task', () => {
|
|
358
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 5.0 }));
|
|
359
|
+
const toolCall = createTestToolCall();
|
|
360
|
+
const report = manager.getReport(toolCall, 0.01);
|
|
361
|
+
expect(report.estimated_cost_usd).toBe(0.01);
|
|
362
|
+
expect(report.spent_cost_usd_task).toBe(0);
|
|
363
|
+
expect(report.remaining_cost_usd_task).toBe(5.0);
|
|
364
|
+
});
|
|
365
|
+
it('should calculate correct remaining after spend', () => {
|
|
366
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 1.0 }));
|
|
367
|
+
const toolCall = createTestToolCall();
|
|
368
|
+
manager.record(toolCall, 0.3);
|
|
369
|
+
const report = manager.getReport(toolCall, 0.1);
|
|
370
|
+
expect(report.estimated_cost_usd).toBe(0.1);
|
|
371
|
+
expect(report.spent_cost_usd_task).toBeCloseTo(0.3, 6);
|
|
372
|
+
expect(report.remaining_cost_usd_task).toBeCloseTo(0.7, 6);
|
|
373
|
+
});
|
|
374
|
+
it('should clamp remaining to zero when overspent', () => {
|
|
375
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.001 }));
|
|
376
|
+
const toolCall = createTestToolCall();
|
|
377
|
+
manager.record(toolCall, 0.005);
|
|
378
|
+
const report = manager.getReport(toolCall, 0);
|
|
379
|
+
// remaining should be Math.max(0, 0.001 - 0.005) = 0
|
|
380
|
+
expect(report.remaining_cost_usd_task).toBe(0);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
// -----------------------------------------------------------------------
|
|
384
|
+
// Reset
|
|
385
|
+
// -----------------------------------------------------------------------
|
|
386
|
+
describe('reset', () => {
|
|
387
|
+
it('should clear all internal state', () => {
|
|
388
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 1.0 }));
|
|
389
|
+
const toolCall = createTestToolCall();
|
|
390
|
+
// Record some state
|
|
391
|
+
manager.record(toolCall, 0.5);
|
|
392
|
+
manager.recordRetry('tc-001');
|
|
393
|
+
// After reset, everything should be fresh
|
|
394
|
+
manager.reset();
|
|
395
|
+
const report = manager.getReport(toolCall, 0.001);
|
|
396
|
+
expect(report.spent_cost_usd_task).toBe(0);
|
|
397
|
+
expect(report.remaining_cost_usd_task).toBe(2.0);
|
|
398
|
+
// Retries should also be cleared
|
|
399
|
+
const retry = manager.recordRetry('tc-001');
|
|
400
|
+
expect(retry.count).toBe(1);
|
|
401
|
+
expect(retry.allowed).toBe(true);
|
|
402
|
+
// Budget check should pass again
|
|
403
|
+
const result = manager.check(toolCall);
|
|
404
|
+
expect(result.allowed).toBe(true);
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
// -----------------------------------------------------------------------
|
|
408
|
+
// Multiple Tasks Tracked Independently
|
|
409
|
+
// -----------------------------------------------------------------------
|
|
410
|
+
describe('multiple tasks', () => {
|
|
411
|
+
it('should track tasks independently', () => {
|
|
412
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.01 }));
|
|
413
|
+
const task1 = createTestToolCall({ task_id: 'task-A' });
|
|
414
|
+
const task2 = createTestToolCall({ task_id: 'task-B' });
|
|
415
|
+
manager.record(task1, 0.008);
|
|
416
|
+
manager.record(task2, 0.001);
|
|
417
|
+
// task-A is nearly exhausted
|
|
418
|
+
const report1 = manager.getReport(task1, 0);
|
|
419
|
+
expect(report1.spent_cost_usd_task).toBeCloseTo(0.008, 6);
|
|
420
|
+
expect(report1.remaining_cost_usd_task).toBeCloseTo(0.002, 6);
|
|
421
|
+
// task-B has plenty remaining
|
|
422
|
+
const report2 = manager.getReport(task2, 0);
|
|
423
|
+
expect(report2.spent_cost_usd_task).toBeCloseTo(0.001, 6);
|
|
424
|
+
expect(report2.remaining_cost_usd_task).toBeCloseTo(0.009, 6);
|
|
425
|
+
// task-A next call should be denied (0.008 + 0.001 estimated > 0.01? No, 0.009 <= 0.01)
|
|
426
|
+
const check1 = manager.check(task1);
|
|
427
|
+
expect(check1.allowed).toBe(true);
|
|
428
|
+
// Record more on task-A to push it over
|
|
429
|
+
manager.record(task1, 0.002);
|
|
430
|
+
const check1b = manager.check(task1);
|
|
431
|
+
expect(check1b.allowed).toBe(false);
|
|
432
|
+
expect(check1b.reason).toContain('Task budget exceeded');
|
|
433
|
+
// task-B should still be fine
|
|
434
|
+
const check2 = manager.check(task2);
|
|
435
|
+
expect(check2.allowed).toBe(true);
|
|
436
|
+
});
|
|
437
|
+
it('should share user spend across tasks', () => {
|
|
438
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ user_daily_budget_usd: 0.005, task_budget_usd: 10 }));
|
|
439
|
+
const task1 = createTestToolCall({ task_id: 'task-A', actor: { type: 'agent', id: 'agent-001' } });
|
|
440
|
+
const task2 = createTestToolCall({ task_id: 'task-B', actor: { type: 'agent', id: 'agent-001' } });
|
|
441
|
+
// Spend across two tasks under the same user
|
|
442
|
+
manager.record(task1, 0.003);
|
|
443
|
+
manager.record(task2, 0.002);
|
|
444
|
+
// Total user daily spend = 0.005, any further call should be denied
|
|
445
|
+
const check = manager.check(task2);
|
|
446
|
+
expect(check.allowed).toBe(false);
|
|
447
|
+
expect(check.reason).toContain('User daily budget exceeded');
|
|
448
|
+
});
|
|
449
|
+
it('should share workspace spend across different actors', () => {
|
|
450
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ workspace_daily_budget_usd: 0.005, task_budget_usd: 10 }));
|
|
451
|
+
const tc1 = createTestToolCall({
|
|
452
|
+
task_id: 'task-A',
|
|
453
|
+
workspace_id: 'ws-001',
|
|
454
|
+
actor: { type: 'agent', id: 'agent-001' },
|
|
455
|
+
});
|
|
456
|
+
const tc2 = createTestToolCall({
|
|
457
|
+
task_id: 'task-B',
|
|
458
|
+
workspace_id: 'ws-001',
|
|
459
|
+
actor: { type: 'agent', id: 'agent-002' },
|
|
460
|
+
});
|
|
461
|
+
manager.record(tc1, 0.003);
|
|
462
|
+
manager.record(tc2, 0.002);
|
|
463
|
+
// Both share workspace spend; total = 0.005
|
|
464
|
+
const check = manager.check(tc2);
|
|
465
|
+
expect(check.allowed).toBe(false);
|
|
466
|
+
expect(check.reason).toContain('Workspace daily budget exceeded');
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
// -----------------------------------------------------------------------
|
|
470
|
+
// Default Config Values
|
|
471
|
+
// -----------------------------------------------------------------------
|
|
472
|
+
describe('default config values', () => {
|
|
473
|
+
it('should apply default task_budget_usd when not provided', () => {
|
|
474
|
+
const manager = new manager_1.BudgetManager({});
|
|
475
|
+
const config = manager.getConfig();
|
|
476
|
+
expect(config.task_budget_usd).toBe(2.0);
|
|
477
|
+
});
|
|
478
|
+
it('should apply default max_steps_per_task when not provided', () => {
|
|
479
|
+
const manager = new manager_1.BudgetManager({});
|
|
480
|
+
const config = manager.getConfig();
|
|
481
|
+
expect(config.max_steps_per_task).toBe(50);
|
|
482
|
+
});
|
|
483
|
+
it('should apply default max_retries_per_call when not provided', () => {
|
|
484
|
+
const manager = new manager_1.BudgetManager({});
|
|
485
|
+
const config = manager.getConfig();
|
|
486
|
+
expect(config.max_retries_per_call).toBe(3);
|
|
487
|
+
});
|
|
488
|
+
it('should apply default max_wall_clock_ms when not provided', () => {
|
|
489
|
+
const manager = new manager_1.BudgetManager({});
|
|
490
|
+
const config = manager.getConfig();
|
|
491
|
+
expect(config.max_wall_clock_ms).toBe(300000);
|
|
492
|
+
});
|
|
493
|
+
it('should leave optional budgets undefined when not provided', () => {
|
|
494
|
+
const manager = new manager_1.BudgetManager({});
|
|
495
|
+
const config = manager.getConfig();
|
|
496
|
+
expect(config.user_daily_budget_usd).toBeUndefined();
|
|
497
|
+
expect(config.user_monthly_budget_usd).toBeUndefined();
|
|
498
|
+
expect(config.workspace_daily_budget_usd).toBeUndefined();
|
|
499
|
+
expect(config.workspace_monthly_budget_usd).toBeUndefined();
|
|
500
|
+
});
|
|
501
|
+
it('should override defaults when explicit values provided', () => {
|
|
502
|
+
const manager = new manager_1.BudgetManager({
|
|
503
|
+
task_budget_usd: 10,
|
|
504
|
+
max_steps_per_task: 100,
|
|
505
|
+
max_retries_per_call: 5,
|
|
506
|
+
max_wall_clock_ms: 600000,
|
|
507
|
+
});
|
|
508
|
+
const config = manager.getConfig();
|
|
509
|
+
expect(config.task_budget_usd).toBe(10);
|
|
510
|
+
expect(config.max_steps_per_task).toBe(100);
|
|
511
|
+
expect(config.max_retries_per_call).toBe(5);
|
|
512
|
+
expect(config.max_wall_clock_ms).toBe(600000);
|
|
513
|
+
});
|
|
514
|
+
it('should return a copy of config (not the original)', () => {
|
|
515
|
+
const manager = new manager_1.BudgetManager({});
|
|
516
|
+
const config1 = manager.getConfig();
|
|
517
|
+
const config2 = manager.getConfig();
|
|
518
|
+
expect(config1).toEqual(config2);
|
|
519
|
+
expect(config1).not.toBe(config2);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
// -----------------------------------------------------------------------
|
|
523
|
+
// Budget check report structure
|
|
524
|
+
// -----------------------------------------------------------------------
|
|
525
|
+
describe('check report structure', () => {
|
|
526
|
+
it('should include all required fields in report when allowed', () => {
|
|
527
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
528
|
+
const toolCall = createTestToolCall();
|
|
529
|
+
const result = manager.check(toolCall);
|
|
530
|
+
expect(result.report).toHaveProperty('estimated_cost_usd');
|
|
531
|
+
expect(result.report).toHaveProperty('spent_cost_usd_task');
|
|
532
|
+
expect(result.report).toHaveProperty('remaining_cost_usd_task');
|
|
533
|
+
expect(typeof result.report.estimated_cost_usd).toBe('number');
|
|
534
|
+
expect(typeof result.report.spent_cost_usd_task).toBe('number');
|
|
535
|
+
expect(typeof result.report.remaining_cost_usd_task).toBe('number');
|
|
536
|
+
});
|
|
537
|
+
it('should include report even when denied', () => {
|
|
538
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.0001 }));
|
|
539
|
+
const toolCall = createTestToolCall();
|
|
540
|
+
manager.record(toolCall, 0.001);
|
|
541
|
+
const result = manager.check(toolCall);
|
|
542
|
+
expect(result.allowed).toBe(false);
|
|
543
|
+
expect(result.report).toBeDefined();
|
|
544
|
+
expect(result.report.estimated_cost_usd).toBeGreaterThan(0);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
// -----------------------------------------------------------------------
|
|
548
|
+
// Configurable Cost Table
|
|
549
|
+
// -----------------------------------------------------------------------
|
|
550
|
+
describe('configurable cost table', () => {
|
|
551
|
+
it('should use default cost table when no override provided', () => {
|
|
552
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
553
|
+
const costTable = manager.getCostTable();
|
|
554
|
+
expect(costTable['http.request']).toBe(0.001);
|
|
555
|
+
expect(costTable['default']).toBe(0.001);
|
|
556
|
+
});
|
|
557
|
+
it('should merge custom cost_table over defaults', () => {
|
|
558
|
+
const manager = new manager_1.BudgetManager(defaultConfig({
|
|
559
|
+
cost_table: {
|
|
560
|
+
'http.request': 0.01,
|
|
561
|
+
'llm.completion': 0.05,
|
|
562
|
+
},
|
|
563
|
+
}));
|
|
564
|
+
const costTable = manager.getCostTable();
|
|
565
|
+
// Custom overrides applied
|
|
566
|
+
expect(costTable['http.request']).toBe(0.01);
|
|
567
|
+
expect(costTable['llm.completion']).toBe(0.05);
|
|
568
|
+
// Defaults preserved
|
|
569
|
+
expect(costTable['slack.post']).toBe(0.003);
|
|
570
|
+
expect(costTable['default']).toBe(0.001);
|
|
571
|
+
});
|
|
572
|
+
it('should use custom cost_table values for estimateCost', () => {
|
|
573
|
+
const manager = new manager_1.BudgetManager(defaultConfig({
|
|
574
|
+
cost_table: { 'http.request': 0.05 },
|
|
575
|
+
}));
|
|
576
|
+
const toolCall = createTestToolCall({ tool: { name: 'http.request', capability: 'read' } });
|
|
577
|
+
const estimate = manager.estimateCost(toolCall);
|
|
578
|
+
expect(estimate.estimated_cost_usd).toBe(0.05);
|
|
579
|
+
});
|
|
580
|
+
it('should allow overriding the default cost', () => {
|
|
581
|
+
const manager = new manager_1.BudgetManager(defaultConfig({
|
|
582
|
+
cost_table: { 'default': 0.01 },
|
|
583
|
+
}));
|
|
584
|
+
const toolCall = createTestToolCall({ tool: { name: 'unknown.tool', capability: 'read' } });
|
|
585
|
+
const estimate = manager.estimateCost(toolCall);
|
|
586
|
+
expect(estimate.estimated_cost_usd).toBe(0.01);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
// -----------------------------------------------------------------------
|
|
590
|
+
// CostRecord recording
|
|
591
|
+
// -----------------------------------------------------------------------
|
|
592
|
+
describe('record with CostRecord', () => {
|
|
593
|
+
it('should accept a CostRecord object', () => {
|
|
594
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
595
|
+
const toolCall = createTestToolCall();
|
|
596
|
+
manager.record(toolCall, {
|
|
597
|
+
estimated_cost_usd: 0.001,
|
|
598
|
+
actual_cost_usd: 0.015,
|
|
599
|
+
usage: { input_tokens: 500, output_tokens: 200, total_tokens: 700, source: 'headers' },
|
|
600
|
+
});
|
|
601
|
+
const report = manager.getReport(toolCall, 0);
|
|
602
|
+
// Should have used actual_cost_usd (0.015) for spend tracking
|
|
603
|
+
expect(report.spent_cost_usd_task).toBe(0.015);
|
|
604
|
+
});
|
|
605
|
+
it('should fall back to estimated_cost_usd when actual is not provided', () => {
|
|
606
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
607
|
+
const toolCall = createTestToolCall();
|
|
608
|
+
manager.record(toolCall, {
|
|
609
|
+
estimated_cost_usd: 0.005,
|
|
610
|
+
});
|
|
611
|
+
const report = manager.getReport(toolCall, 0);
|
|
612
|
+
expect(report.spent_cost_usd_task).toBe(0.005);
|
|
613
|
+
});
|
|
614
|
+
it('should store CostRecord for retrieval', () => {
|
|
615
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
616
|
+
const toolCall = createTestToolCall();
|
|
617
|
+
const costRecord = {
|
|
618
|
+
estimated_cost_usd: 0.001,
|
|
619
|
+
actual_cost_usd: 0.015,
|
|
620
|
+
usage: { input_tokens: 500, output_tokens: 200 },
|
|
621
|
+
};
|
|
622
|
+
manager.record(toolCall, costRecord);
|
|
623
|
+
const retrieved = manager.getCostRecord(toolCall.tool_call_id);
|
|
624
|
+
expect(retrieved).toEqual(costRecord);
|
|
625
|
+
});
|
|
626
|
+
it('should return undefined for unknown tool_call_id', () => {
|
|
627
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
628
|
+
expect(manager.getCostRecord('nonexistent')).toBeUndefined();
|
|
629
|
+
});
|
|
630
|
+
it('should still accept plain number for backward compatibility', () => {
|
|
631
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
632
|
+
const toolCall = createTestToolCall();
|
|
633
|
+
manager.record(toolCall, 0.003);
|
|
634
|
+
const report = manager.getReport(toolCall, 0);
|
|
635
|
+
expect(report.spent_cost_usd_task).toBe(0.003);
|
|
636
|
+
// No CostRecord stored when using plain number
|
|
637
|
+
expect(manager.getCostRecord(toolCall.tool_call_id)).toBeUndefined();
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
// -----------------------------------------------------------------------
|
|
641
|
+
// getReportWithActual
|
|
642
|
+
// -----------------------------------------------------------------------
|
|
643
|
+
describe('getReportWithActual', () => {
|
|
644
|
+
it('should include actual_cost_usd and usage in report', () => {
|
|
645
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
646
|
+
const toolCall = createTestToolCall();
|
|
647
|
+
const usage = { input_tokens: 500, output_tokens: 200, total_tokens: 700, source: 'headers' };
|
|
648
|
+
const report = manager.getReportWithActual(toolCall, 0.001, 0.015, usage);
|
|
649
|
+
expect(report.estimated_cost_usd).toBe(0.001);
|
|
650
|
+
expect(report.actual_cost_usd).toBe(0.015);
|
|
651
|
+
expect(report.usage).toEqual(usage);
|
|
652
|
+
expect(report.spent_cost_usd_task).toBe(0);
|
|
653
|
+
expect(report.remaining_cost_usd_task).toBe(2.0);
|
|
654
|
+
});
|
|
655
|
+
it('should work without actual cost or usage', () => {
|
|
656
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
657
|
+
const toolCall = createTestToolCall();
|
|
658
|
+
const report = manager.getReportWithActual(toolCall, 0.001);
|
|
659
|
+
expect(report.estimated_cost_usd).toBe(0.001);
|
|
660
|
+
expect(report.actual_cost_usd).toBeUndefined();
|
|
661
|
+
expect(report.usage).toBeUndefined();
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
// -----------------------------------------------------------------------
|
|
665
|
+
// Token pricing config
|
|
666
|
+
// -----------------------------------------------------------------------
|
|
667
|
+
describe('token pricing config', () => {
|
|
668
|
+
it('should store token_pricing in config', () => {
|
|
669
|
+
const manager = new manager_1.BudgetManager(defaultConfig({
|
|
670
|
+
token_pricing: {
|
|
671
|
+
'gpt-4': { input_per_token: 0.00003, output_per_token: 0.00006 },
|
|
672
|
+
},
|
|
673
|
+
}));
|
|
674
|
+
const config = manager.getConfig();
|
|
675
|
+
expect(config.token_pricing).toBeDefined();
|
|
676
|
+
expect(config.token_pricing['gpt-4']).toEqual({
|
|
677
|
+
input_per_token: 0.00003,
|
|
678
|
+
output_per_token: 0.00006,
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
// -----------------------------------------------------------------------
|
|
683
|
+
// Budget Reservation (S5: Atomic check + reserve)
|
|
684
|
+
// -----------------------------------------------------------------------
|
|
685
|
+
describe('reserveAndCheck', () => {
|
|
686
|
+
it('should atomically check and reserve budget', async () => {
|
|
687
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
688
|
+
const toolCall = createTestToolCall();
|
|
689
|
+
const result = await manager.reserveAndCheck(toolCall);
|
|
690
|
+
expect(result.allowed).toBe(true);
|
|
691
|
+
expect(result.reservationKey).toBeDefined();
|
|
692
|
+
expect(result.reservationKey).toContain('reservation:');
|
|
693
|
+
// The reserved amount should be reflected in the spent budget
|
|
694
|
+
const report = manager.getReport(toolCall, 0);
|
|
695
|
+
expect(report.spent_cost_usd_task).toBe(0.001); // estimated cost reserved
|
|
696
|
+
});
|
|
697
|
+
it('should deny when reservation would exceed task budget', async () => {
|
|
698
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.001 }));
|
|
699
|
+
const toolCall = createTestToolCall();
|
|
700
|
+
// First reservation takes the whole budget
|
|
701
|
+
const first = await manager.reserveAndCheck(toolCall);
|
|
702
|
+
expect(first.allowed).toBe(true);
|
|
703
|
+
// Second reservation should be denied (budget exhausted)
|
|
704
|
+
const second = await manager.reserveAndCheck(createTestToolCall({ tool_call_id: 'tc-002' }));
|
|
705
|
+
expect(second.allowed).toBe(false);
|
|
706
|
+
expect(second.reason).toContain('Task budget exceeded');
|
|
707
|
+
});
|
|
708
|
+
it('should prevent concurrent requests from both passing budget check', async () => {
|
|
709
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.0015 }));
|
|
710
|
+
const tc1 = createTestToolCall({ tool_call_id: 'tc-001' });
|
|
711
|
+
const tc2 = createTestToolCall({ tool_call_id: 'tc-002' });
|
|
712
|
+
// First reservation passes (0.001 estimated)
|
|
713
|
+
const r1 = await manager.reserveAndCheck(tc1);
|
|
714
|
+
expect(r1.allowed).toBe(true);
|
|
715
|
+
// Second reservation should fail (0.001 reserved + 0.001 estimated > 0.0015)
|
|
716
|
+
const r2 = await manager.reserveAndCheck(tc2);
|
|
717
|
+
expect(r2.allowed).toBe(false);
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
describe('commitReservation', () => {
|
|
721
|
+
it('should adjust budget when actual cost differs from estimate', async () => {
|
|
722
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 1.0 }));
|
|
723
|
+
const toolCall = createTestToolCall();
|
|
724
|
+
const result = await manager.reserveAndCheck(toolCall);
|
|
725
|
+
expect(result.allowed).toBe(true);
|
|
726
|
+
// Reserved 0.001 (estimated), but actual cost is 0.0005
|
|
727
|
+
manager.commitReservation(result.reservationKey, 0.0005);
|
|
728
|
+
// Budget should reflect actual cost (0.0005), not estimated (0.001)
|
|
729
|
+
const report = manager.getReport(toolCall, 0);
|
|
730
|
+
expect(report.spent_cost_usd_task).toBeCloseTo(0.0005, 6);
|
|
731
|
+
expect(report.remaining_cost_usd_task).toBeCloseTo(0.9995, 6);
|
|
732
|
+
});
|
|
733
|
+
it('should be a no-op for unknown reservation key', () => {
|
|
734
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
735
|
+
// Should not throw
|
|
736
|
+
manager.commitReservation('reservation:unknown', 0.001);
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
describe('releaseReservation', () => {
|
|
740
|
+
it('should fully release reserved budget', async () => {
|
|
741
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
742
|
+
const toolCall = createTestToolCall();
|
|
743
|
+
const result = await manager.reserveAndCheck(toolCall);
|
|
744
|
+
expect(result.allowed).toBe(true);
|
|
745
|
+
// Verify budget was reserved
|
|
746
|
+
const beforeRelease = manager.getReport(toolCall, 0);
|
|
747
|
+
expect(beforeRelease.spent_cost_usd_task).toBe(0.001);
|
|
748
|
+
// Release the reservation
|
|
749
|
+
manager.releaseReservation(result.reservationKey);
|
|
750
|
+
// Budget should be fully restored
|
|
751
|
+
const afterRelease = manager.getReport(toolCall, 0);
|
|
752
|
+
expect(afterRelease.spent_cost_usd_task).toBe(0);
|
|
753
|
+
expect(afterRelease.remaining_cost_usd_task).toBe(2.0);
|
|
754
|
+
});
|
|
755
|
+
it('should be a no-op for unknown reservation key', () => {
|
|
756
|
+
const manager = new manager_1.BudgetManager(defaultConfig());
|
|
757
|
+
// Should not throw
|
|
758
|
+
manager.releaseReservation('reservation:unknown');
|
|
759
|
+
});
|
|
760
|
+
it('should restore budget for subsequent reservations after release', async () => {
|
|
761
|
+
const manager = new manager_1.BudgetManager(defaultConfig({ task_budget_usd: 0.0015 }));
|
|
762
|
+
const tc1 = createTestToolCall({ tool_call_id: 'tc-001' });
|
|
763
|
+
const tc2 = createTestToolCall({ tool_call_id: 'tc-002' });
|
|
764
|
+
// First reservation
|
|
765
|
+
const r1 = await manager.reserveAndCheck(tc1);
|
|
766
|
+
expect(r1.allowed).toBe(true);
|
|
767
|
+
// Second would fail due to budget
|
|
768
|
+
const r2Fail = await manager.reserveAndCheck(tc2);
|
|
769
|
+
expect(r2Fail.allowed).toBe(false);
|
|
770
|
+
// Release first reservation
|
|
771
|
+
manager.releaseReservation(r1.reservationKey);
|
|
772
|
+
// Now second should succeed
|
|
773
|
+
const r2Pass = await manager.reserveAndCheck(tc2);
|
|
774
|
+
expect(r2Pass.allowed).toBe(true);
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
//# sourceMappingURL=budget-manager.test.js.map
|