palaryn 0.1.0 → 0.3.2
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/README.md +243 -588
- package/dist/sdk/typescript/src/client.js +2 -2
- package/dist/sdk/typescript/src/client.js.map +1 -1
- package/dist/src/anomaly/detector.d.ts +7 -4
- package/dist/src/anomaly/detector.d.ts.map +1 -1
- package/dist/src/anomaly/detector.js +22 -12
- package/dist/src/anomaly/detector.js.map +1 -1
- package/dist/src/audit/logger.d.ts +10 -0
- package/dist/src/audit/logger.d.ts.map +1 -1
- package/dist/src/audit/logger.js +52 -38
- package/dist/src/audit/logger.js.map +1 -1
- package/dist/src/auth/routes.d.ts.map +1 -1
- package/dist/src/auth/routes.js +35 -0
- package/dist/src/auth/routes.js.map +1 -1
- package/dist/src/budget/manager.d.ts +5 -0
- package/dist/src/budget/manager.d.ts.map +1 -1
- package/dist/src/budget/manager.js +32 -0
- package/dist/src/budget/manager.js.map +1 -1
- package/dist/src/budget/model-pricing.d.ts +20 -0
- package/dist/src/budget/model-pricing.d.ts.map +1 -0
- package/dist/src/budget/model-pricing.js +107 -0
- package/dist/src/budget/model-pricing.js.map +1 -0
- package/dist/src/budget/usage-extractor.d.ts +3 -1
- package/dist/src/budget/usage-extractor.d.ts.map +1 -1
- package/dist/src/budget/usage-extractor.js +47 -3
- package/dist/src/budget/usage-extractor.js.map +1 -1
- package/dist/src/config/defaults.d.ts.map +1 -1
- package/dist/src/config/defaults.js +65 -13
- package/dist/src/config/defaults.js.map +1 -1
- package/dist/src/dlp/tool-patterns.d.ts +7 -0
- package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
- package/dist/src/dlp/tool-patterns.js +34 -0
- package/dist/src/dlp/tool-patterns.js.map +1 -0
- package/dist/src/executor/filesystem-executor.d.ts +28 -0
- package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
- package/dist/src/executor/filesystem-executor.js +192 -0
- package/dist/src/executor/filesystem-executor.js.map +1 -0
- package/dist/src/executor/http-executor.d.ts.map +1 -1
- package/dist/src/executor/http-executor.js +22 -2
- package/dist/src/executor/http-executor.js.map +1 -1
- package/dist/src/executor/index.d.ts +4 -0
- package/dist/src/executor/index.d.ts.map +1 -1
- package/dist/src/executor/index.js +9 -1
- package/dist/src/executor/index.js.map +1 -1
- package/dist/src/executor/shell-executor.d.ts +22 -0
- package/dist/src/executor/shell-executor.d.ts.map +1 -0
- package/dist/src/executor/shell-executor.js +119 -0
- package/dist/src/executor/shell-executor.js.map +1 -0
- package/dist/src/executor/sql-executor.d.ts +29 -0
- package/dist/src/executor/sql-executor.d.ts.map +1 -0
- package/dist/src/executor/sql-executor.js +114 -0
- package/dist/src/executor/sql-executor.js.map +1 -0
- package/dist/src/executor/websocket-executor.d.ts +26 -0
- package/dist/src/executor/websocket-executor.d.ts.map +1 -0
- package/dist/src/executor/websocket-executor.js +205 -0
- package/dist/src/executor/websocket-executor.js.map +1 -0
- package/dist/src/interceptor/index.d.ts +2 -0
- package/dist/src/interceptor/index.d.ts.map +1 -0
- package/dist/src/interceptor/index.js +6 -0
- package/dist/src/interceptor/index.js.map +1 -0
- package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
- package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
- package/dist/src/interceptor/provider-interceptor.js +302 -0
- package/dist/src/interceptor/provider-interceptor.js.map +1 -0
- package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
- package/dist/src/mcp/auth-verifier.js +3 -2
- package/dist/src/mcp/auth-verifier.js.map +1 -1
- package/dist/src/mcp/bridge.d.ts +14 -10
- package/dist/src/mcp/bridge.d.ts.map +1 -1
- package/dist/src/mcp/bridge.js +51 -227
- package/dist/src/mcp/bridge.js.map +1 -1
- package/dist/src/mcp/http-transport.d.ts +2 -0
- package/dist/src/mcp/http-transport.d.ts.map +1 -1
- package/dist/src/mcp/http-transport.js +117 -66
- package/dist/src/mcp/http-transport.js.map +1 -1
- package/dist/src/mcp/internal-auth.d.ts +13 -0
- package/dist/src/mcp/internal-auth.d.ts.map +1 -0
- package/dist/src/mcp/internal-auth.js +12 -0
- package/dist/src/mcp/internal-auth.js.map +1 -0
- package/dist/src/mcp/tool-definitions.d.ts +41 -0
- package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
- package/dist/src/mcp/tool-definitions.js +491 -0
- package/dist/src/mcp/tool-definitions.js.map +1 -0
- package/dist/src/middleware/auth.js.map +1 -1
- package/dist/src/middleware/session.js.map +1 -1
- package/dist/src/middleware/validate.d.ts +8 -0
- package/dist/src/middleware/validate.d.ts.map +1 -1
- package/dist/src/middleware/validate.js +45 -0
- package/dist/src/middleware/validate.js.map +1 -1
- package/dist/src/policy/engine.d.ts +4 -0
- package/dist/src/policy/engine.d.ts.map +1 -1
- package/dist/src/policy/engine.js +117 -0
- package/dist/src/policy/engine.js.map +1 -1
- package/dist/src/saas/routes.d.ts.map +1 -1
- package/dist/src/saas/routes.js +355 -22
- package/dist/src/saas/routes.js.map +1 -1
- package/dist/src/server/app.d.ts.map +1 -1
- package/dist/src/server/app.js +24 -3
- package/dist/src/server/app.js.map +1 -1
- package/dist/src/server/gateway.d.ts.map +1 -1
- package/dist/src/server/gateway.js +17 -0
- package/dist/src/server/gateway.js.map +1 -1
- package/dist/src/server/index.d.ts.map +1 -1
- package/dist/src/server/index.js +18 -0
- package/dist/src/server/index.js.map +1 -1
- package/dist/src/storage/interfaces.d.ts +14 -3
- package/dist/src/storage/interfaces.d.ts.map +1 -1
- package/dist/src/storage/memory.d.ts +2 -0
- package/dist/src/storage/memory.d.ts.map +1 -1
- package/dist/src/storage/memory.js +6 -0
- package/dist/src/storage/memory.js.map +1 -1
- package/dist/src/storage/postgres.d.ts +5 -0
- package/dist/src/storage/postgres.d.ts.map +1 -1
- package/dist/src/storage/postgres.js +16 -0
- package/dist/src/storage/postgres.js.map +1 -1
- package/dist/src/storage/redis.d.ts +10 -0
- package/dist/src/storage/redis.d.ts.map +1 -1
- package/dist/src/storage/redis.js +65 -0
- package/dist/src/storage/redis.js.map +1 -1
- package/dist/src/types/budget.d.ts +4 -0
- package/dist/src/types/budget.d.ts.map +1 -1
- package/dist/src/types/config.d.ts +58 -0
- package/dist/src/types/config.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +1 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/policy.d.ts +11 -1
- package/dist/src/types/policy.d.ts.map +1 -1
- package/dist/src/types/tool-result.d.ts +11 -0
- package/dist/src/types/tool-result.d.ts.map +1 -1
- package/dist/tests/unit/app-routes.test.d.ts +2 -0
- package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/app-routes.test.js +715 -0
- package/dist/tests/unit/app-routes.test.js.map +1 -0
- package/dist/tests/unit/audit-logger.test.js +105 -0
- package/dist/tests/unit/audit-logger.test.js.map +1 -1
- package/dist/tests/unit/auth-providers.test.d.ts +2 -0
- package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-providers.test.js +279 -0
- package/dist/tests/unit/auth-providers.test.js.map +1 -0
- package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
- package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-routes-extended.test.js +993 -0
- package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
- package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
- package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-verifier.test.js +505 -0
- package/dist/tests/unit/auth-verifier.test.js.map +1 -0
- package/dist/tests/unit/billing-routes.test.d.ts +2 -0
- package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/billing-routes.test.js +432 -0
- package/dist/tests/unit/billing-routes.test.js.map +1 -0
- package/dist/tests/unit/config-defaults.test.d.ts +2 -0
- package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
- package/dist/tests/unit/config-defaults.test.js +119 -0
- package/dist/tests/unit/config-defaults.test.js.map +1 -0
- package/dist/tests/unit/defaults.test.js +0 -10
- package/dist/tests/unit/defaults.test.js.map +1 -1
- package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
- package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/filesystem-executor.test.js +280 -0
- package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
- package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
- package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/gateway-branches.test.js +1039 -0
- package/dist/tests/unit/gateway-branches.test.js.map +1 -0
- package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
- package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/http-executor-branches.test.js +495 -0
- package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
- package/dist/tests/unit/logger.test.d.ts +2 -0
- package/dist/tests/unit/logger.test.d.ts.map +1 -0
- package/dist/tests/unit/logger.test.js +97 -0
- package/dist/tests/unit/logger.test.js.map +1 -0
- package/dist/tests/unit/mcp-internal-auth.test.d.ts +2 -0
- package/dist/tests/unit/mcp-internal-auth.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-internal-auth.test.js +445 -0
- package/dist/tests/unit/mcp-internal-auth.test.js.map +1 -0
- package/dist/tests/unit/metrics.test.js +102 -0
- package/dist/tests/unit/metrics.test.js.map +1 -1
- package/dist/tests/unit/model-pricing.test.d.ts +2 -0
- package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
- package/dist/tests/unit/model-pricing.test.js +87 -0
- package/dist/tests/unit/model-pricing.test.js.map +1 -0
- package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
- package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
- package/dist/tests/unit/oauth-stores.test.js +260 -0
- package/dist/tests/unit/oauth-stores.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +466 -0
- package/dist/tests/unit/policy-engine.test.js.map +1 -1
- package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
- package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
- package/dist/tests/unit/provider-interceptor.test.js +472 -0
- package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-branches.test.js +2165 -0
- package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-crud.test.js +332 -0
- package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
- package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes-data.test.js +405 -0
- package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
- package/dist/tests/unit/saas-routes.test.js +3 -3
- package/dist/tests/unit/saas-routes.test.js.map +1 -1
- package/dist/tests/unit/shell-executor.test.d.ts +2 -0
- package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/shell-executor.test.js +145 -0
- package/dist/tests/unit/shell-executor.test.js.map +1 -0
- package/dist/tests/unit/sql-executor.test.d.ts +2 -0
- package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/sql-executor.test.js +177 -0
- package/dist/tests/unit/sql-executor.test.js.map +1 -0
- package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
- package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
- package/dist/tests/unit/stream-proxy.test.js +147 -0
- package/dist/tests/unit/stream-proxy.test.js.map +1 -0
- package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
- package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
- package/dist/tests/unit/tool-definitions.test.js +184 -0
- package/dist/tests/unit/tool-definitions.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor.test.js +140 -0
- package/dist/tests/unit/usage-extractor.test.js.map +1 -1
- package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
- package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
- package/dist/tests/unit/webhook-handler.test.js +453 -0
- package/dist/tests/unit/webhook-handler.test.js.map +1 -0
- package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
- package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/webhook-routes.test.js +69 -0
- package/dist/tests/unit/webhook-routes.test.js.map +1 -0
- package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
- package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/websocket-executor.test.js +121 -0
- package/dist/tests/unit/websocket-executor.test.js.map +1 -0
- package/package.json +8 -2
- package/policy-packs/demo_fail.yaml +41 -0
- package/policy-packs/full_tools.yaml +136 -0
- package/src/admin/index.ts +1 -0
- package/src/admin/routes.ts +509 -0
- package/src/admin/templates.ts +572 -0
- package/src/anomaly/detector.ts +730 -0
- package/src/anomaly/index.ts +1 -0
- package/src/approval/manager.ts +569 -0
- package/src/approval/webhook.ts +133 -0
- package/src/audit/logger.ts +490 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/password.ts +21 -0
- package/src/auth/pkce.ts +22 -0
- package/src/auth/providers.ts +208 -0
- package/src/auth/routes.ts +561 -0
- package/src/auth/session.ts +84 -0
- package/src/billing/index.ts +6 -0
- package/src/billing/plan-enforcer.ts +135 -0
- package/src/billing/routes.ts +229 -0
- package/src/billing/stripe-client.ts +58 -0
- package/src/billing/webhook-handler.ts +182 -0
- package/src/billing/webhook-routes.ts +28 -0
- package/src/budget/manager.ts +679 -0
- package/src/budget/model-pricing.ts +119 -0
- package/src/budget/usage-extractor.ts +214 -0
- package/src/cli.ts +91 -0
- package/src/config/defaults.ts +261 -0
- package/src/config/validate.ts +88 -0
- package/src/dlp/composite-scanner.ts +213 -0
- package/src/dlp/index.ts +9 -0
- package/src/dlp/interfaces.ts +34 -0
- package/src/dlp/patterns.ts +30 -0
- package/src/dlp/prompt-injection-backend.ts +181 -0
- package/src/dlp/prompt-injection-patterns.ts +302 -0
- package/src/dlp/regex-backend.ts +181 -0
- package/src/dlp/scanner.ts +502 -0
- package/src/dlp/text-normalizer.ts +225 -0
- package/src/dlp/tool-patterns.ts +35 -0
- package/src/dlp/trufflehog-backend.ts +190 -0
- package/src/executor/filesystem-executor.ts +196 -0
- package/src/executor/http-executor.ts +349 -0
- package/src/executor/index.ts +9 -0
- package/src/executor/interfaces.ts +11 -0
- package/src/executor/noop-executor.ts +23 -0
- package/src/executor/registry.ts +64 -0
- package/src/executor/shell-executor.ts +148 -0
- package/src/executor/slack-executor.ts +176 -0
- package/src/executor/sql-executor.ts +146 -0
- package/src/executor/websocket-executor.ts +211 -0
- package/src/index.ts +24 -0
- package/src/interceptor/index.ts +1 -0
- package/src/interceptor/provider-interceptor.ts +315 -0
- package/src/mcp/auth-verifier.ts +152 -0
- package/src/mcp/bridge.ts +703 -0
- package/src/mcp/http-transport.ts +698 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/internal-auth.ts +14 -0
- package/src/mcp/oauth-pages.ts +139 -0
- package/src/mcp/oauth-postgres-stores.ts +278 -0
- package/src/mcp/oauth-provider.ts +536 -0
- package/src/mcp/oauth-stores.ts +202 -0
- package/src/mcp/server.ts +55 -0
- package/src/mcp/tool-definitions.ts +562 -0
- package/src/metrics/collector.ts +357 -0
- package/src/metrics/index.ts +1 -0
- package/src/middleware/auth.ts +814 -0
- package/src/middleware/session.ts +85 -0
- package/src/middleware/validate.ts +130 -0
- package/src/policy/engine.ts +815 -0
- package/src/policy/index.ts +2 -0
- package/src/policy/opa-engine.ts +829 -0
- package/src/proxy/forward-proxy.ts +649 -0
- package/src/proxy/index.ts +1 -0
- package/src/ratelimit/limiter.ts +196 -0
- package/src/replay/engine.ts +142 -0
- package/src/replay/index.ts +1 -0
- package/src/saas/index.ts +1 -0
- package/src/saas/routes.ts +2178 -0
- package/src/server/app.ts +985 -0
- package/src/server/errors.ts +49 -0
- package/src/server/gateway.ts +1130 -0
- package/src/server/index.ts +307 -0
- package/src/server/logger.ts +255 -0
- package/src/server/stream-proxy.ts +202 -0
- package/src/storage/file-persistence.ts +315 -0
- package/src/storage/index.ts +4 -0
- package/src/storage/interfaces.ts +287 -0
- package/src/storage/memory.ts +686 -0
- package/src/storage/postgres.ts +1831 -0
- package/src/storage/redis.ts +835 -0
- package/src/tracing/index.ts +1 -0
- package/src/tracing/provider.ts +100 -0
- package/src/trust/calculator.ts +141 -0
- package/src/trust/index.ts +7 -0
- package/src/types/budget.ts +36 -0
- package/src/types/config.ts +278 -0
- package/src/types/events.ts +41 -0
- package/src/types/express.d.ts +14 -0
- package/src/types/index.ts +7 -0
- package/src/types/policy.ts +83 -0
- package/src/types/stripe-config.ts +11 -0
- package/src/types/subscription.ts +59 -0
- package/src/types/tool-call.ts +47 -0
- package/src/types/tool-result.ts +82 -0
- package/src/types/user.ts +125 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { TokenPricing } from '../types/budget';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Built-in model pricing database.
|
|
5
|
+
* Prices are per TOKEN (not per million). Updated March 2026.
|
|
6
|
+
*
|
|
7
|
+
* Fallback chain: workspace config → built-in pricing → undefined (falls to DEFAULT_COST_TABLE).
|
|
8
|
+
*/
|
|
9
|
+
export const MODEL_PRICING: Record<string, TokenPricing> = {
|
|
10
|
+
// ── Anthropic ──────────────────────────────────────────────
|
|
11
|
+
'claude-opus-4-6': { input_per_token: 15 / 1e6, output_per_token: 75 / 1e6 },
|
|
12
|
+
'claude-sonnet-4-6': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
|
|
13
|
+
'claude-haiku-4-5': { input_per_token: 1 / 1e6, output_per_token: 5 / 1e6 },
|
|
14
|
+
'claude-sonnet-4-5': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
|
|
15
|
+
'claude-3-5-sonnet-20241022': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
|
|
16
|
+
'claude-3-5-haiku-20241022': { input_per_token: 1 / 1e6, output_per_token: 5 / 1e6 },
|
|
17
|
+
'claude-3-opus-20240229': { input_per_token: 15 / 1e6, output_per_token: 75 / 1e6 },
|
|
18
|
+
'claude-3-sonnet-20240229': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
|
|
19
|
+
'claude-3-haiku-20240307': { input_per_token: 0.25 / 1e6, output_per_token: 1.25 / 1e6 },
|
|
20
|
+
|
|
21
|
+
// ── OpenAI ─────────────────────────────────────────────────
|
|
22
|
+
'gpt-4.1': { input_per_token: 2 / 1e6, output_per_token: 8 / 1e6 },
|
|
23
|
+
'gpt-4.1-mini': { input_per_token: 0.4 / 1e6, output_per_token: 1.6 / 1e6 },
|
|
24
|
+
'gpt-4.1-nano': { input_per_token: 0.1 / 1e6, output_per_token: 0.4 / 1e6 },
|
|
25
|
+
'gpt-4o': { input_per_token: 2.5 / 1e6, output_per_token: 10 / 1e6 },
|
|
26
|
+
'gpt-4o-mini': { input_per_token: 0.15 / 1e6, output_per_token: 0.6 / 1e6 },
|
|
27
|
+
'gpt-4-turbo': { input_per_token: 10 / 1e6, output_per_token: 30 / 1e6 },
|
|
28
|
+
'o3': { input_per_token: 2 / 1e6, output_per_token: 8 / 1e6 },
|
|
29
|
+
'o3-mini': { input_per_token: 1.1 / 1e6, output_per_token: 4.4 / 1e6 },
|
|
30
|
+
'o1': { input_per_token: 15 / 1e6, output_per_token: 60 / 1e6 },
|
|
31
|
+
'o1-mini': { input_per_token: 3 / 1e6, output_per_token: 12 / 1e6 },
|
|
32
|
+
|
|
33
|
+
// ── Google ─────────────────────────────────────────────────
|
|
34
|
+
'gemini-2.5-pro': { input_per_token: 1.25 / 1e6, output_per_token: 10 / 1e6 },
|
|
35
|
+
'gemini-2.5-flash': { input_per_token: 0.15 / 1e6, output_per_token: 0.6 / 1e6 },
|
|
36
|
+
'gemini-2.0-flash': { input_per_token: 0.1 / 1e6, output_per_token: 0.4 / 1e6 },
|
|
37
|
+
'gemini-1.5-pro': { input_per_token: 1.25 / 1e6, output_per_token: 5 / 1e6 },
|
|
38
|
+
'gemini-1.5-flash': { input_per_token: 0.075 / 1e6, output_per_token: 0.3 / 1e6 },
|
|
39
|
+
|
|
40
|
+
// ── Mistral ────────────────────────────────────────────────
|
|
41
|
+
'mistral-large-latest': { input_per_token: 2 / 1e6, output_per_token: 6 / 1e6 },
|
|
42
|
+
'mistral-small-latest': { input_per_token: 0.2 / 1e6, output_per_token: 0.6 / 1e6 },
|
|
43
|
+
'codestral-latest': { input_per_token: 0.3 / 1e6, output_per_token: 0.9 / 1e6 },
|
|
44
|
+
|
|
45
|
+
// ── Meta (via API providers) ───────────────────────────────
|
|
46
|
+
'llama-3.3-70b': { input_per_token: 0.8 / 1e6, output_per_token: 0.8 / 1e6 },
|
|
47
|
+
'llama-3.1-405b': { input_per_token: 3 / 1e6, output_per_token: 3 / 1e6 },
|
|
48
|
+
'llama-3.1-70b': { input_per_token: 0.8 / 1e6, output_per_token: 0.8 / 1e6 },
|
|
49
|
+
'llama-3.1-8b': { input_per_token: 0.1 / 1e6, output_per_token: 0.1 / 1e6 },
|
|
50
|
+
|
|
51
|
+
// ── Cohere ─────────────────────────────────────────────────
|
|
52
|
+
'command-r-plus': { input_per_token: 2.5 / 1e6, output_per_token: 10 / 1e6 },
|
|
53
|
+
'command-r': { input_per_token: 0.15 / 1e6, output_per_token: 0.6 / 1e6 },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Alias map for model name normalization.
|
|
58
|
+
* Maps common variations/prefixes to canonical model names in MODEL_PRICING.
|
|
59
|
+
*/
|
|
60
|
+
const MODEL_ALIASES: Record<string, string> = {
|
|
61
|
+
// OpenAI versioned snapshots
|
|
62
|
+
'gpt-4o-2024-11-20': 'gpt-4o',
|
|
63
|
+
'gpt-4o-2024-08-06': 'gpt-4o',
|
|
64
|
+
'gpt-4o-mini-2024-07-18': 'gpt-4o-mini',
|
|
65
|
+
'gpt-4-turbo-2024-04-09': 'gpt-4-turbo',
|
|
66
|
+
// Anthropic shortcuts
|
|
67
|
+
'claude-3.5-sonnet': 'claude-sonnet-4-5',
|
|
68
|
+
'claude-3.5-haiku': 'claude-haiku-4-5',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve token pricing for a model name.
|
|
73
|
+
*
|
|
74
|
+
* Lookup order:
|
|
75
|
+
* 1. Exact match in `overrides` (workspace-level config)
|
|
76
|
+
* 2. Exact match in built-in MODEL_PRICING
|
|
77
|
+
* 3. Alias lookup
|
|
78
|
+
* 4. Prefix match (e.g. "gpt-4o-2024-05-13" → "gpt-4o")
|
|
79
|
+
* 5. undefined — caller falls back to DEFAULT_COST_TABLE
|
|
80
|
+
*/
|
|
81
|
+
export function resolveModelPricing(
|
|
82
|
+
modelName: string,
|
|
83
|
+
overrides?: Record<string, TokenPricing>,
|
|
84
|
+
): TokenPricing | undefined {
|
|
85
|
+
// 1. Workspace override (exact)
|
|
86
|
+
if (overrides?.[modelName]) return overrides[modelName];
|
|
87
|
+
|
|
88
|
+
// 2. Built-in exact match
|
|
89
|
+
if (MODEL_PRICING[modelName]) return MODEL_PRICING[modelName];
|
|
90
|
+
|
|
91
|
+
// 3. Alias lookup
|
|
92
|
+
const aliased = MODEL_ALIASES[modelName];
|
|
93
|
+
if (aliased) {
|
|
94
|
+
return overrides?.[aliased] ?? MODEL_PRICING[aliased];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 4. Prefix match — find the longest matching key
|
|
98
|
+
// e.g. "claude-3-5-sonnet-20241022-v2" matches "claude-3-5-sonnet-20241022"
|
|
99
|
+
let bestMatch: string | undefined;
|
|
100
|
+
let bestLen = 0;
|
|
101
|
+
|
|
102
|
+
const allKeys = new Set([
|
|
103
|
+
...Object.keys(MODEL_PRICING),
|
|
104
|
+
...(overrides ? Object.keys(overrides) : []),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
for (const key of allKeys) {
|
|
108
|
+
if (modelName.startsWith(key) && key.length > bestLen) {
|
|
109
|
+
bestMatch = key;
|
|
110
|
+
bestLen = key.length;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (bestMatch) {
|
|
115
|
+
return overrides?.[bestMatch] ?? MODEL_PRICING[bestMatch];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { UsageData } from '../types/tool-result';
|
|
2
|
+
import { TokenPricing } from '../types/budget';
|
|
3
|
+
import { resolveModelPricing } from './model-pricing';
|
|
4
|
+
|
|
5
|
+
export class UsageExtractor {
|
|
6
|
+
private tokenPricing: Record<string, TokenPricing>;
|
|
7
|
+
|
|
8
|
+
constructor(tokenPricing?: Record<string, TokenPricing>) {
|
|
9
|
+
this.tokenPricing = tokenPricing || {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract usage data from HTTP response headers.
|
|
14
|
+
* Supports Anthropic, OpenAI-style, and generic header conventions.
|
|
15
|
+
*/
|
|
16
|
+
extractFromHeaders(headers?: Record<string, string>): UsageData | undefined {
|
|
17
|
+
if (!headers) return undefined;
|
|
18
|
+
|
|
19
|
+
// Normalize header keys to lowercase for case-insensitive matching
|
|
20
|
+
const normalized: Record<string, string> = {};
|
|
21
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
22
|
+
normalized[key.toLowerCase()] = value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const inputTokens = this.parseIntHeader(normalized['x-usage-input-tokens']);
|
|
26
|
+
const outputTokens = this.parseIntHeader(normalized['x-usage-output-tokens']);
|
|
27
|
+
const totalTokens = this.parseIntHeader(normalized['x-usage-total-tokens']);
|
|
28
|
+
const providerCost = this.parseFloatHeader(normalized['x-usage-cost-usd']);
|
|
29
|
+
|
|
30
|
+
if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined && providerCost === undefined) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const usage: UsageData = {
|
|
35
|
+
source: 'headers',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (inputTokens !== undefined) usage.input_tokens = inputTokens;
|
|
39
|
+
if (outputTokens !== undefined) usage.output_tokens = outputTokens;
|
|
40
|
+
if (totalTokens !== undefined) {
|
|
41
|
+
usage.total_tokens = totalTokens;
|
|
42
|
+
} else if (inputTokens !== undefined && outputTokens !== undefined) {
|
|
43
|
+
usage.total_tokens = inputTokens + outputTokens;
|
|
44
|
+
}
|
|
45
|
+
if (providerCost !== undefined) usage.provider_cost_usd = providerCost;
|
|
46
|
+
|
|
47
|
+
return usage;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract usage data from response body.
|
|
52
|
+
* Supports OpenAI-style `usage` object and Anthropic-style `usage` object,
|
|
53
|
+
* including cache tokens (Anthropic) and reasoning tokens (OpenAI o1/o3).
|
|
54
|
+
*/
|
|
55
|
+
extractFromBody(body: unknown): UsageData | undefined {
|
|
56
|
+
if (!body || typeof body !== 'object') return undefined;
|
|
57
|
+
|
|
58
|
+
const bodyObj = body as Record<string, unknown>;
|
|
59
|
+
const usageObj = bodyObj.usage as Record<string, unknown> | undefined;
|
|
60
|
+
|
|
61
|
+
if (!usageObj || typeof usageObj !== 'object') return undefined;
|
|
62
|
+
|
|
63
|
+
// OpenAI format: usage.prompt_tokens, usage.completion_tokens, usage.total_tokens
|
|
64
|
+
const promptTokens = typeof usageObj.prompt_tokens === 'number' ? usageObj.prompt_tokens : undefined;
|
|
65
|
+
const completionTokens = typeof usageObj.completion_tokens === 'number' ? usageObj.completion_tokens : undefined;
|
|
66
|
+
const totalTokens = typeof usageObj.total_tokens === 'number' ? usageObj.total_tokens : undefined;
|
|
67
|
+
|
|
68
|
+
// Anthropic format: usage.input_tokens, usage.output_tokens
|
|
69
|
+
const inputTokens = typeof usageObj.input_tokens === 'number' ? usageObj.input_tokens : promptTokens;
|
|
70
|
+
const outputTokens = typeof usageObj.output_tokens === 'number' ? usageObj.output_tokens : completionTokens;
|
|
71
|
+
|
|
72
|
+
// Anthropic cache tokens
|
|
73
|
+
const cacheCreationTokens = typeof usageObj.cache_creation_input_tokens === 'number'
|
|
74
|
+
? usageObj.cache_creation_input_tokens : undefined;
|
|
75
|
+
const cacheReadTokens = typeof usageObj.cache_read_input_tokens === 'number'
|
|
76
|
+
? usageObj.cache_read_input_tokens : undefined;
|
|
77
|
+
|
|
78
|
+
// OpenAI reasoning tokens (o1/o3 models)
|
|
79
|
+
// Located at usage.completion_tokens_details.reasoning_tokens
|
|
80
|
+
let reasoningTokens: number | undefined;
|
|
81
|
+
const completionDetails = usageObj.completion_tokens_details as Record<string, unknown> | undefined;
|
|
82
|
+
if (completionDetails && typeof completionDetails === 'object') {
|
|
83
|
+
reasoningTokens = typeof completionDetails.reasoning_tokens === 'number'
|
|
84
|
+
? completionDetails.reasoning_tokens : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const usage: UsageData = {
|
|
92
|
+
source: 'body',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (inputTokens !== undefined) usage.input_tokens = inputTokens;
|
|
96
|
+
if (outputTokens !== undefined) usage.output_tokens = outputTokens;
|
|
97
|
+
if (totalTokens !== undefined) {
|
|
98
|
+
usage.total_tokens = totalTokens;
|
|
99
|
+
} else if (inputTokens !== undefined && outputTokens !== undefined) {
|
|
100
|
+
usage.total_tokens = inputTokens + outputTokens;
|
|
101
|
+
}
|
|
102
|
+
if (cacheCreationTokens !== undefined) usage.cache_creation_tokens = cacheCreationTokens;
|
|
103
|
+
if (cacheReadTokens !== undefined) usage.cache_read_tokens = cacheReadTokens;
|
|
104
|
+
if (reasoningTokens !== undefined) usage.reasoning_tokens = reasoningTokens;
|
|
105
|
+
|
|
106
|
+
return usage;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Compute cost from usage data using token pricing config.
|
|
111
|
+
* Falls back to built-in MODEL_PRICING when workspace config doesn't have the model.
|
|
112
|
+
* Returns the computed cost in USD, or undefined if pricing not available.
|
|
113
|
+
*/
|
|
114
|
+
computeCost(usage: UsageData, model?: string): number | undefined {
|
|
115
|
+
if (!model) return undefined;
|
|
116
|
+
|
|
117
|
+
// Resolve pricing: workspace config → built-in pricing → undefined
|
|
118
|
+
const pricing = resolveModelPricing(model, this.tokenPricing);
|
|
119
|
+
if (!pricing) return undefined;
|
|
120
|
+
|
|
121
|
+
let cost = 0;
|
|
122
|
+
|
|
123
|
+
// Base input tokens (excluding cache tokens which are billed differently)
|
|
124
|
+
if (usage.input_tokens !== undefined) {
|
|
125
|
+
cost += usage.input_tokens * pricing.input_per_token;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Base output tokens
|
|
129
|
+
if (usage.output_tokens !== undefined) {
|
|
130
|
+
cost += usage.output_tokens * pricing.output_per_token;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Cache creation tokens: billed at cache_creation_multiplier × input price (default 1.25x)
|
|
134
|
+
if (usage.cache_creation_tokens !== undefined && usage.cache_creation_tokens > 0) {
|
|
135
|
+
const multiplier = pricing.cache_creation_multiplier ?? 1.25;
|
|
136
|
+
cost += usage.cache_creation_tokens * pricing.input_per_token * multiplier;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Cache read tokens: billed at cache_read_multiplier × input price (default 0.1x)
|
|
140
|
+
if (usage.cache_read_tokens !== undefined && usage.cache_read_tokens > 0) {
|
|
141
|
+
const multiplier = pricing.cache_read_multiplier ?? 0.1;
|
|
142
|
+
cost += usage.cache_read_tokens * pricing.input_per_token * multiplier;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Reasoning tokens (o1/o3): billed at output price
|
|
146
|
+
if (usage.reasoning_tokens !== undefined && usage.reasoning_tokens > 0) {
|
|
147
|
+
cost += usage.reasoning_tokens * pricing.output_per_token;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return cost;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Extract model name from response body.
|
|
155
|
+
* Supports OpenAI format (body.model) and Anthropic format (body.model).
|
|
156
|
+
*/
|
|
157
|
+
extractModelFromBody(body: unknown): string | undefined {
|
|
158
|
+
if (!body || typeof body !== 'object') return undefined;
|
|
159
|
+
const bodyObj = body as Record<string, unknown>;
|
|
160
|
+
if (typeof bodyObj.model === 'string') return bodyObj.model;
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Detect provider from model name.
|
|
166
|
+
*/
|
|
167
|
+
detectProvider(model: string): string {
|
|
168
|
+
if (/^claude/i.test(model)) return 'anthropic';
|
|
169
|
+
if (/^gpt|^o[1-9]|^dall-e/i.test(model)) return 'openai';
|
|
170
|
+
if (/^gemini/i.test(model)) return 'google';
|
|
171
|
+
if (/^mistral|^mixtral/i.test(model)) return 'mistral';
|
|
172
|
+
if (/^llama/i.test(model)) return 'meta';
|
|
173
|
+
if (/^command/i.test(model)) return 'cohere';
|
|
174
|
+
return 'unknown';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Merge two UsageData objects. The second (body) takes precedence for conflicting fields,
|
|
179
|
+
* except provider_cost_usd which is preferred from headers.
|
|
180
|
+
*/
|
|
181
|
+
merge(fromHeaders?: UsageData, fromBody?: UsageData): UsageData | undefined {
|
|
182
|
+
if (!fromHeaders && !fromBody) return undefined;
|
|
183
|
+
if (!fromHeaders) return fromBody;
|
|
184
|
+
if (!fromBody) return fromHeaders;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
input_tokens: fromBody.input_tokens ?? fromHeaders.input_tokens,
|
|
188
|
+
output_tokens: fromBody.output_tokens ?? fromHeaders.output_tokens,
|
|
189
|
+
total_tokens: fromBody.total_tokens ?? fromHeaders.total_tokens,
|
|
190
|
+
cache_creation_tokens: fromBody.cache_creation_tokens ?? fromHeaders.cache_creation_tokens,
|
|
191
|
+
cache_read_tokens: fromBody.cache_read_tokens ?? fromHeaders.cache_read_tokens,
|
|
192
|
+
reasoning_tokens: fromBody.reasoning_tokens ?? fromHeaders.reasoning_tokens,
|
|
193
|
+
provider_cost_usd: fromHeaders.provider_cost_usd ?? fromBody.provider_cost_usd,
|
|
194
|
+
computed_cost_usd: fromHeaders.computed_cost_usd ?? fromBody.computed_cost_usd,
|
|
195
|
+
source: fromBody.source && fromHeaders.source
|
|
196
|
+
? `${fromHeaders.source}+${fromBody.source}`
|
|
197
|
+
: fromBody.source || fromHeaders.source,
|
|
198
|
+
model: fromBody.model ?? fromHeaders.model,
|
|
199
|
+
provider: fromBody.provider ?? fromHeaders.provider,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private parseIntHeader(value?: string): number | undefined {
|
|
204
|
+
if (value === undefined) return undefined;
|
|
205
|
+
const parsed = parseInt(value, 10);
|
|
206
|
+
return isNaN(parsed) ? undefined : parsed;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private parseFloatHeader(value?: string): number | undefined {
|
|
210
|
+
if (value === undefined) return undefined;
|
|
211
|
+
const parsed = parseFloat(value);
|
|
212
|
+
return isNaN(parsed) ? undefined : parsed;
|
|
213
|
+
}
|
|
214
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
|
|
8
|
+
// Resolve the package root (two levels up from dist/src/cli.js)
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
10
|
+
|
|
11
|
+
// Read version from package.json
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('palaryn')
|
|
18
|
+
.description('Model-agnostic infrastructure layer for AI agent I/O security')
|
|
19
|
+
.version(pkg.version);
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command('start', { isDefault: true })
|
|
23
|
+
.description('Start the gateway server')
|
|
24
|
+
.option('-p, --port <port>', 'Server port', '3000')
|
|
25
|
+
.option('--host <host>', 'Bind address', '0.0.0.0')
|
|
26
|
+
.option('--policy-pack <path>', 'Path to policy pack YAML')
|
|
27
|
+
.option('--audit-log-dir <path>', 'Audit log directory')
|
|
28
|
+
.option('--no-auth', 'Disable authentication')
|
|
29
|
+
.action(async (opts) => {
|
|
30
|
+
// Set env vars from CLI flags BEFORE importing server modules
|
|
31
|
+
// (DEFAULT_CONFIG reads env vars at import time)
|
|
32
|
+
process.env.PORT = opts.port;
|
|
33
|
+
process.env.HOST = opts.host;
|
|
34
|
+
|
|
35
|
+
if (opts.policyPack) {
|
|
36
|
+
process.env.POLICY_PACK_PATH = opts.policyPack;
|
|
37
|
+
} else if (!process.env.POLICY_PACK_PATH) {
|
|
38
|
+
// Resolve default policy pack relative to the package installation dir
|
|
39
|
+
const bundledPack = path.join(PACKAGE_ROOT, 'policy-packs', 'default.yaml');
|
|
40
|
+
if (fs.existsSync(bundledPack)) {
|
|
41
|
+
process.env.POLICY_PACK_PATH = bundledPack;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (opts.auditLogDir) {
|
|
46
|
+
process.env.AUDIT_LOG_DIR = opts.auditLogDir;
|
|
47
|
+
} else if (!process.env.AUDIT_LOG_DIR) {
|
|
48
|
+
// Default audit logs to ~/.palaryn/logs for global installs
|
|
49
|
+
const defaultLogDir = path.join(os.homedir(), '.palaryn', 'logs');
|
|
50
|
+
fs.mkdirSync(defaultLogDir, { recursive: true });
|
|
51
|
+
process.env.AUDIT_LOG_DIR = defaultLogDir;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!opts.auth) {
|
|
55
|
+
process.env.AUTH_ENABLED = 'false';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Now import and start the server (env vars are set)
|
|
59
|
+
const { startServer } = await import('./server/index');
|
|
60
|
+
await startServer();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
program
|
|
64
|
+
.command('mcp')
|
|
65
|
+
.description('Start MCP stdio server (for claude mcp add palaryn -- palaryn mcp)')
|
|
66
|
+
.option('--workspace <id>', 'Workspace ID')
|
|
67
|
+
.option('--actor <id>', 'Actor ID')
|
|
68
|
+
.action(async (opts) => {
|
|
69
|
+
if (opts.workspace) {
|
|
70
|
+
process.env.PALARYN_MCP_WORKSPACE = opts.workspace;
|
|
71
|
+
}
|
|
72
|
+
if (opts.actor) {
|
|
73
|
+
process.env.PALARYN_MCP_ACTOR = opts.actor;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Resolve default policy pack for MCP server too
|
|
77
|
+
if (!process.env.POLICY_PACK_PATH) {
|
|
78
|
+
const bundledPack = path.join(PACKAGE_ROOT, 'policy-packs', 'default.yaml');
|
|
79
|
+
if (fs.existsSync(bundledPack)) {
|
|
80
|
+
process.env.POLICY_PACK_PATH = bundledPack;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { startMCPServer } = await import('./mcp/server');
|
|
85
|
+
await startMCPServer();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
89
|
+
console.error('Fatal error:', err instanceof Error ? err.message : String(err));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { GatewayConfig } from '../types/config';
|
|
4
|
+
import { MODEL_PRICING } from '../budget/model-pricing';
|
|
5
|
+
|
|
6
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read a secret from a file path or env var.
|
|
10
|
+
* Lookup order:
|
|
11
|
+
* 1. `<ENV_VAR>_FILE` — path to a file whose contents are the secret
|
|
12
|
+
* (e.g. `/run/secrets/stripe_key`). Trailing whitespace is stripped.
|
|
13
|
+
* 2. `<ENV_VAR>` — plain env var (fallback, dev convenience).
|
|
14
|
+
*
|
|
15
|
+
* This lets you use Docker secrets, Kubernetes secret volumes, or any
|
|
16
|
+
* file-based secret manager without exposing values in env vars.
|
|
17
|
+
*/
|
|
18
|
+
function readSecret(envVar: string): string | undefined {
|
|
19
|
+
const filePath = process.env[`${envVar}_FILE`];
|
|
20
|
+
if (filePath) {
|
|
21
|
+
try {
|
|
22
|
+
return fs.readFileSync(filePath, 'utf-8').trim();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
throw new Error(`Failed to read secret file for ${envVar}_FILE (${filePath}): ${msg}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return process.env[envVar];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function requireSecret(envVar: string): string {
|
|
32
|
+
const value = readSecret(envVar);
|
|
33
|
+
if (value) return value;
|
|
34
|
+
if (isProduction) {
|
|
35
|
+
throw new Error(`${envVar} (or ${envVar}_FILE) must be set in production`);
|
|
36
|
+
}
|
|
37
|
+
const generated = crypto.randomBytes(32).toString('hex');
|
|
38
|
+
console.warn(`[config] WARNING: ${envVar} not set — generated random dev secret. Set ${envVar} or ${envVar}_FILE for stable restarts.`);
|
|
39
|
+
return generated;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const DEFAULT_CONFIG: GatewayConfig = {
|
|
43
|
+
port: parseInt(process.env.PORT || '3000', 10),
|
|
44
|
+
host: process.env.HOST || '0.0.0.0',
|
|
45
|
+
auth: {
|
|
46
|
+
enabled: process.env.AUTH_ENABLED !== 'false',
|
|
47
|
+
api_keys: isProduction ? {} : {
|
|
48
|
+
'dev-key-001': { workspace_id: 'ws_default', description: 'Development key' },
|
|
49
|
+
'pn_2b922eca4de8b8666f1dcc72d0763de9440deebec42e9277a1a82352771362f3': { workspace_id: 'd214b8e5-da34-41a1-bf30-ae6c0e7dd50c', description: 'Android app key' },
|
|
50
|
+
},
|
|
51
|
+
jwt_secret: requireSecret('JWT_SECRET'),
|
|
52
|
+
jwt: {
|
|
53
|
+
enabled: false,
|
|
54
|
+
secret: process.env.JWT_SECRET,
|
|
55
|
+
algorithms: ['RS256', 'HS256'],
|
|
56
|
+
workspace_claim: 'workspace_id',
|
|
57
|
+
roles_claim: 'roles',
|
|
58
|
+
actor_claim: 'sub',
|
|
59
|
+
},
|
|
60
|
+
rbac: {
|
|
61
|
+
enabled: false, // Off by default for backwards compat
|
|
62
|
+
roles: {
|
|
63
|
+
admin: {
|
|
64
|
+
description: 'Full admin access',
|
|
65
|
+
permissions: ['admin:full'],
|
|
66
|
+
},
|
|
67
|
+
operator: {
|
|
68
|
+
description: 'Can execute tools and manage approvals',
|
|
69
|
+
permissions: ['tool:execute', 'approval:manage', 'trace:read', 'policy:read'],
|
|
70
|
+
},
|
|
71
|
+
readonly: {
|
|
72
|
+
description: 'Read-only access',
|
|
73
|
+
permissions: ['tool:execute:read', 'trace:read', 'policy:read'],
|
|
74
|
+
},
|
|
75
|
+
agent: {
|
|
76
|
+
description: 'AI agent - can execute tools',
|
|
77
|
+
permissions: ['tool:execute'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
default_role: 'agent',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
policy: {
|
|
84
|
+
pack_path: process.env.POLICY_PACK_PATH || './policy-packs/default.yaml',
|
|
85
|
+
default_effect: 'DENY',
|
|
86
|
+
hot_reload: true,
|
|
87
|
+
},
|
|
88
|
+
dlp: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
scan_args: true,
|
|
91
|
+
scan_output: true,
|
|
92
|
+
secrets_detection: true,
|
|
93
|
+
pii_detection: true,
|
|
94
|
+
prompt_injection_detection: true,
|
|
95
|
+
prompt_injection_action: 'log',
|
|
96
|
+
prompt_injection_block_threshold: 'high',
|
|
97
|
+
prompt_injection_response: 'deny',
|
|
98
|
+
default_redaction_method: 'mask',
|
|
99
|
+
},
|
|
100
|
+
budget: {
|
|
101
|
+
task_budget_usd: process.env.BUDGET_TASK_USD
|
|
102
|
+
? parseFloat(process.env.BUDGET_TASK_USD) : 2.0,
|
|
103
|
+
user_daily_budget_usd: process.env.BUDGET_USER_DAILY_USD
|
|
104
|
+
? parseFloat(process.env.BUDGET_USER_DAILY_USD) : 50.0,
|
|
105
|
+
user_monthly_budget_usd: process.env.BUDGET_USER_MONTHLY_USD
|
|
106
|
+
? parseFloat(process.env.BUDGET_USER_MONTHLY_USD) : 500.0,
|
|
107
|
+
workspace_daily_budget_usd: process.env.BUDGET_WORKSPACE_DAILY_USD
|
|
108
|
+
? parseFloat(process.env.BUDGET_WORKSPACE_DAILY_USD) : 200.0,
|
|
109
|
+
workspace_monthly_budget_usd: process.env.BUDGET_WORKSPACE_MONTHLY_USD
|
|
110
|
+
? parseFloat(process.env.BUDGET_WORKSPACE_MONTHLY_USD) : 5000.0,
|
|
111
|
+
max_steps_per_task: process.env.BUDGET_MAX_STEPS
|
|
112
|
+
? parseInt(process.env.BUDGET_MAX_STEPS, 10) : 50,
|
|
113
|
+
max_retries_per_call: 3,
|
|
114
|
+
max_wall_clock_ms: 300000,
|
|
115
|
+
token_pricing: MODEL_PRICING,
|
|
116
|
+
},
|
|
117
|
+
audit: {
|
|
118
|
+
enabled: true,
|
|
119
|
+
log_dir: process.env.AUDIT_LOG_DIR || './logs',
|
|
120
|
+
console_output: !isProduction,
|
|
121
|
+
retention_days: 30,
|
|
122
|
+
},
|
|
123
|
+
executor: {
|
|
124
|
+
http: {
|
|
125
|
+
timeout_ms: 15000,
|
|
126
|
+
max_retries: 3,
|
|
127
|
+
backoff_base_ms: 1000,
|
|
128
|
+
},
|
|
129
|
+
cache: {
|
|
130
|
+
enabled: true,
|
|
131
|
+
ttl_ms: 300000,
|
|
132
|
+
},
|
|
133
|
+
filesystem: process.env.PALARYN_FILE_ENABLED === 'true' ? {
|
|
134
|
+
enabled: true,
|
|
135
|
+
base_dir: process.env.PALARYN_FILE_BASE_DIR || './sandbox',
|
|
136
|
+
allowed_extensions: process.env.PALARYN_FILE_EXTENSIONS
|
|
137
|
+
? process.env.PALARYN_FILE_EXTENSIONS.split(',').map(s => s.trim())
|
|
138
|
+
: undefined,
|
|
139
|
+
max_file_size_bytes: process.env.PALARYN_FILE_MAX_SIZE
|
|
140
|
+
? parseInt(process.env.PALARYN_FILE_MAX_SIZE, 10)
|
|
141
|
+
: 10 * 1024 * 1024,
|
|
142
|
+
} : undefined,
|
|
143
|
+
sql: process.env.PALARYN_SQL_ENABLED === 'true' ? {
|
|
144
|
+
enabled: true,
|
|
145
|
+
connection_string: process.env.PALARYN_SQL_CONNECTION || '',
|
|
146
|
+
timeout_ms: process.env.PALARYN_SQL_TIMEOUT
|
|
147
|
+
? parseInt(process.env.PALARYN_SQL_TIMEOUT, 10)
|
|
148
|
+
: 30000,
|
|
149
|
+
read_only: process.env.PALARYN_SQL_READONLY !== 'false',
|
|
150
|
+
max_rows: process.env.PALARYN_SQL_MAX_ROWS
|
|
151
|
+
? parseInt(process.env.PALARYN_SQL_MAX_ROWS, 10)
|
|
152
|
+
: 1000,
|
|
153
|
+
blocked_tables: process.env.PALARYN_SQL_BLOCKED_TABLES
|
|
154
|
+
? process.env.PALARYN_SQL_BLOCKED_TABLES.split(',').map(s => s.trim())
|
|
155
|
+
: undefined,
|
|
156
|
+
} : undefined,
|
|
157
|
+
shell: process.env.PALARYN_SHELL_ENABLED === 'true' ? {
|
|
158
|
+
enabled: true,
|
|
159
|
+
allowed_commands: process.env.PALARYN_SHELL_ALLOWED
|
|
160
|
+
? process.env.PALARYN_SHELL_ALLOWED.split(',').map(s => s.trim())
|
|
161
|
+
: [],
|
|
162
|
+
blocked_commands: process.env.PALARYN_SHELL_BLOCKED
|
|
163
|
+
? process.env.PALARYN_SHELL_BLOCKED.split(',').map(s => s.trim())
|
|
164
|
+
: undefined,
|
|
165
|
+
timeout_ms: process.env.PALARYN_SHELL_TIMEOUT
|
|
166
|
+
? parseInt(process.env.PALARYN_SHELL_TIMEOUT, 10)
|
|
167
|
+
: 30000,
|
|
168
|
+
cwd: process.env.PALARYN_SHELL_CWD,
|
|
169
|
+
max_output_bytes: process.env.PALARYN_SHELL_MAX_OUTPUT
|
|
170
|
+
? parseInt(process.env.PALARYN_SHELL_MAX_OUTPUT, 10)
|
|
171
|
+
: 1024 * 1024,
|
|
172
|
+
} : undefined,
|
|
173
|
+
websocket: process.env.PALARYN_WS_ENABLED === 'true' ? {
|
|
174
|
+
enabled: true,
|
|
175
|
+
allowed_urls: process.env.PALARYN_WS_ALLOWED_URLS
|
|
176
|
+
? process.env.PALARYN_WS_ALLOWED_URLS.split(',').map(s => s.trim())
|
|
177
|
+
: [],
|
|
178
|
+
connect_timeout_ms: process.env.PALARYN_WS_CONNECT_TIMEOUT
|
|
179
|
+
? parseInt(process.env.PALARYN_WS_CONNECT_TIMEOUT, 10)
|
|
180
|
+
: 10000,
|
|
181
|
+
max_message_size_bytes: process.env.PALARYN_WS_MAX_MSG_SIZE
|
|
182
|
+
? parseInt(process.env.PALARYN_WS_MAX_MSG_SIZE, 10)
|
|
183
|
+
: 1024 * 1024,
|
|
184
|
+
} : undefined,
|
|
185
|
+
provider_intercept: process.env.PALARYN_PROVIDER_INTERCEPT_ENABLED === 'true' ? {
|
|
186
|
+
enabled: true,
|
|
187
|
+
provider_url_patterns: process.env.PALARYN_PROVIDER_URL_PATTERNS
|
|
188
|
+
? process.env.PALARYN_PROVIDER_URL_PATTERNS.split(',').map(s => s.trim())
|
|
189
|
+
: ['api\\.anthropic\\.com', 'api\\.openai\\.com', 'generativelanguage\\.googleapis\\.com'],
|
|
190
|
+
scan_inputs: process.env.PALARYN_PROVIDER_SCAN_INPUTS !== 'false',
|
|
191
|
+
scan_outputs: process.env.PALARYN_PROVIDER_SCAN_OUTPUTS !== 'false',
|
|
192
|
+
} : undefined,
|
|
193
|
+
},
|
|
194
|
+
approval: {
|
|
195
|
+
enabled: true,
|
|
196
|
+
token_secret: requireSecret('APPROVAL_SECRET'),
|
|
197
|
+
default_ttl_seconds: 3600,
|
|
198
|
+
},
|
|
199
|
+
rate_limit: {
|
|
200
|
+
enabled: true,
|
|
201
|
+
actor_max_per_window: process.env.RATE_LIMIT_ACTOR_MAX
|
|
202
|
+
? parseInt(process.env.RATE_LIMIT_ACTOR_MAX, 10) : 100,
|
|
203
|
+
workspace_max_per_window: process.env.RATE_LIMIT_WORKSPACE_MAX
|
|
204
|
+
? parseInt(process.env.RATE_LIMIT_WORKSPACE_MAX, 10) : 500,
|
|
205
|
+
window_ms: process.env.RATE_LIMIT_WINDOW_MS
|
|
206
|
+
? parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) : 60000,
|
|
207
|
+
},
|
|
208
|
+
anomaly: {
|
|
209
|
+
enabled: true,
|
|
210
|
+
window_ms: 300000, // 5 minutes (shorter for dev testing)
|
|
211
|
+
z_score_threshold: 2, // Lower threshold for easier testing
|
|
212
|
+
min_samples: 5, // Need only 5 data points before detection activates
|
|
213
|
+
action: 'flag', // 'flag' adds to result metadata; change to 'block' to deny
|
|
214
|
+
track_actors: true,
|
|
215
|
+
track_tools: true,
|
|
216
|
+
track_workspaces: true,
|
|
217
|
+
},
|
|
218
|
+
proxy: {
|
|
219
|
+
enabled: process.env.PROXY_ENABLED === 'true',
|
|
220
|
+
port: parseInt(process.env.PROXY_PORT || '3128', 10),
|
|
221
|
+
passthrough_domains: process.env.PROXY_PASSTHROUGH_DOMAINS
|
|
222
|
+
? process.env.PROXY_PASSTHROUGH_DOMAINS.split(',').map(s => s.trim())
|
|
223
|
+
: undefined,
|
|
224
|
+
default_workspace_id: process.env.PALARYN_WORKSPACE_ID,
|
|
225
|
+
default_actor_id: process.env.PALARYN_ACTOR_ID,
|
|
226
|
+
require_auth: process.env.PROXY_REQUIRE_AUTH !== 'false',
|
|
227
|
+
},
|
|
228
|
+
cors_origins: process.env.CORS_ORIGINS
|
|
229
|
+
? process.env.CORS_ORIGINS.split(',').map(s => s.trim())
|
|
230
|
+
: undefined,
|
|
231
|
+
oauth: {
|
|
232
|
+
enabled: false,
|
|
233
|
+
session_secret: '',
|
|
234
|
+
session_ttl_seconds: 604800,
|
|
235
|
+
},
|
|
236
|
+
stripe: readSecret('STRIPE_SECRET_KEY') ? {
|
|
237
|
+
secret_key: readSecret('STRIPE_SECRET_KEY')!,
|
|
238
|
+
webhook_secret: readSecret('STRIPE_WEBHOOK_SECRET') || '',
|
|
239
|
+
price_ids: {
|
|
240
|
+
pro_monthly: process.env.STRIPE_PRICE_PRO_MONTHLY || '',
|
|
241
|
+
business_monthly: process.env.STRIPE_PRICE_BUSINESS_MONTHLY || '',
|
|
242
|
+
},
|
|
243
|
+
checkout_success_url: process.env.STRIPE_CHECKOUT_SUCCESS_URL,
|
|
244
|
+
checkout_cancel_url: process.env.STRIPE_CHECKOUT_CANCEL_URL,
|
|
245
|
+
portal_return_url: process.env.STRIPE_PORTAL_RETURN_URL,
|
|
246
|
+
} : undefined,
|
|
247
|
+
frontend: {
|
|
248
|
+
enabled: process.env.FRONTEND_ENABLED === 'true',
|
|
249
|
+
build_path: process.env.FRONTEND_BUILD_PATH || './web/dist',
|
|
250
|
+
},
|
|
251
|
+
mcp_oauth: process.env.MCP_OAUTH_ENABLED === 'true' ? {
|
|
252
|
+
enabled: true,
|
|
253
|
+
base_url: process.env.MCP_OAUTH_BASE_URL,
|
|
254
|
+
access_token_ttl: process.env.MCP_OAUTH_ACCESS_TOKEN_TTL
|
|
255
|
+
? parseInt(process.env.MCP_OAUTH_ACCESS_TOKEN_TTL, 10)
|
|
256
|
+
: undefined,
|
|
257
|
+
refresh_token_ttl: process.env.MCP_OAUTH_REFRESH_TOKEN_TTL
|
|
258
|
+
? parseInt(process.env.MCP_OAUTH_REFRESH_TOKEN_TTL, 10)
|
|
259
|
+
: undefined,
|
|
260
|
+
} : undefined,
|
|
261
|
+
};
|