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
package/dist/src/saas/routes.js
CHANGED
|
@@ -41,6 +41,7 @@ const memory_1 = require("../storage/memory");
|
|
|
41
41
|
const calculator_1 = require("../trust/calculator");
|
|
42
42
|
const engine_1 = require("../replay/engine");
|
|
43
43
|
const plan_enforcer_1 = require("../billing/plan-enforcer");
|
|
44
|
+
const model_pricing_1 = require("../budget/model-pricing");
|
|
44
45
|
/** Extract a route param as string (Express 5 returns string | string[]). */
|
|
45
46
|
function param(req, name) {
|
|
46
47
|
const val = req.params[name];
|
|
@@ -313,7 +314,7 @@ function createSaaSRouter(deps) {
|
|
|
313
314
|
res.json({
|
|
314
315
|
api_keys: keys.map(k => ({
|
|
315
316
|
id: k.id,
|
|
316
|
-
|
|
317
|
+
prefix: k.key_prefix,
|
|
317
318
|
name: k.name,
|
|
318
319
|
roles: k.roles,
|
|
319
320
|
tags: k.tags || [],
|
|
@@ -376,7 +377,7 @@ function createSaaSRouter(deps) {
|
|
|
376
377
|
res.status(201).json({
|
|
377
378
|
id: apiKey.id,
|
|
378
379
|
key: rawKey,
|
|
379
|
-
|
|
380
|
+
prefix: keyPrefix,
|
|
380
381
|
name: apiKey.name,
|
|
381
382
|
roles: apiKey.roles,
|
|
382
383
|
tags: apiKey.tags,
|
|
@@ -495,6 +496,201 @@ function createSaaSRouter(deps) {
|
|
|
495
496
|
offset,
|
|
496
497
|
});
|
|
497
498
|
});
|
|
499
|
+
router.delete('/workspaces/:id/traces', (req, res) => {
|
|
500
|
+
if (!requireSession(req, res))
|
|
501
|
+
return;
|
|
502
|
+
const user = req.sessionUser;
|
|
503
|
+
const workspaceId = param(req, 'id');
|
|
504
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
505
|
+
if (!membership || (membership.role !== 'owner' && membership.role !== 'admin')) {
|
|
506
|
+
res.status(403).json({ error: 'Admin or owner access required' });
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
gateway.getAuditLogger().clear();
|
|
510
|
+
res.json({ status: 'ok', message: 'All traces cleared' });
|
|
511
|
+
});
|
|
512
|
+
router.delete('/workspaces/:id/traces/:taskId', (req, res) => {
|
|
513
|
+
if (!requireSession(req, res))
|
|
514
|
+
return;
|
|
515
|
+
const user = req.sessionUser;
|
|
516
|
+
const workspaceId = param(req, 'id');
|
|
517
|
+
const taskId = param(req, 'taskId');
|
|
518
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
519
|
+
if (!membership || (membership.role !== 'owner' && membership.role !== 'admin')) {
|
|
520
|
+
res.status(403).json({ error: 'Admin or owner access required' });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
gateway.getAuditLogger().deleteByTaskId(taskId);
|
|
524
|
+
res.json({ status: 'ok', message: `Trace ${taskId} deleted` });
|
|
525
|
+
});
|
|
526
|
+
router.delete('/workspaces/:id/sessions/:sessionId', (req, res) => {
|
|
527
|
+
if (!requireSession(req, res))
|
|
528
|
+
return;
|
|
529
|
+
const user = req.sessionUser;
|
|
530
|
+
const workspaceId = param(req, 'id');
|
|
531
|
+
const sessionId = param(req, 'sessionId');
|
|
532
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
533
|
+
if (!membership || (membership.role !== 'owner' && membership.role !== 'admin')) {
|
|
534
|
+
res.status(403).json({ error: 'Admin or owner access required' });
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
gateway.getAuditLogger().deleteBySessionId(sessionId);
|
|
538
|
+
res.json({ status: 'ok', message: `Session ${sessionId} traces deleted` });
|
|
539
|
+
});
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
// Sessions (grouped traces)
|
|
542
|
+
// ---------------------------------------------------------------------------
|
|
543
|
+
router.get('/workspaces/:id/sessions', (req, res) => {
|
|
544
|
+
if (!requireSession(req, res))
|
|
545
|
+
return;
|
|
546
|
+
const user = req.sessionUser;
|
|
547
|
+
const workspaceId = param(req, 'id');
|
|
548
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
549
|
+
if (!membership) {
|
|
550
|
+
res.status(403).json({ error: 'Not a member of this workspace' });
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
|
|
554
|
+
const offset = parseInt(req.query.offset) || 0;
|
|
555
|
+
const allEvents = gateway.getAuditLogger().getAllEvents();
|
|
556
|
+
const wsForSessions = workspaceStore.getById(workspaceId);
|
|
557
|
+
const slugForSessions = wsForSessions?.slug;
|
|
558
|
+
const wsEvents = allEvents.filter(e => e.workspace_id === workspaceId || (slugForSessions && e.workspace_id === slugForSessions));
|
|
559
|
+
// Group events by session_id (falling back to tool_call_id for old events)
|
|
560
|
+
const sessionMap = new Map();
|
|
561
|
+
for (const e of wsEvents) {
|
|
562
|
+
const key = e.session_id || e.tool_call_id || e.task_id;
|
|
563
|
+
if (!sessionMap.has(key))
|
|
564
|
+
sessionMap.set(key, []);
|
|
565
|
+
sessionMap.get(key).push(e);
|
|
566
|
+
}
|
|
567
|
+
// Build session summaries
|
|
568
|
+
const sessions = [];
|
|
569
|
+
for (const [sessionId, events] of sessionMap) {
|
|
570
|
+
events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
571
|
+
const first = events[0];
|
|
572
|
+
const last = events[events.length - 1];
|
|
573
|
+
// Count unique tool_call_ids (each represents one tool call)
|
|
574
|
+
const toolCallIds = new Set(events.map(e => e.tool_call_id));
|
|
575
|
+
const receivedEvents = events.filter(e => e.event_type === 'TOOL_CALL_RECEIVED');
|
|
576
|
+
const toolCallCount = Math.max(receivedEvents.length, 1);
|
|
577
|
+
// Unique tools
|
|
578
|
+
const toolsUsed = [...new Set(events.map(e => e.tool_name).filter(Boolean))];
|
|
579
|
+
// Derive status counts per tool_call_id
|
|
580
|
+
const statusCounts = { ok: 0, blocked: 0, error: 0, approval: 0, redacted: 0 };
|
|
581
|
+
for (const tcId of toolCallIds) {
|
|
582
|
+
const tcEvents = events.filter(e => e.tool_call_id === tcId);
|
|
583
|
+
const hasDeny = tcEvents.some(e => e.event_type === 'POLICY_DECIDED' && e.metadata?.decision === 'deny');
|
|
584
|
+
const hasApproval = tcEvents.some(e => e.event_type === 'APPROVAL_REQUESTED');
|
|
585
|
+
const hasError = tcEvents.some(e => e.event_type === 'TOOL_EXECUTED' && e.metadata?.status === 'error');
|
|
586
|
+
const hasRedaction = tcEvents.some(e => e.event_type === 'DLP_SCANNED' && Array.isArray(e.metadata?.detected) && e.metadata.detected.length > 0);
|
|
587
|
+
if (hasDeny)
|
|
588
|
+
statusCounts.blocked++;
|
|
589
|
+
else if (hasError)
|
|
590
|
+
statusCounts.error++;
|
|
591
|
+
else if (hasApproval)
|
|
592
|
+
statusCounts.approval++;
|
|
593
|
+
else
|
|
594
|
+
statusCounts.ok++;
|
|
595
|
+
// Redacted is orthogonal — a call can be ok AND redacted
|
|
596
|
+
if (hasRedaction)
|
|
597
|
+
statusCounts.redacted++;
|
|
598
|
+
}
|
|
599
|
+
// Overall status: worst status across calls
|
|
600
|
+
let overallStatus = 'ok';
|
|
601
|
+
if (statusCounts.blocked > 0)
|
|
602
|
+
overallStatus = 'blocked';
|
|
603
|
+
else if (statusCounts.error > 0)
|
|
604
|
+
overallStatus = 'error';
|
|
605
|
+
else if (statusCounts.approval > 0)
|
|
606
|
+
overallStatus = 'approval';
|
|
607
|
+
sessions.push({
|
|
608
|
+
session_id: sessionId,
|
|
609
|
+
tool_call_count: toolCallCount,
|
|
610
|
+
first_timestamp: first.timestamp,
|
|
611
|
+
last_timestamp: last.timestamp,
|
|
612
|
+
duration_ms: new Date(last.timestamp).getTime() - new Date(first.timestamp).getTime(),
|
|
613
|
+
actor_id: first.actor_id || '',
|
|
614
|
+
platform: first.metadata?.platform || 'unknown',
|
|
615
|
+
tools_used: toolsUsed,
|
|
616
|
+
overall_status: overallStatus,
|
|
617
|
+
status_counts: statusCounts,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
// Sort newest first
|
|
621
|
+
sessions.sort((a, b) => b.first_timestamp.localeCompare(a.first_timestamp));
|
|
622
|
+
res.json({
|
|
623
|
+
sessions: sessions.slice(offset, offset + limit),
|
|
624
|
+
total: sessions.length,
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
router.get('/workspaces/:id/sessions/:sessionId', (req, res) => {
|
|
628
|
+
if (!requireSession(req, res))
|
|
629
|
+
return;
|
|
630
|
+
const user = req.sessionUser;
|
|
631
|
+
const workspaceId = param(req, 'id');
|
|
632
|
+
const sessionId = param(req, 'sessionId');
|
|
633
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
634
|
+
if (!membership) {
|
|
635
|
+
res.status(403).json({ error: 'Not a member of this workspace' });
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const allEvents = gateway.getAuditLogger().getAllEvents();
|
|
639
|
+
const workspace = workspaceStore.getById(workspaceId);
|
|
640
|
+
const wsSlug = workspace?.slug;
|
|
641
|
+
// Find events by session_id, filtered to this workspace
|
|
642
|
+
const sessionEvents = allEvents.filter(e => e.session_id === sessionId &&
|
|
643
|
+
(e.workspace_id === workspaceId || (wsSlug && e.workspace_id === wsSlug)));
|
|
644
|
+
// If no events found by session_id, try treating sessionId as a tool_call_id (backward compat)
|
|
645
|
+
const events = sessionEvents.length > 0
|
|
646
|
+
? sessionEvents
|
|
647
|
+
: allEvents.filter(e => e.tool_call_id === sessionId &&
|
|
648
|
+
(e.workspace_id === workspaceId || (wsSlug && e.workspace_id === wsSlug)));
|
|
649
|
+
// Group by tool_call_id
|
|
650
|
+
const toolCallMap = new Map();
|
|
651
|
+
for (const e of events) {
|
|
652
|
+
const tcId = e.tool_call_id;
|
|
653
|
+
if (!toolCallMap.has(tcId))
|
|
654
|
+
toolCallMap.set(tcId, []);
|
|
655
|
+
toolCallMap.get(tcId).push(e);
|
|
656
|
+
}
|
|
657
|
+
const toolCalls = [];
|
|
658
|
+
for (const [tcId, tcEvents] of toolCallMap) {
|
|
659
|
+
tcEvents.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
660
|
+
const first = tcEvents[0];
|
|
661
|
+
const last = tcEvents[tcEvents.length - 1];
|
|
662
|
+
const resultEvent = tcEvents.find(e => e.event_type === 'TOOL_RESULT_RETURNED');
|
|
663
|
+
const durationMs = resultEvent?.metadata?.duration_ms ||
|
|
664
|
+
(new Date(last.timestamp).getTime() - new Date(first.timestamp).getTime());
|
|
665
|
+
// Derive status
|
|
666
|
+
const hasDeny = tcEvents.some(e => e.event_type === 'POLICY_DECIDED' && e.metadata?.decision === 'deny');
|
|
667
|
+
const hasApproval = tcEvents.some(e => e.event_type === 'APPROVAL_REQUESTED');
|
|
668
|
+
const hasError = tcEvents.some(e => e.event_type === 'TOOL_EXECUTED' && e.metadata?.status === 'error');
|
|
669
|
+
let status = 'ok';
|
|
670
|
+
if (hasDeny)
|
|
671
|
+
status = 'blocked';
|
|
672
|
+
else if (hasError)
|
|
673
|
+
status = 'error';
|
|
674
|
+
else if (hasApproval)
|
|
675
|
+
status = 'approval';
|
|
676
|
+
toolCalls.push({
|
|
677
|
+
tool_call_id: tcId,
|
|
678
|
+
task_id: first.task_id,
|
|
679
|
+
tool_name: first.tool_name || 'unknown',
|
|
680
|
+
timestamp: first.timestamp,
|
|
681
|
+
duration_ms: durationMs,
|
|
682
|
+
status,
|
|
683
|
+
events: tcEvents,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
// Sort by timestamp
|
|
687
|
+
toolCalls.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
688
|
+
res.json({
|
|
689
|
+
session_id: sessionId,
|
|
690
|
+
tool_calls: toolCalls,
|
|
691
|
+
total_events: events.length,
|
|
692
|
+
});
|
|
693
|
+
});
|
|
498
694
|
// ---------------------------------------------------------------------------
|
|
499
695
|
// Budgets
|
|
500
696
|
// ---------------------------------------------------------------------------
|
|
@@ -712,6 +908,23 @@ function createSaaSRouter(deps) {
|
|
|
712
908
|
// ---------------------------------------------------------------------------
|
|
713
909
|
// Policy Rule Generation (LLM-powered)
|
|
714
910
|
// ---------------------------------------------------------------------------
|
|
911
|
+
// Per-user rate limiter for LLM rule generation: 10 requests per hour
|
|
912
|
+
const LLM_RATE_LIMIT_MAX = 10;
|
|
913
|
+
const LLM_RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000; // 1 hour
|
|
914
|
+
const llmRateLimitHits = new Map();
|
|
915
|
+
// Periodic cleanup to prevent memory leaks
|
|
916
|
+
setInterval(() => {
|
|
917
|
+
const now = Date.now();
|
|
918
|
+
for (const [userId, timestamps] of llmRateLimitHits) {
|
|
919
|
+
const valid = timestamps.filter(t => now - t < LLM_RATE_LIMIT_WINDOW_MS);
|
|
920
|
+
if (valid.length === 0) {
|
|
921
|
+
llmRateLimitHits.delete(userId);
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
llmRateLimitHits.set(userId, valid);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}, LLM_RATE_LIMIT_WINDOW_MS).unref();
|
|
715
928
|
router.post('/workspaces/:id/policies/generate-rule', async (req, res) => {
|
|
716
929
|
if (!requireSession(req, res))
|
|
717
930
|
return;
|
|
@@ -733,6 +946,21 @@ function createSaaSRouter(deps) {
|
|
|
733
946
|
return;
|
|
734
947
|
}
|
|
735
948
|
const trimmed = description.trim().slice(0, 500);
|
|
949
|
+
// Per-user rate limit check (after validation, before LLM call)
|
|
950
|
+
const now = Date.now();
|
|
951
|
+
const userTimestamps = llmRateLimitHits.get(user.id) || [];
|
|
952
|
+
const windowStart = now - LLM_RATE_LIMIT_WINDOW_MS;
|
|
953
|
+
const validTimestamps = userTimestamps.filter(t => t > windowStart);
|
|
954
|
+
if (validTimestamps.length >= LLM_RATE_LIMIT_MAX) {
|
|
955
|
+
const retryAfterMs = LLM_RATE_LIMIT_WINDOW_MS - (now - validTimestamps[0]);
|
|
956
|
+
res.status(429).json({
|
|
957
|
+
error: `You've reached the limit of ${LLM_RATE_LIMIT_MAX} AI generations per hour. Please try again later.`,
|
|
958
|
+
retry_after_ms: retryAfterMs,
|
|
959
|
+
});
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
validTimestamps.push(now);
|
|
963
|
+
llmRateLimitHits.set(user.id, validTimestamps);
|
|
736
964
|
const systemPrompt = `You are a policy rule generator for Palaryn, an AI agent gateway. Given a natural language description, generate a single JSON PolicyRule object.
|
|
737
965
|
|
|
738
966
|
The PolicyRule schema:
|
|
@@ -784,19 +1012,33 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
784
1012
|
try {
|
|
785
1013
|
const controller = new AbortController();
|
|
786
1014
|
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
|
|
1015
|
+
// Detect provider from API key prefix
|
|
1016
|
+
const isOpenAI = apiKey.startsWith('sk-proj-') || apiKey.startsWith('sk-');
|
|
1017
|
+
const llmUrl = isOpenAI
|
|
1018
|
+
? 'https://api.openai.com/v1/chat/completions'
|
|
1019
|
+
: 'https://api.anthropic.com/v1/messages';
|
|
1020
|
+
const llmHeaders = isOpenAI
|
|
1021
|
+
? { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }
|
|
1022
|
+
: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' };
|
|
1023
|
+
const llmBody = isOpenAI
|
|
1024
|
+
? JSON.stringify({
|
|
1025
|
+
model: 'gpt-4.1-mini',
|
|
1026
|
+
max_tokens: 1024,
|
|
1027
|
+
messages: [
|
|
1028
|
+
{ role: 'system', content: systemPrompt },
|
|
1029
|
+
{ role: 'user', content: trimmed },
|
|
1030
|
+
],
|
|
1031
|
+
})
|
|
1032
|
+
: JSON.stringify({
|
|
1033
|
+
model: 'claude-sonnet-4-5-20241022',
|
|
796
1034
|
max_tokens: 1024,
|
|
797
1035
|
system: systemPrompt,
|
|
798
1036
|
messages: [{ role: 'user', content: trimmed }],
|
|
799
|
-
})
|
|
1037
|
+
});
|
|
1038
|
+
const llmRes = await fetch(llmUrl, {
|
|
1039
|
+
method: 'POST',
|
|
1040
|
+
headers: llmHeaders,
|
|
1041
|
+
body: llmBody,
|
|
800
1042
|
signal: controller.signal,
|
|
801
1043
|
});
|
|
802
1044
|
clearTimeout(timeout);
|
|
@@ -807,7 +1049,9 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
807
1049
|
return;
|
|
808
1050
|
}
|
|
809
1051
|
const llmData = await llmRes.json();
|
|
810
|
-
const text =
|
|
1052
|
+
const text = isOpenAI
|
|
1053
|
+
? (llmData.choices?.[0]?.message?.content || '')
|
|
1054
|
+
: (llmData.content?.[0]?.text || '');
|
|
811
1055
|
// Strip markdown code fences if present
|
|
812
1056
|
const cleaned = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/i, '').trim();
|
|
813
1057
|
let rule;
|
|
@@ -862,20 +1106,38 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
862
1106
|
}
|
|
863
1107
|
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
|
|
864
1108
|
const offset = parseInt(req.query.offset) || 0;
|
|
1109
|
+
const severity = req.query.severity;
|
|
1110
|
+
const tool = req.query.tool;
|
|
1111
|
+
const actor = req.query.actor;
|
|
1112
|
+
const q = req.query.q;
|
|
865
1113
|
const allDlpEvents = gateway.getAuditLogger().getEventsByType('DLP_SCANNED');
|
|
866
1114
|
const workspace = workspaceStore.getById(workspaceId);
|
|
867
1115
|
const wsSlug = workspace?.slug;
|
|
868
1116
|
const wsEvents = allDlpEvents
|
|
869
1117
|
.filter(e => e.workspace_id === workspaceId || (wsSlug && e.workspace_id === wsSlug))
|
|
870
1118
|
.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
871
|
-
//
|
|
1119
|
+
// Apply optional filters
|
|
1120
|
+
let filtered = wsEvents;
|
|
1121
|
+
if (severity)
|
|
1122
|
+
filtered = filtered.filter(e => e.metadata?.severity === severity);
|
|
1123
|
+
if (tool)
|
|
1124
|
+
filtered = filtered.filter(e => e.tool_name.toLowerCase().includes(tool.toLowerCase()));
|
|
1125
|
+
if (actor)
|
|
1126
|
+
filtered = filtered.filter(e => e.actor_id.toLowerCase().includes(actor.toLowerCase()));
|
|
1127
|
+
if (q) {
|
|
1128
|
+
const ql = q.toLowerCase();
|
|
1129
|
+
filtered = filtered.filter(e => e.tool_name.toLowerCase().includes(ql) ||
|
|
1130
|
+
e.actor_id.toLowerCase().includes(ql) ||
|
|
1131
|
+
(e.metadata?.detected || []).some(p => p.toLowerCase().includes(ql)));
|
|
1132
|
+
}
|
|
1133
|
+
// Compute severity stats over filtered results
|
|
872
1134
|
let high = 0, medium = 0, low = 0;
|
|
873
1135
|
const patternSet = new Set();
|
|
874
|
-
for (const e of
|
|
875
|
-
const
|
|
876
|
-
if (
|
|
1136
|
+
for (const e of filtered) {
|
|
1137
|
+
const sev = e.metadata?.severity;
|
|
1138
|
+
if (sev === 'high')
|
|
877
1139
|
high++;
|
|
878
|
-
else if (
|
|
1140
|
+
else if (sev === 'medium')
|
|
879
1141
|
medium++;
|
|
880
1142
|
else
|
|
881
1143
|
low++;
|
|
@@ -886,12 +1148,12 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
886
1148
|
}
|
|
887
1149
|
}
|
|
888
1150
|
res.json({
|
|
889
|
-
events:
|
|
890
|
-
total:
|
|
1151
|
+
events: filtered.slice(offset, offset + limit),
|
|
1152
|
+
total: filtered.length,
|
|
891
1153
|
limit,
|
|
892
1154
|
offset,
|
|
893
1155
|
stats: {
|
|
894
|
-
total:
|
|
1156
|
+
total: filtered.length,
|
|
895
1157
|
high,
|
|
896
1158
|
medium,
|
|
897
1159
|
low,
|
|
@@ -1140,11 +1402,16 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
1140
1402
|
const denyCount = allStats.reduce((acc, s) => acc + (s.policy_breakdown['deny'] || 0), 0);
|
|
1141
1403
|
const shield_score = {
|
|
1142
1404
|
score: totalDecisions > 0 ? Math.round(((allowCount + transformCount) / totalDecisions) * 100) : 100,
|
|
1405
|
+
total: totalDecisions,
|
|
1143
1406
|
breakdown: {
|
|
1144
1407
|
allowed_percent: totalDecisions > 0 ? Math.round((allowCount / totalDecisions) * 100) : 100,
|
|
1145
1408
|
transformed_percent: totalDecisions > 0 ? Math.round((transformCount / totalDecisions) * 100) : 0,
|
|
1146
1409
|
approval_percent: totalDecisions > 0 ? Math.round((approvalCount / totalDecisions) * 100) : 0,
|
|
1147
1410
|
blocked_percent: totalDecisions > 0 ? Math.round((denyCount / totalDecisions) * 100) : 0,
|
|
1411
|
+
allowed_count: allowCount,
|
|
1412
|
+
transformed_count: transformCount,
|
|
1413
|
+
approval_count: approvalCount,
|
|
1414
|
+
blocked_count: denyCount,
|
|
1148
1415
|
},
|
|
1149
1416
|
};
|
|
1150
1417
|
// Pipeline throughput from stats (merged across all matching workspace IDs)
|
|
@@ -1177,7 +1444,7 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
1177
1444
|
res.json({ current: {}, baseline: {}, alerts: [] });
|
|
1178
1445
|
return;
|
|
1179
1446
|
}
|
|
1180
|
-
const report = detector.getBaselineReport();
|
|
1447
|
+
const report = detector.getBaselineReport(workspaceId);
|
|
1181
1448
|
res.json(report);
|
|
1182
1449
|
});
|
|
1183
1450
|
// ---------------------------------------------------------------------------
|
|
@@ -1561,6 +1828,72 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
1561
1828
|
});
|
|
1562
1829
|
res.json({ status: 'reset', config: config.budget, is_custom: false });
|
|
1563
1830
|
});
|
|
1831
|
+
// ---------------------------------------------------------------------------
|
|
1832
|
+
// Model Pricing: Get merged pricing (built-in + workspace overrides)
|
|
1833
|
+
// ---------------------------------------------------------------------------
|
|
1834
|
+
router.get('/workspaces/:id/model-pricing', (req, res) => {
|
|
1835
|
+
if (!requireSession(req, res))
|
|
1836
|
+
return;
|
|
1837
|
+
const user = req.sessionUser;
|
|
1838
|
+
const workspaceId = param(req, 'id');
|
|
1839
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
1840
|
+
if (!membership) {
|
|
1841
|
+
res.status(403).json({ error: 'Not a member of this workspace' });
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
// Get workspace-level budget config overrides (if any)
|
|
1845
|
+
const wsConfig = budgetConfigStore.getByWorkspaceId(workspaceId);
|
|
1846
|
+
const workspaceOverrides = wsConfig?.token_pricing;
|
|
1847
|
+
// Merge: built-in pricing as base, workspace overrides on top
|
|
1848
|
+
const merged = {};
|
|
1849
|
+
for (const [model, pricing] of Object.entries(model_pricing_1.MODEL_PRICING)) {
|
|
1850
|
+
merged[model] = {
|
|
1851
|
+
input_per_token: pricing.input_per_token,
|
|
1852
|
+
output_per_token: pricing.output_per_token,
|
|
1853
|
+
source: 'built-in',
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
if (workspaceOverrides) {
|
|
1857
|
+
for (const [model, pricing] of Object.entries(workspaceOverrides)) {
|
|
1858
|
+
merged[model] = {
|
|
1859
|
+
input_per_token: pricing.input_per_token,
|
|
1860
|
+
output_per_token: pricing.output_per_token,
|
|
1861
|
+
source: 'workspace',
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
res.json({
|
|
1866
|
+
models: merged,
|
|
1867
|
+
has_workspace_overrides: !!workspaceOverrides,
|
|
1868
|
+
});
|
|
1869
|
+
});
|
|
1870
|
+
// ---------------------------------------------------------------------------
|
|
1871
|
+
// Admin: Update Workspace Plan (platform admin only)
|
|
1872
|
+
// ---------------------------------------------------------------------------
|
|
1873
|
+
const VALID_PLANS = ['free', 'pro', 'business', 'enterprise'];
|
|
1874
|
+
router.put('/workspaces/:id/plan', (req, res) => {
|
|
1875
|
+
if (!requireSession(req, res))
|
|
1876
|
+
return;
|
|
1877
|
+
const user = req.sessionUser;
|
|
1878
|
+
const adminEmail = process.env.SEED_ADMIN_EMAIL;
|
|
1879
|
+
if (!adminEmail || user.email !== adminEmail) {
|
|
1880
|
+
res.status(403).json({ error: 'Platform admin access required' });
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
const { plan } = req.body || {};
|
|
1884
|
+
if (!plan || !VALID_PLANS.includes(plan)) {
|
|
1885
|
+
res.status(400).json({ error: `Invalid plan. Must be one of: ${VALID_PLANS.join(', ')}` });
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
const workspaceId = param(req, 'id');
|
|
1889
|
+
const workspace = workspaceStore.getById(workspaceId);
|
|
1890
|
+
if (!workspace) {
|
|
1891
|
+
res.status(404).json({ error: 'Workspace not found' });
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
const updated = workspaceStore.update(workspaceId, { plan, updated_at: new Date().toISOString() });
|
|
1895
|
+
res.json(updated);
|
|
1896
|
+
});
|
|
1564
1897
|
return router;
|
|
1565
1898
|
}
|
|
1566
1899
|
//# sourceMappingURL=routes.js.map
|