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,964 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Gateway = void 0;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const api_1 = require("@opentelemetry/api");
|
|
39
|
+
const engine_1 = require("../policy/engine");
|
|
40
|
+
const opa_engine_1 = require("../policy/opa-engine");
|
|
41
|
+
const scanner_1 = require("../dlp/scanner");
|
|
42
|
+
const composite_scanner_1 = require("../dlp/composite-scanner");
|
|
43
|
+
const prompt_injection_backend_1 = require("../dlp/prompt-injection-backend");
|
|
44
|
+
const trufflehog_backend_1 = require("../dlp/trufflehog-backend");
|
|
45
|
+
const manager_1 = require("../budget/manager");
|
|
46
|
+
const usage_extractor_1 = require("../budget/usage-extractor");
|
|
47
|
+
const logger_1 = require("../audit/logger");
|
|
48
|
+
const http_executor_1 = require("../executor/http-executor");
|
|
49
|
+
const registry_1 = require("../executor/registry");
|
|
50
|
+
const manager_2 = require("../approval/manager");
|
|
51
|
+
const limiter_1 = require("../ratelimit/limiter");
|
|
52
|
+
const memory_1 = require("../storage/memory");
|
|
53
|
+
const anomaly_1 = require("../anomaly");
|
|
54
|
+
const logger_2 = require("./logger");
|
|
55
|
+
/** Compute a short body hash for idempotency cache keys (A2). */
|
|
56
|
+
function computeBodyHash(toolCall) {
|
|
57
|
+
const payload = toolCall.tool.name + JSON.stringify(toolCall.args);
|
|
58
|
+
return crypto.createHash('sha256').update(payload).digest('hex').substring(0, 16);
|
|
59
|
+
}
|
|
60
|
+
const DEFAULT_RATE_LIMIT = {
|
|
61
|
+
enabled: false,
|
|
62
|
+
actor_max_per_window: 100,
|
|
63
|
+
workspace_max_per_window: 500,
|
|
64
|
+
window_ms: 60000,
|
|
65
|
+
};
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Span helpers — no-op when otel is undefined
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
function childSpan(otel, name, fn) {
|
|
70
|
+
if (!otel)
|
|
71
|
+
return fn();
|
|
72
|
+
return otel.startActiveSpan(name, (s) => {
|
|
73
|
+
try {
|
|
74
|
+
const result = fn();
|
|
75
|
+
s.end();
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
s.setStatus({ code: api_1.SpanStatusCode.ERROR, message: String(e) });
|
|
80
|
+
s.recordException(e);
|
|
81
|
+
s.end();
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function asyncChildSpan(otel, name, fn) {
|
|
87
|
+
if (!otel)
|
|
88
|
+
return fn();
|
|
89
|
+
return otel.startActiveSpan(name, async (s) => {
|
|
90
|
+
try {
|
|
91
|
+
const result = await fn();
|
|
92
|
+
s.end();
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
s.setStatus({ code: api_1.SpanStatusCode.ERROR, message: String(e) });
|
|
97
|
+
s.recordException(e);
|
|
98
|
+
s.end();
|
|
99
|
+
throw e;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
class Gateway {
|
|
104
|
+
constructor(config, metrics, tracer) {
|
|
105
|
+
/**
|
|
106
|
+
* Tracks tool_call_ids currently being processed to prevent TOCTOU races.
|
|
107
|
+
* Maps cacheKey -> InFlightEntry so duplicate arrivals can await
|
|
108
|
+
* the in-flight result instead of executing a second time.
|
|
109
|
+
*/
|
|
110
|
+
this.inFlightCalls = new Map();
|
|
111
|
+
this._shuttingDown = false;
|
|
112
|
+
this.config = config;
|
|
113
|
+
this.metrics = metrics;
|
|
114
|
+
this.tracer = tracer;
|
|
115
|
+
this.policyEngine = new engine_1.PolicyEngine(config.policy.pack_path, config.policy.default_effect);
|
|
116
|
+
// Build DLP pipeline: use CompositeDLPScanner when additional backends are configured
|
|
117
|
+
const dlpBackends = [];
|
|
118
|
+
if (config.dlp.prompt_injection_detection !== false) {
|
|
119
|
+
dlpBackends.push(new prompt_injection_backend_1.PromptInjectionBackend({
|
|
120
|
+
scan_output: config.dlp.scan_output,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
if (config.dlp.trufflehog?.enabled) {
|
|
124
|
+
dlpBackends.push(new trufflehog_backend_1.TruffleHogBackend({
|
|
125
|
+
binaryPath: config.dlp.trufflehog.binary_path,
|
|
126
|
+
timeout: config.dlp.trufflehog.timeout_ms,
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
if (dlpBackends.length > 0) {
|
|
130
|
+
this.dlpScanner = new composite_scanner_1.CompositeDLPScanner(config.dlp, dlpBackends);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
this.dlpScanner = new scanner_1.DLPScanner(config.dlp);
|
|
134
|
+
}
|
|
135
|
+
this.budgetManager = new manager_1.BudgetManager(config.budget);
|
|
136
|
+
this.auditLogger = new logger_1.AuditLogger(config.audit);
|
|
137
|
+
this.approvalManager = new manager_2.ApprovalManager(config.approval);
|
|
138
|
+
this.rateLimiter = new limiter_1.RateLimiter(config.rate_limit || DEFAULT_RATE_LIMIT);
|
|
139
|
+
this.idempotencyStore = new memory_1.InMemoryIdempotencyStore();
|
|
140
|
+
// Set up anomaly detector if enabled
|
|
141
|
+
if (config.anomaly?.enabled) {
|
|
142
|
+
this.anomalyDetector = new anomaly_1.AnomalyDetector(config.anomaly);
|
|
143
|
+
}
|
|
144
|
+
// Set up OPA engine if enabled
|
|
145
|
+
if (config.policy.opa?.enabled) {
|
|
146
|
+
this.opaEngine = new opa_engine_1.OPAEngine(config.policy.opa);
|
|
147
|
+
}
|
|
148
|
+
this.usageExtractor = new usage_extractor_1.UsageExtractor(config.budget.token_pricing);
|
|
149
|
+
// Set up executor registry with HTTP as default + catch-all fallback
|
|
150
|
+
this.executorRegistry = new registry_1.ExecutorRegistry();
|
|
151
|
+
this.httpExecutor = new http_executor_1.HttpExecutor(config.executor);
|
|
152
|
+
this.executorRegistry.register('http.*', this.httpExecutor);
|
|
153
|
+
this.executorRegistry.register('*', this.httpExecutor); // fallback
|
|
154
|
+
// A1: Periodic cleanup of stale inFlightCalls entries (every 60s, remove entries older than 5min)
|
|
155
|
+
this.inFlightCleanupInterval = setInterval(() => {
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
const MAX_AGE_MS = 5 * 60 * 1000;
|
|
158
|
+
for (const [key, entry] of this.inFlightCalls) {
|
|
159
|
+
if (now - entry.createdAt > MAX_AGE_MS) {
|
|
160
|
+
clearTimeout(entry.timeout);
|
|
161
|
+
this.inFlightCalls.delete(key);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}, 60000);
|
|
165
|
+
// Don't let the interval prevent process exit
|
|
166
|
+
if (this.inFlightCleanupInterval.unref) {
|
|
167
|
+
this.inFlightCleanupInterval.unref();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Register a custom executor for a tool name pattern (prepends to take priority over catch-all) */
|
|
171
|
+
registerExecutor(pattern, executor) {
|
|
172
|
+
this.executorRegistry.register(pattern, executor, true);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Run the pre-execution pipeline: rate limit, anomaly, policy, DLP args, budget.
|
|
176
|
+
* Does NOT handle idempotency or in-flight tracking — the caller manages those.
|
|
177
|
+
* Returns { allowed: true, ... } with pipeline state on success, or
|
|
178
|
+
* { allowed: false, result } with a ToolResult to return to the client.
|
|
179
|
+
*/
|
|
180
|
+
async preExecute(toolCall, otel, requestingApiKeyId) {
|
|
181
|
+
const startTime = Date.now();
|
|
182
|
+
const stepTimings = {};
|
|
183
|
+
let stepStart;
|
|
184
|
+
// A3: Capture a local reference to the policy engine so the request uses
|
|
185
|
+
// a consistent snapshot even if reloadPolicy() swaps the engine mid-request.
|
|
186
|
+
const policyEngine = this.policyEngine;
|
|
187
|
+
if (!toolCall.timestamp) {
|
|
188
|
+
toolCall.timestamp = new Date().toISOString();
|
|
189
|
+
}
|
|
190
|
+
logger_2.log.pipelineStart(toolCall.tool_call_id, toolCall.tool.name);
|
|
191
|
+
// Log receipt
|
|
192
|
+
this.auditLogger.logToolCallReceived(toolCall);
|
|
193
|
+
// Rate limit check (with optional per-workspace overrides)
|
|
194
|
+
stepStart = Date.now();
|
|
195
|
+
const wsRateLimitOverrides = toolCall.workspace_id
|
|
196
|
+
? this.getWorkspaceRateLimitConfig(toolCall.workspace_id)
|
|
197
|
+
: undefined;
|
|
198
|
+
const rateLimitResult = childSpan(otel, 'gateway.rate_limit', () => {
|
|
199
|
+
return this.rateLimiter.check(toolCall, wsRateLimitOverrides);
|
|
200
|
+
});
|
|
201
|
+
stepTimings.rate_limit = Date.now() - stepStart;
|
|
202
|
+
if (!rateLimitResult.allowed) {
|
|
203
|
+
logger_2.log.rateLimit(false, rateLimitResult.current, rateLimitResult.limit, rateLimitResult.blocked_by);
|
|
204
|
+
this.metrics?.recordRateLimitBlock(rateLimitResult.blocked_by || 'unknown');
|
|
205
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
206
|
+
this.metrics?.recordRequest('blocked', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
207
|
+
const result = this.buildResult(toolCall, 'blocked', {
|
|
208
|
+
decision: 'deny',
|
|
209
|
+
rule_id: 'rate_limit',
|
|
210
|
+
rule_name: 'Rate limit',
|
|
211
|
+
reasons: [`Rate limit exceeded (${rateLimitResult.blocked_by}): ${rateLimitResult.current}/${rateLimitResult.limit} requests in window`],
|
|
212
|
+
}, startTime, undefined, `Rate limit exceeded by ${rateLimitResult.blocked_by}: ${rateLimitResult.current}/${rateLimitResult.limit} requests. Resets at ${rateLimitResult.reset_at}`);
|
|
213
|
+
logger_2.log.pipelineEnd('blocked', Date.now() - startTime);
|
|
214
|
+
return { allowed: false, result, stepTimings, startTime };
|
|
215
|
+
}
|
|
216
|
+
logger_2.log.rateLimit(true, rateLimitResult.current, rateLimitResult.limit);
|
|
217
|
+
// Anomaly detection
|
|
218
|
+
stepStart = Date.now();
|
|
219
|
+
const anomalyAlerts = childSpan(otel, 'gateway.anomaly_detection', () => {
|
|
220
|
+
return this.anomalyDetector?.analyze(toolCall) || [];
|
|
221
|
+
});
|
|
222
|
+
stepTimings.anomaly = Date.now() - stepStart;
|
|
223
|
+
if (anomalyAlerts.length > 0 && this.config.anomaly?.action === 'block') {
|
|
224
|
+
const highSeverity = anomalyAlerts.filter(a => a.severity === 'high');
|
|
225
|
+
if (highSeverity.length > 0) {
|
|
226
|
+
logger_2.log.anomaly(anomalyAlerts.length, true);
|
|
227
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
228
|
+
this.metrics?.recordRequest('blocked', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
229
|
+
const result = this.buildResult(toolCall, 'blocked', {
|
|
230
|
+
decision: 'deny',
|
|
231
|
+
rule_id: 'anomaly_detection',
|
|
232
|
+
rule_name: 'Anomaly detection',
|
|
233
|
+
reasons: highSeverity.map(a => `${a.anomaly_type}: ${a.metric} z-score=${a.z_score.toFixed(2)}`),
|
|
234
|
+
}, startTime, undefined, 'Blocked by anomaly detection');
|
|
235
|
+
logger_2.log.pipelineEnd('blocked', Date.now() - startTime);
|
|
236
|
+
return { allowed: false, result, stepTimings, startTime };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
logger_2.log.anomaly(anomalyAlerts.length, false);
|
|
240
|
+
// DLP scan on full toolCall (args + context + actor fields)
|
|
241
|
+
// Runs BEFORE policy evaluation so secrets are always detected regardless of policy outcome
|
|
242
|
+
stepStart = Date.now();
|
|
243
|
+
const argsDlp = childSpan(otel, 'gateway.dlp_scan_args', () => {
|
|
244
|
+
if (!this.config.dlp.scan_args) {
|
|
245
|
+
return { detected: [], redactions: [], severity: 'low' };
|
|
246
|
+
}
|
|
247
|
+
return this.dlpScanner.scan(toolCall, '');
|
|
248
|
+
});
|
|
249
|
+
stepTimings.dlp_args = Date.now() - stepStart;
|
|
250
|
+
this.auditLogger.logDLPScanned(toolCall, argsDlp.detected, argsDlp.severity, argsDlp.redactions.length, argsDlp.redactions);
|
|
251
|
+
logger_2.log.dlp('args', argsDlp.detected, argsDlp.severity, argsDlp.redactions.length);
|
|
252
|
+
if (argsDlp.detected.length > 0) {
|
|
253
|
+
for (const detectionType of argsDlp.detected) {
|
|
254
|
+
this.metrics?.recordDLPDetection(detectionType, argsDlp.severity);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (argsDlp.severity === 'high') {
|
|
258
|
+
this.auditLogger.logIncident(toolCall, 'high', 'dlp_secret_detected', `High severity DLP detection in args: ${argsDlp.detected.join(', ')}`, 'Review and rotate any exposed credentials');
|
|
259
|
+
}
|
|
260
|
+
// Prompt injection blocking check (before policy, so it always runs)
|
|
261
|
+
const piAction = this.config.dlp.prompt_injection_action || 'log';
|
|
262
|
+
if (piAction === 'block' && argsDlp.detected.length > 0) {
|
|
263
|
+
const piDetections = argsDlp.detected.filter((d) => d.startsWith('prompt_injection_'));
|
|
264
|
+
if (piDetections.length > 0) {
|
|
265
|
+
const threshold = this.config.dlp.prompt_injection_block_threshold || 'high';
|
|
266
|
+
const severityRank = { low: 0, medium: 1, high: 2 };
|
|
267
|
+
const thresholdRank = severityRank[threshold] ?? 2;
|
|
268
|
+
const maxSeverityRank = severityRank[argsDlp.severity] ?? 0;
|
|
269
|
+
if (maxSeverityRank >= thresholdRank) {
|
|
270
|
+
const responseMode = this.config.dlp.prompt_injection_response || 'deny';
|
|
271
|
+
if (responseMode === 'require_approval') {
|
|
272
|
+
const { approval, token } = this.approvalManager.createApproval(toolCall, 'admin', `Prompt injection detected: ${piDetections.join(', ')}`, undefined, requestingApiKeyId);
|
|
273
|
+
await this.approvalManager.flush();
|
|
274
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
275
|
+
this.metrics?.recordRequest('needs_approval', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
276
|
+
const result = this.buildResult(toolCall, 'needs_approval', { decision: 'require_approval', rule_id: 'prompt_injection', rule_name: 'Prompt injection detected', reasons: piDetections }, startTime, undefined, undefined, { approval_id: approval.approval_id, token, expires_at: approval.expires_at }, argsDlp);
|
|
277
|
+
logger_2.log.pipelineEnd('needs_approval', Date.now() - startTime);
|
|
278
|
+
return { allowed: false, result, stepTimings, startTime };
|
|
279
|
+
}
|
|
280
|
+
logger_2.log.pipelineStep('🛡️', 'PROMPT_INJECTION_BLOCK', `Blocked: ${piDetections.join(', ')} (severity: ${argsDlp.severity}, threshold: ${threshold})`);
|
|
281
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
282
|
+
this.metrics?.recordRequest('blocked', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
283
|
+
const result = this.buildResult(toolCall, 'blocked', {
|
|
284
|
+
decision: 'deny',
|
|
285
|
+
rule_id: 'prompt_injection_block',
|
|
286
|
+
rule_name: 'Prompt injection detected',
|
|
287
|
+
reasons: [`Prompt injection detected: ${piDetections.join(', ')}`],
|
|
288
|
+
}, startTime, undefined, `Blocked by prompt injection detection: ${piDetections.join(', ')} (severity: ${argsDlp.severity})`, undefined, argsDlp);
|
|
289
|
+
logger_2.log.pipelineEnd('blocked', Date.now() - startTime);
|
|
290
|
+
return { allowed: false, result, stepTimings, startTime };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Policy evaluation — DLP context is passed so DLP-conditioned rules
|
|
295
|
+
// compete with all other rules in a single priority-ordered pass.
|
|
296
|
+
stepStart = Date.now();
|
|
297
|
+
const dlpContext = argsDlp.detected.length > 0
|
|
298
|
+
? { detected: argsDlp.detected, severity: argsDlp.severity, pattern_names: argsDlp.detected }
|
|
299
|
+
: undefined;
|
|
300
|
+
let policyResult;
|
|
301
|
+
let usedWorkspacePolicy = false;
|
|
302
|
+
if (this.policyStore && toolCall.workspace_id) {
|
|
303
|
+
const workspacePack = this.policyStore.getByWorkspaceId(toolCall.workspace_id);
|
|
304
|
+
if (workspacePack) {
|
|
305
|
+
policyResult = childSpan(otel, 'gateway.policy_eval_workspace', () => {
|
|
306
|
+
const ephemeralEngine = engine_1.PolicyEngine.fromPack(workspacePack);
|
|
307
|
+
return ephemeralEngine.evaluate(toolCall, dlpContext);
|
|
308
|
+
});
|
|
309
|
+
usedWorkspacePolicy = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!usedWorkspacePolicy) {
|
|
313
|
+
if (this.opaEngine) {
|
|
314
|
+
try {
|
|
315
|
+
policyResult = await asyncChildSpan(otel, 'gateway.policy_eval_opa', async () => {
|
|
316
|
+
return this.opaEngine.evaluate(toolCall);
|
|
317
|
+
});
|
|
318
|
+
if (policyResult.rule_id === 'opa_fallback') {
|
|
319
|
+
policyResult = childSpan(otel, 'gateway.policy_eval', () => {
|
|
320
|
+
return policyEngine.evaluate(toolCall, dlpContext);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
logger_2.logger.error('OPA evaluation failed, falling back to YAML engine', { component: 'gateway', error: err instanceof Error ? err.message : String(err) });
|
|
326
|
+
policyResult = childSpan(otel, 'gateway.policy_eval', () => {
|
|
327
|
+
return policyEngine.evaluate(toolCall, dlpContext);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
policyResult = childSpan(otel, 'gateway.policy_eval', () => {
|
|
333
|
+
return policyEngine.evaluate(toolCall, dlpContext);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
stepTimings.policy = Date.now() - stepStart;
|
|
338
|
+
this.auditLogger.logPolicyDecided(toolCall, policyResult.decision, policyResult.rule_id, policyResult.reasons);
|
|
339
|
+
this.metrics?.recordPolicyDecision(policyResult.decision, policyResult.rule_id || 'unknown');
|
|
340
|
+
logger_2.log.policy(policyResult.decision, policyResult.rule_id, policyResult.reasons);
|
|
341
|
+
// Policy: deny
|
|
342
|
+
if (policyResult.decision === 'deny') {
|
|
343
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
344
|
+
this.metrics?.recordRequest('blocked', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
345
|
+
const ruleInfo = policyResult.rule_id ? ` [rule: ${policyResult.rule_id}]` : '';
|
|
346
|
+
const result = this.buildResult(toolCall, 'blocked', policyResult, startTime, undefined, `Blocked by policy${ruleInfo}: ${policyResult.reasons.join(', ')}`, undefined, argsDlp);
|
|
347
|
+
logger_2.log.pipelineEnd('blocked', Date.now() - startTime);
|
|
348
|
+
return { allowed: false, result, policyResult, argsDlp, stepTimings, startTime };
|
|
349
|
+
}
|
|
350
|
+
// Policy: require_approval (DLP report is now always included)
|
|
351
|
+
if (policyResult.decision === 'require_approval') {
|
|
352
|
+
const existingApproval = this.approvalManager.findApprovedForTask(toolCall.task_id, toolCall.actor.id, toolCall.tool.name, toolCall.tool.capability);
|
|
353
|
+
if (existingApproval) {
|
|
354
|
+
logger_2.log.pipelineStep('✅', 'APPROVAL_BYPASS', `Reusing approval ${existingApproval.approval_id} for task ${toolCall.task_id}`);
|
|
355
|
+
policyResult = { ...policyResult, decision: 'allow', rule_id: `approved:${existingApproval.approval_id}` };
|
|
356
|
+
// Fall through to budget/execute below
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const { approval, token } = this.approvalManager.createApproval(toolCall, policyResult.approval?.scope || 'admin', policyResult.approval?.reason || policyResult.reasons.join(', '), policyResult.approval?.ttl_seconds, requestingApiKeyId);
|
|
360
|
+
await this.approvalManager.flush();
|
|
361
|
+
this.auditLogger.logApprovalRequested(toolCall, approval.scope, approval.reason, this.config.approval.default_ttl_seconds);
|
|
362
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
363
|
+
this.metrics?.recordRequest('needs_approval', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
364
|
+
this.metrics?.setActiveApprovals(this.approvalManager.getPendingApprovals().length);
|
|
365
|
+
const result = this.buildResult(toolCall, 'needs_approval', policyResult, startTime, undefined, undefined, { approval_id: approval.approval_id, token, expires_at: approval.expires_at }, argsDlp);
|
|
366
|
+
return { allowed: false, result, policyResult, argsDlp, stepTimings, startTime };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Apply transformations
|
|
370
|
+
let processedToolCall = toolCall;
|
|
371
|
+
if (policyResult.decision === 'transform' && policyResult.transformations) {
|
|
372
|
+
logger_2.log.transform(policyResult.transformations);
|
|
373
|
+
processedToolCall = this.applyTransformations(toolCall, policyResult.transformations);
|
|
374
|
+
}
|
|
375
|
+
// Budget check + atomic reservation (S5) with optional per-workspace overrides
|
|
376
|
+
stepStart = Date.now();
|
|
377
|
+
const wsBudgetOverrides = processedToolCall.workspace_id
|
|
378
|
+
? this.getWorkspaceBudgetConfig(processedToolCall.workspace_id)
|
|
379
|
+
: undefined;
|
|
380
|
+
const budgetCheck = await asyncChildSpan(otel, 'gateway.budget_check', () => {
|
|
381
|
+
return this.budgetManager.reserveAndCheck(processedToolCall, wsBudgetOverrides);
|
|
382
|
+
});
|
|
383
|
+
stepTimings.budget = Date.now() - stepStart;
|
|
384
|
+
this.auditLogger.logBudgetChecked(toolCall, budgetCheck.report.estimated_cost_usd, budgetCheck.report.spent_cost_usd_task, budgetCheck.report.remaining_cost_usd_task);
|
|
385
|
+
if (!budgetCheck.allowed) {
|
|
386
|
+
logger_2.log.budget(false, budgetCheck.report.estimated_cost_usd, budgetCheck.report.spent_cost_usd_task, budgetCheck.report.remaining_cost_usd_task, budgetCheck.reason);
|
|
387
|
+
this.metrics?.recordBudgetBlock(this.classifyBudgetReason(budgetCheck.reason || ''));
|
|
388
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
389
|
+
this.metrics?.recordRequest('blocked', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
390
|
+
const spent = budgetCheck.report.spent_cost_usd_task?.toFixed(4) ?? '?';
|
|
391
|
+
const remaining = budgetCheck.report.remaining_cost_usd_task?.toFixed(4) ?? '?';
|
|
392
|
+
const estimated = budgetCheck.report.estimated_cost_usd?.toFixed(4) ?? '?';
|
|
393
|
+
const result = this.buildResult(toolCall, 'blocked', policyResult, startTime, undefined, `Budget exceeded: ${budgetCheck.reason} (spent: $${spent}, remaining: $${remaining}, estimated: $${estimated})`, undefined, argsDlp, budgetCheck.report);
|
|
394
|
+
logger_2.log.pipelineEnd('blocked', Date.now() - startTime);
|
|
395
|
+
return { allowed: false, result, policyResult, processedToolCall, argsDlp, budgetCheck, stepTimings, startTime };
|
|
396
|
+
}
|
|
397
|
+
logger_2.log.budget(true, budgetCheck.report.estimated_cost_usd, budgetCheck.report.spent_cost_usd_task, budgetCheck.report.remaining_cost_usd_task);
|
|
398
|
+
return { allowed: true, policyResult, processedToolCall, argsDlp, budgetCheck, reservationKey: budgetCheck.reservationKey, stepTimings, startTime };
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Run the post-execution pipeline: DLP scan output, usage extraction, budget recording,
|
|
402
|
+
* metrics, audit log, and build the final ToolResult.
|
|
403
|
+
* Does NOT cache for idempotency — the caller handles that.
|
|
404
|
+
*/
|
|
405
|
+
async postExecute(toolCall, output, pre, otel) {
|
|
406
|
+
const { policyResult, processedToolCall, argsDlp, budgetCheck, reservationKey, stepTimings, startTime } = pre;
|
|
407
|
+
const executionDuration = Date.now() - startTime;
|
|
408
|
+
logger_2.log.executed(output.http_status || 200, executionDuration);
|
|
409
|
+
this.auditLogger.logToolExecuted(toolCall, 'ok', executionDuration, output.http_status);
|
|
410
|
+
// Record result for anomaly detection
|
|
411
|
+
this.anomalyDetector?.recordResult(toolCall, executionDuration, false);
|
|
412
|
+
this.anomalyDetector?.analyzeResult(toolCall, executionDuration, false);
|
|
413
|
+
// DLP scan on output
|
|
414
|
+
let stepStart = Date.now();
|
|
415
|
+
let outputDlp = { detected: [], redactions: [], severity: 'low' };
|
|
416
|
+
if (this.config.dlp.scan_output && output.body) {
|
|
417
|
+
outputDlp = childSpan(otel, 'gateway.dlp_scan_output', () => {
|
|
418
|
+
return this.dlpScanner.scan(output, 'output');
|
|
419
|
+
});
|
|
420
|
+
logger_2.log.dlp('output', outputDlp.detected, outputDlp.severity, outputDlp.redactions.length);
|
|
421
|
+
if (outputDlp.detected.length > 0) {
|
|
422
|
+
this.auditLogger.logDLPScanned(toolCall, outputDlp.detected, outputDlp.severity, outputDlp.redactions.length, outputDlp.redactions);
|
|
423
|
+
for (const detectionType of outputDlp.detected) {
|
|
424
|
+
this.metrics?.recordDLPDetection(detectionType, outputDlp.severity);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else if (!this.config.dlp.scan_output && output.body) {
|
|
429
|
+
// Warn when DLP output scanning is disabled for sensitive operations
|
|
430
|
+
const capability = toolCall.tool.capability;
|
|
431
|
+
if (capability === 'write' || capability === 'delete' || capability === 'admin') {
|
|
432
|
+
logger_2.logger.warn('DLP output scanning is disabled for sensitive operation', {
|
|
433
|
+
component: 'gateway',
|
|
434
|
+
tool: toolCall.tool.name,
|
|
435
|
+
capability,
|
|
436
|
+
hint: 'Enable dlp.scan_output for production use',
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
stepTimings.dlp_out = Date.now() - stepStart;
|
|
441
|
+
// Extract usage data from response
|
|
442
|
+
const headerUsage = this.usageExtractor.extractFromHeaders(output.headers);
|
|
443
|
+
const bodyUsage = this.usageExtractor.extractFromBody(output.body);
|
|
444
|
+
const mergedUsage = this.usageExtractor.merge(headerUsage, bodyUsage);
|
|
445
|
+
// Compute actual cost from usage if available
|
|
446
|
+
let actualCostUsd;
|
|
447
|
+
if (mergedUsage) {
|
|
448
|
+
if (mergedUsage.provider_cost_usd !== undefined) {
|
|
449
|
+
actualCostUsd = mergedUsage.provider_cost_usd;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const effectiveToolCall = processedToolCall || toolCall;
|
|
453
|
+
const model = typeof effectiveToolCall.args.model === 'string' ? effectiveToolCall.args.model : undefined;
|
|
454
|
+
const computedCost = this.usageExtractor.computeCost(mergedUsage, model);
|
|
455
|
+
if (computedCost !== undefined) {
|
|
456
|
+
actualCostUsd = computedCost;
|
|
457
|
+
mergedUsage.computed_cost_usd = computedCost;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Record cost (skip budget recording when budgetCheck is missing, e.g. passthrough)
|
|
462
|
+
const estimatedCost = budgetCheck?.report?.estimated_cost_usd ?? 0;
|
|
463
|
+
const recordedCost = actualCostUsd ?? estimatedCost;
|
|
464
|
+
const effectiveToolCall = processedToolCall || toolCall;
|
|
465
|
+
if (budgetCheck) {
|
|
466
|
+
// Commit the reservation with actual cost (releases difference between estimate and actual)
|
|
467
|
+
if (reservationKey) {
|
|
468
|
+
this.budgetManager.commitReservation(reservationKey, recordedCost);
|
|
469
|
+
}
|
|
470
|
+
// Record step count and cost record metadata.
|
|
471
|
+
// Skip cost increment when reservation already accounts for the cost.
|
|
472
|
+
const costRecord = {
|
|
473
|
+
estimated_cost_usd: estimatedCost,
|
|
474
|
+
actual_cost_usd: actualCostUsd,
|
|
475
|
+
usage: mergedUsage,
|
|
476
|
+
};
|
|
477
|
+
this.budgetManager.record(effectiveToolCall, costRecord, !!reservationKey);
|
|
478
|
+
await this.budgetManager.flush();
|
|
479
|
+
}
|
|
480
|
+
// Extract model info for LLM monitoring
|
|
481
|
+
const model = this.usageExtractor.extractModelFromBody(output.body)
|
|
482
|
+
|| (typeof effectiveToolCall.args.model === 'string' ? effectiveToolCall.args.model : undefined);
|
|
483
|
+
if (model && mergedUsage) {
|
|
484
|
+
const provider = this.usageExtractor.detectProvider(model);
|
|
485
|
+
mergedUsage.model = model;
|
|
486
|
+
mergedUsage.provider = provider;
|
|
487
|
+
// Record LLM-specific metrics
|
|
488
|
+
if (this.metrics) {
|
|
489
|
+
this.metrics.recordLLMUsage({
|
|
490
|
+
model,
|
|
491
|
+
provider,
|
|
492
|
+
inputTokens: mergedUsage.input_tokens,
|
|
493
|
+
outputTokens: mergedUsage.output_tokens,
|
|
494
|
+
costUsd: actualCostUsd,
|
|
495
|
+
durationSeconds: (Date.now() - startTime) / 1000,
|
|
496
|
+
status: 'ok',
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Record cost and token usage metrics
|
|
501
|
+
if (this.metrics) {
|
|
502
|
+
this.metrics.recordCost(recordedCost, mergedUsage?.provider_cost_usd !== undefined ? 'provider' : actualCostUsd !== undefined ? 'computed' : 'estimated', toolCall.tool.name);
|
|
503
|
+
if (mergedUsage) {
|
|
504
|
+
if (mergedUsage.input_tokens !== undefined) {
|
|
505
|
+
this.metrics.recordTokenUsage('input', toolCall.tool.name, mergedUsage.input_tokens);
|
|
506
|
+
}
|
|
507
|
+
if (mergedUsage.output_tokens !== undefined) {
|
|
508
|
+
this.metrics.recordTokenUsage('output', toolCall.tool.name, mergedUsage.output_tokens);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Merge DLP reports
|
|
513
|
+
const argsDlpSafe = argsDlp || { detected: [], redactions: [], severity: 'low' };
|
|
514
|
+
const mergedDlp = {
|
|
515
|
+
detected: [...new Set([...argsDlpSafe.detected, ...outputDlp.detected])],
|
|
516
|
+
redactions: [...argsDlpSafe.redactions, ...outputDlp.redactions],
|
|
517
|
+
severity: this.maxSeverity(argsDlpSafe.severity, outputDlp.severity),
|
|
518
|
+
};
|
|
519
|
+
// Build final result
|
|
520
|
+
const budgetReportWithActual = budgetCheck
|
|
521
|
+
? this.budgetManager.getReportWithActual(effectiveToolCall, estimatedCost, actualCostUsd, mergedUsage)
|
|
522
|
+
: { estimated_cost_usd: 0, spent_cost_usd_task: 0, remaining_cost_usd_task: 0 };
|
|
523
|
+
const defaultPolicy = policyResult || { decision: 'allow', rule_id: 'passthrough', rule_name: 'Passthrough', reasons: [] };
|
|
524
|
+
const result = this.buildResult(toolCall, 'ok', defaultPolicy, startTime, output, undefined, undefined, mergedDlp, budgetReportWithActual);
|
|
525
|
+
const auditMeta = { step_timings: stepTimings };
|
|
526
|
+
if (model) {
|
|
527
|
+
auditMeta.model = model;
|
|
528
|
+
auditMeta.provider = mergedUsage?.provider;
|
|
529
|
+
auditMeta.input_tokens = mergedUsage?.input_tokens;
|
|
530
|
+
auditMeta.output_tokens = mergedUsage?.output_tokens;
|
|
531
|
+
auditMeta.cost_usd = actualCostUsd;
|
|
532
|
+
}
|
|
533
|
+
this.auditLogger.logToolResultReturned(toolCall, 'ok', Date.now() - startTime, auditMeta);
|
|
534
|
+
// Record successful request metrics
|
|
535
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
536
|
+
this.metrics?.recordRequest('ok', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
537
|
+
logger_2.log.pipelineEnd('ok', Date.now() - startTime);
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
// Main execution pipeline - implements the full runtime path
|
|
541
|
+
async execute(toolCall, requestingApiKeyId) {
|
|
542
|
+
const otel = this.tracer?.getTracer();
|
|
543
|
+
// If no tracer or not enabled, run without spans
|
|
544
|
+
if (!otel) {
|
|
545
|
+
return this._executeInternal(toolCall, undefined, requestingApiKeyId);
|
|
546
|
+
}
|
|
547
|
+
return otel.startActiveSpan('gateway.execute', {
|
|
548
|
+
kind: api_1.SpanKind.SERVER,
|
|
549
|
+
attributes: {
|
|
550
|
+
'palaryn.tool_call_id': toolCall.tool_call_id,
|
|
551
|
+
'palaryn.task_id': toolCall.task_id,
|
|
552
|
+
'palaryn.tool': toolCall.tool.name,
|
|
553
|
+
'palaryn.capability': toolCall.tool.capability,
|
|
554
|
+
'palaryn.actor': toolCall.actor.id,
|
|
555
|
+
'palaryn.workspace': toolCall.workspace_id,
|
|
556
|
+
},
|
|
557
|
+
}, async (span) => {
|
|
558
|
+
try {
|
|
559
|
+
const result = await this._executeInternal(toolCall, otel, requestingApiKeyId);
|
|
560
|
+
span.setAttribute('palaryn.status', result.status);
|
|
561
|
+
span.setAttribute('palaryn.duration_ms', result.timing.duration_ms);
|
|
562
|
+
if (result.status === 'error') {
|
|
563
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: result.error });
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
|
567
|
+
}
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: String(err) });
|
|
572
|
+
span.recordException(err);
|
|
573
|
+
throw err;
|
|
574
|
+
}
|
|
575
|
+
finally {
|
|
576
|
+
span.end();
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
// Internal pipeline with optional tracing — delegates to preExecute/postExecute
|
|
581
|
+
async _executeInternal(toolCall, otel, requestingApiKeyId) {
|
|
582
|
+
const startTime = Date.now();
|
|
583
|
+
// A2: Compute cache key using tool_call_id + body hash
|
|
584
|
+
const bodyHash = computeBodyHash(toolCall);
|
|
585
|
+
const cacheKey = `${toolCall.tool_call_id}:${bodyHash}`;
|
|
586
|
+
// Step 0: Idempotency check - return cached result for duplicate tool_call_id + body
|
|
587
|
+
const cached = await asyncChildSpan(otel, 'gateway.idempotency_check', async () => {
|
|
588
|
+
return this.idempotencyStore.get(cacheKey);
|
|
589
|
+
});
|
|
590
|
+
if (cached) {
|
|
591
|
+
this.metrics?.recordIdempotencyHit();
|
|
592
|
+
logger_2.log.idempotencyHit(toolCall.tool_call_id);
|
|
593
|
+
logger_2.log.pipelineEnd('ok (cached)', Date.now() - startTime);
|
|
594
|
+
return cached;
|
|
595
|
+
}
|
|
596
|
+
logger_2.log.idempotencyMiss();
|
|
597
|
+
// Step 0.5: TOCTOU guard — if another request with the same cache key is already
|
|
598
|
+
// in flight, await its result instead of executing a duplicate.
|
|
599
|
+
const existingFlight = this.inFlightCalls.get(cacheKey);
|
|
600
|
+
if (existingFlight) {
|
|
601
|
+
logger_2.log.idempotencyHit(toolCall.tool_call_id);
|
|
602
|
+
return existingFlight.promise;
|
|
603
|
+
}
|
|
604
|
+
// S6: Register this execution as in-flight via a deferred promise with timeout.
|
|
605
|
+
let resolveInFlight;
|
|
606
|
+
let rejectInFlight;
|
|
607
|
+
const flightPromise = new Promise((resolve, reject) => {
|
|
608
|
+
resolveInFlight = resolve;
|
|
609
|
+
rejectInFlight = reject;
|
|
610
|
+
});
|
|
611
|
+
const flightTimeout = setTimeout(() => {
|
|
612
|
+
rejectInFlight(new Error('In-flight request timed out after 60s'));
|
|
613
|
+
this.inFlightCalls.delete(cacheKey);
|
|
614
|
+
}, 60000);
|
|
615
|
+
this.inFlightCalls.set(cacheKey, {
|
|
616
|
+
promise: flightPromise,
|
|
617
|
+
timeout: flightTimeout,
|
|
618
|
+
createdAt: Date.now(),
|
|
619
|
+
});
|
|
620
|
+
const executeAndResolve = async () => {
|
|
621
|
+
// Run pre-execution pipeline (rate limit, anomaly, policy, DLP args, budget)
|
|
622
|
+
const pre = await this.preExecute(toolCall, otel, requestingApiKeyId);
|
|
623
|
+
if (!pre.allowed) {
|
|
624
|
+
// Release budget reservation if pre-execute denied after reservation
|
|
625
|
+
if (pre.reservationKey) {
|
|
626
|
+
this.budgetManager.releaseReservation(pre.reservationKey);
|
|
627
|
+
}
|
|
628
|
+
return pre.result;
|
|
629
|
+
}
|
|
630
|
+
const { processedToolCall, policyResult, argsDlp, budgetCheck, reservationKey, stepTimings } = pre;
|
|
631
|
+
// Step 6: Execute tool via executor registry
|
|
632
|
+
logger_2.log.executing(processedToolCall.tool.name, processedToolCall.args.url);
|
|
633
|
+
try {
|
|
634
|
+
let stepStart = Date.now();
|
|
635
|
+
const output = await asyncChildSpan(otel, 'gateway.tool_execute', () => {
|
|
636
|
+
return this.executorRegistry.execute(processedToolCall);
|
|
637
|
+
});
|
|
638
|
+
stepTimings.execute = Date.now() - stepStart;
|
|
639
|
+
// Run post-execution pipeline (DLP output, usage, budget recording, metrics, audit)
|
|
640
|
+
const result = await this.postExecute(toolCall, output, pre, otel);
|
|
641
|
+
// Cache result for idempotency (5 minute TTL) using cache key with body hash
|
|
642
|
+
this.idempotencyStore.set(cacheKey, result, 300000);
|
|
643
|
+
if (this.idempotencyStore.flush)
|
|
644
|
+
await this.idempotencyStore.flush();
|
|
645
|
+
return result;
|
|
646
|
+
}
|
|
647
|
+
catch (err) {
|
|
648
|
+
// S5: Release budget reservation on execution error
|
|
649
|
+
if (reservationKey) {
|
|
650
|
+
this.budgetManager.releaseReservation(reservationKey);
|
|
651
|
+
}
|
|
652
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
653
|
+
const errorType = err instanceof Error ? err.constructor.name : 'UnknownError';
|
|
654
|
+
const executionDuration = Date.now() - pre.startTime;
|
|
655
|
+
logger_2.log.executionError(errorMsg);
|
|
656
|
+
this.auditLogger.logToolExecuted(toolCall, 'error', executionDuration);
|
|
657
|
+
this.auditLogger.logToolResultReturned(toolCall, 'error', executionDuration, { step_timings: stepTimings });
|
|
658
|
+
// Record result for anomaly detection (error tracking)
|
|
659
|
+
this.anomalyDetector?.recordResult(toolCall, executionDuration, true);
|
|
660
|
+
this.anomalyDetector?.analyzeResult(toolCall, executionDuration, true);
|
|
661
|
+
// Record error metrics
|
|
662
|
+
const durationSec = executionDuration / 1000;
|
|
663
|
+
this.metrics?.recordExecutorError(toolCall.tool.name, errorType);
|
|
664
|
+
this.metrics?.recordRequest('error', toolCall.tool.name, toolCall.tool.capability, durationSec);
|
|
665
|
+
const result = this.buildResult(toolCall, 'error', policyResult, pre.startTime, undefined, errorMsg, undefined, argsDlp, budgetCheck.report);
|
|
666
|
+
logger_2.log.pipelineEnd('error', Date.now() - pre.startTime);
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
try {
|
|
671
|
+
const result = await executeAndResolve();
|
|
672
|
+
clearTimeout(flightTimeout);
|
|
673
|
+
resolveInFlight(result);
|
|
674
|
+
return result;
|
|
675
|
+
}
|
|
676
|
+
catch (err) {
|
|
677
|
+
clearTimeout(flightTimeout);
|
|
678
|
+
// Even on unexpected errors, resolve the promise so waiters don't hang
|
|
679
|
+
const errorResult = this.buildResult(toolCall, 'error', {
|
|
680
|
+
decision: 'deny', rule_id: 'internal_error', rule_name: 'Internal error', reasons: [String(err)],
|
|
681
|
+
}, startTime, undefined, String(err));
|
|
682
|
+
resolveInFlight(errorResult);
|
|
683
|
+
throw err;
|
|
684
|
+
}
|
|
685
|
+
finally {
|
|
686
|
+
this.inFlightCalls.delete(cacheKey);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// Process an approval token
|
|
690
|
+
async processApproval(token, approverId, approved, reason, approverApiKeyId) {
|
|
691
|
+
try {
|
|
692
|
+
if (approved) {
|
|
693
|
+
const result = await this.approvalManager.approve(token, approverId, approverApiKeyId);
|
|
694
|
+
if (!result.approved) {
|
|
695
|
+
return { success: false, error: 'Approval failed or expired' };
|
|
696
|
+
}
|
|
697
|
+
return { success: true };
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
await this.approvalManager.deny(token, approverId, reason || 'Denied by approver');
|
|
701
|
+
return { success: true };
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
catch (err) {
|
|
705
|
+
const message = err instanceof Error ? err.message : 'Approval processing failed';
|
|
706
|
+
return { success: false, error: message };
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// Report usage from a client (e.g. Android reporting actual LLM costs)
|
|
710
|
+
reportUsage(params) {
|
|
711
|
+
// Log audit event
|
|
712
|
+
this.auditLogger.log({
|
|
713
|
+
event_type: 'USAGE_REPORTED',
|
|
714
|
+
tool_call_id: params.tool_call_id,
|
|
715
|
+
task_id: params.task_id,
|
|
716
|
+
workspace_id: params.workspace_id || 'unknown',
|
|
717
|
+
actor_id: params.actor_id || 'unknown',
|
|
718
|
+
tool_name: 'client_report',
|
|
719
|
+
metadata: {
|
|
720
|
+
actual_cost_usd: params.actual_cost_usd,
|
|
721
|
+
usage: params.usage,
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
// Record metrics
|
|
725
|
+
if (this.metrics) {
|
|
726
|
+
if (params.actual_cost_usd !== undefined) {
|
|
727
|
+
this.metrics.recordCost(params.actual_cost_usd, 'client_reported', 'client_report');
|
|
728
|
+
}
|
|
729
|
+
if (params.usage) {
|
|
730
|
+
if (params.usage.input_tokens !== undefined) {
|
|
731
|
+
this.metrics.recordTokenUsage('input', 'client_report', params.usage.input_tokens);
|
|
732
|
+
}
|
|
733
|
+
if (params.usage.output_tokens !== undefined) {
|
|
734
|
+
this.metrics.recordTokenUsage('output', 'client_report', params.usage.output_tokens);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Get trace for a task
|
|
740
|
+
getTaskTrace(taskId) {
|
|
741
|
+
return this.auditLogger.getTaskTrace(taskId);
|
|
742
|
+
}
|
|
743
|
+
// Get current policy pack
|
|
744
|
+
getCurrentPolicy() {
|
|
745
|
+
return this.policyEngine.getPack();
|
|
746
|
+
}
|
|
747
|
+
// Validate a policy pack
|
|
748
|
+
validatePolicy(pack) {
|
|
749
|
+
return engine_1.PolicyEngine.validate(pack);
|
|
750
|
+
}
|
|
751
|
+
// Get pending approvals
|
|
752
|
+
getPendingApprovals(workspaceId) {
|
|
753
|
+
return this.approvalManager.getPendingApprovals(workspaceId);
|
|
754
|
+
}
|
|
755
|
+
/** Reload the policy pack from disk. Creates a new engine and swaps atomically. */
|
|
756
|
+
reloadPolicy() {
|
|
757
|
+
try {
|
|
758
|
+
// A3: Create new engine, load it, then swap the reference atomically
|
|
759
|
+
const newEngine = new engine_1.PolicyEngine(this.config.policy.pack_path, this.config.policy.default_effect);
|
|
760
|
+
const pack = newEngine.getPack();
|
|
761
|
+
this.policyEngine = newEngine;
|
|
762
|
+
return { success: true, ruleCount: pack.rules.length };
|
|
763
|
+
}
|
|
764
|
+
catch (err) {
|
|
765
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
766
|
+
return { success: false, ruleCount: 0, error: message };
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/** Get the file path for the active policy pack */
|
|
770
|
+
getPolicyPackPath() {
|
|
771
|
+
return this.config.policy.pack_path;
|
|
772
|
+
}
|
|
773
|
+
// ---------------------------------------------------------------------------
|
|
774
|
+
// Store injection — replace default in-memory stores with external backends
|
|
775
|
+
// ---------------------------------------------------------------------------
|
|
776
|
+
/** Replace the idempotency store (default: in-memory) */
|
|
777
|
+
setIdempotencyStore(store) {
|
|
778
|
+
this.idempotencyStore = store;
|
|
779
|
+
}
|
|
780
|
+
/** Replace the rate limiter with one backed by an external store */
|
|
781
|
+
setRateLimiter(limiter) {
|
|
782
|
+
this.rateLimiter = limiter;
|
|
783
|
+
}
|
|
784
|
+
/** Inject all external stores at once (e.g. from Redis or Postgres) */
|
|
785
|
+
setStores(stores) {
|
|
786
|
+
if (stores.idempotencyStore) {
|
|
787
|
+
this.idempotencyStore = stores.idempotencyStore;
|
|
788
|
+
}
|
|
789
|
+
if (stores.rateLimitStore) {
|
|
790
|
+
this.rateLimiter = new limiter_1.RateLimiter(this.config.rate_limit || DEFAULT_RATE_LIMIT, stores.rateLimitStore);
|
|
791
|
+
}
|
|
792
|
+
if (stores.auditStore) {
|
|
793
|
+
this.auditLogger.setStore(stores.auditStore);
|
|
794
|
+
}
|
|
795
|
+
if (stores.budgetStore) {
|
|
796
|
+
this.budgetManager = new manager_1.BudgetManager(this.config.budget, stores.budgetStore);
|
|
797
|
+
}
|
|
798
|
+
if (stores.approvalStore) {
|
|
799
|
+
this.approvalManager = new manager_2.ApprovalManager(this.config.approval, stores.approvalStore);
|
|
800
|
+
}
|
|
801
|
+
if (stores.policyStore) {
|
|
802
|
+
this.policyStore = stores.policyStore;
|
|
803
|
+
}
|
|
804
|
+
if (stores.rateLimitConfigStore) {
|
|
805
|
+
this.rateLimitConfigStore = stores.rateLimitConfigStore;
|
|
806
|
+
}
|
|
807
|
+
if (stores.budgetConfigStore) {
|
|
808
|
+
this.budgetConfigStore = stores.budgetConfigStore;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
// Get components for testing / introspection
|
|
812
|
+
getAuditLogger() { return this.auditLogger; }
|
|
813
|
+
getBudgetManager() { return this.budgetManager; }
|
|
814
|
+
getPolicyEngine() { return this.policyEngine; }
|
|
815
|
+
getDLPScanner() { return this.dlpScanner; }
|
|
816
|
+
getApprovalManager() { return this.approvalManager; }
|
|
817
|
+
getExecutorRegistry() { return this.executorRegistry; }
|
|
818
|
+
getHttpExecutor() { return this.httpExecutor; }
|
|
819
|
+
getRateLimiter() { return this.rateLimiter; }
|
|
820
|
+
getIdempotencyStore() { return this.idempotencyStore; }
|
|
821
|
+
getAnomalyDetector() { return this.anomalyDetector; }
|
|
822
|
+
getOPAEngine() { return this.opaEngine; }
|
|
823
|
+
getPolicyStore() { return this.policyStore; }
|
|
824
|
+
getRateLimitConfigStore() { return this.rateLimitConfigStore; }
|
|
825
|
+
getBudgetConfigStore() { return this.budgetConfigStore; }
|
|
826
|
+
/** Return the workspace-specific policy if one exists, otherwise the global policy. */
|
|
827
|
+
getWorkspacePolicy(workspaceId) {
|
|
828
|
+
if (this.policyStore) {
|
|
829
|
+
const custom = this.policyStore.getByWorkspaceId(workspaceId);
|
|
830
|
+
if (custom)
|
|
831
|
+
return { policy: custom, is_custom: true };
|
|
832
|
+
}
|
|
833
|
+
return { policy: this.policyEngine.getPack(), is_custom: false };
|
|
834
|
+
}
|
|
835
|
+
/** Return workspace-specific rate limit config if one exists. */
|
|
836
|
+
getWorkspaceRateLimitConfig(workspaceId) {
|
|
837
|
+
return this.rateLimitConfigStore?.getByWorkspaceId(workspaceId);
|
|
838
|
+
}
|
|
839
|
+
/** Return workspace-specific budget config if one exists. */
|
|
840
|
+
getWorkspaceBudgetConfig(workspaceId) {
|
|
841
|
+
return this.budgetConfigStore?.getByWorkspaceId(workspaceId);
|
|
842
|
+
}
|
|
843
|
+
// Helper: Build a ToolResult
|
|
844
|
+
buildResult(toolCall, status, policyResult, startTime, output, error, approvalInfo, dlpReport, budgetReport) {
|
|
845
|
+
const result = {
|
|
846
|
+
tool_call_id: toolCall.tool_call_id,
|
|
847
|
+
task_id: toolCall.task_id,
|
|
848
|
+
status,
|
|
849
|
+
policy: {
|
|
850
|
+
decision: policyResult.decision,
|
|
851
|
+
rule_id: policyResult.rule_id,
|
|
852
|
+
reasons: policyResult.reasons,
|
|
853
|
+
},
|
|
854
|
+
dlp: dlpReport || { detected: [], redactions: [], severity: 'low' },
|
|
855
|
+
budget: budgetReport || { estimated_cost_usd: 0, spent_cost_usd_task: 0, remaining_cost_usd_task: 0 },
|
|
856
|
+
output: output || undefined,
|
|
857
|
+
error: error || undefined,
|
|
858
|
+
timing: {
|
|
859
|
+
started_at: new Date(startTime).toISOString(),
|
|
860
|
+
duration_ms: Date.now() - startTime,
|
|
861
|
+
},
|
|
862
|
+
};
|
|
863
|
+
if (approvalInfo) {
|
|
864
|
+
result.approval = approvalInfo;
|
|
865
|
+
}
|
|
866
|
+
return result;
|
|
867
|
+
}
|
|
868
|
+
// Helper: Apply policy transformations to a ToolCall
|
|
869
|
+
applyTransformations(toolCall, transformations) {
|
|
870
|
+
const clone = JSON.parse(JSON.stringify(toolCall));
|
|
871
|
+
for (const t of transformations) {
|
|
872
|
+
switch (t.type) {
|
|
873
|
+
case 'strip_header':
|
|
874
|
+
if (clone.args.headers) {
|
|
875
|
+
delete clone.args.headers[t.target];
|
|
876
|
+
}
|
|
877
|
+
break;
|
|
878
|
+
case 'redact_field':
|
|
879
|
+
this.setNestedValue(clone.args, t.target, '[REDACTED]');
|
|
880
|
+
break;
|
|
881
|
+
case 'replace_value':
|
|
882
|
+
this.setNestedValue(clone.args, t.target, t.value || '');
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return clone;
|
|
887
|
+
}
|
|
888
|
+
// Helper: Set a nested value using dot notation
|
|
889
|
+
setNestedValue(obj, path, value) {
|
|
890
|
+
const parts = path.split('.');
|
|
891
|
+
let current = obj;
|
|
892
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
893
|
+
if (current[parts[i]] === undefined)
|
|
894
|
+
return;
|
|
895
|
+
current = current[parts[i]];
|
|
896
|
+
}
|
|
897
|
+
current[parts[parts.length - 1]] = value;
|
|
898
|
+
}
|
|
899
|
+
// Helper: Classify budget block reason into a metric label
|
|
900
|
+
classifyBudgetReason(reason) {
|
|
901
|
+
if (reason.startsWith('Task budget'))
|
|
902
|
+
return 'task';
|
|
903
|
+
if (reason.startsWith('User daily'))
|
|
904
|
+
return 'user_daily';
|
|
905
|
+
if (reason.startsWith('User monthly'))
|
|
906
|
+
return 'user_monthly';
|
|
907
|
+
if (reason.startsWith('Workspace daily'))
|
|
908
|
+
return 'workspace_daily';
|
|
909
|
+
if (reason.startsWith('Workspace monthly'))
|
|
910
|
+
return 'workspace_monthly';
|
|
911
|
+
if (reason.startsWith('Step limit'))
|
|
912
|
+
return 'step_limit';
|
|
913
|
+
if (reason.startsWith('Wall clock'))
|
|
914
|
+
return 'wall_clock';
|
|
915
|
+
return 'unknown';
|
|
916
|
+
}
|
|
917
|
+
// Helper: Get max severity
|
|
918
|
+
maxSeverity(a, b) {
|
|
919
|
+
const order = { low: 0, medium: 1, high: 2 };
|
|
920
|
+
const aVal = order[a] || 0;
|
|
921
|
+
const bVal = order[b] || 0;
|
|
922
|
+
return aVal >= bVal ? a : b;
|
|
923
|
+
}
|
|
924
|
+
/** Returns true if the gateway is in the process of shutting down. */
|
|
925
|
+
get isShuttingDown() {
|
|
926
|
+
return this._shuttingDown;
|
|
927
|
+
}
|
|
928
|
+
// Shutdown — flush pending writes, close logger, clear caches, reset limiter
|
|
929
|
+
async shutdown() {
|
|
930
|
+
this._shuttingDown = true;
|
|
931
|
+
// Clear the in-flight cleanup interval
|
|
932
|
+
if (this.inFlightCleanupInterval) {
|
|
933
|
+
clearInterval(this.inFlightCleanupInterval);
|
|
934
|
+
this.inFlightCleanupInterval = undefined;
|
|
935
|
+
}
|
|
936
|
+
// Clear in-flight call timeouts
|
|
937
|
+
for (const [key, entry] of this.inFlightCalls) {
|
|
938
|
+
clearTimeout(entry.timeout);
|
|
939
|
+
}
|
|
940
|
+
this.inFlightCalls.clear();
|
|
941
|
+
// 1. Flush all pending async writes to storage backends
|
|
942
|
+
// Use Promise.all so errors propagate to the caller instead of being silently swallowed
|
|
943
|
+
const flushes = [];
|
|
944
|
+
if (this.budgetManager.flush)
|
|
945
|
+
flushes.push(this.budgetManager.flush());
|
|
946
|
+
if (this.approvalManager.flush)
|
|
947
|
+
flushes.push(this.approvalManager.flush());
|
|
948
|
+
if (this.idempotencyStore.flush)
|
|
949
|
+
flushes.push(this.idempotencyStore.flush());
|
|
950
|
+
if (this.rateLimiter.flush)
|
|
951
|
+
flushes.push(this.rateLimiter.flush());
|
|
952
|
+
if (flushes.length > 0) {
|
|
953
|
+
await Promise.all(flushes);
|
|
954
|
+
}
|
|
955
|
+
// 2. Flush audit logger pending writes, then close file handles
|
|
956
|
+
await this.auditLogger.flush();
|
|
957
|
+
this.auditLogger.close();
|
|
958
|
+
// 3. Clear caches and reset limiter
|
|
959
|
+
this.idempotencyStore.clear();
|
|
960
|
+
this.rateLimiter.reset();
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
exports.Gateway = Gateway;
|
|
964
|
+
//# sourceMappingURL=gateway.js.map
|