Files
simstudioai--sim/apps/sim/lib/copilot/entitlements.ts
T
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

73 lines
2.7 KiB
TypeScript

import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { LRUCache } from 'lru-cache'
import { isCustomBlocksEligible } from '@/lib/workflows/custom-blocks/operations'
const logger = createLogger('CopilotEntitlements')
/**
* Cross-repo contract: the mothership (Go) matches these exact strings against
* its `core.Entitlement*` constants to gate agent surfaces.
*/
export const CUSTOM_BLOCKS_ENTITLEMENT = 'custom-blocks'
/**
* Workspace entitlements — plan/flag-gated org capabilities sent to the
* mothership as the chat payload's `entitlements` array. The Go side hides the
* matching tools, skills, and prompt sections when an entitlement is absent, so
* a non-entitled org's agents never hear of the feature.
*
* Adding an entitlement:
* 1. Add the kebab-case name and a fail-closed evaluator here. Every payload
* site (interactive chat, headless execute, inbox) picks it up automatically.
* 2. Go repo: add the matching `Entitlement*` constant in `internal/core` and
* gate surfaces declaratively — `RequiredEntitlement` on tool definitions,
* `entitlement:` frontmatter on skills, a conditional section in
* `BuildAgentEnvelope`, or a variant swap in `Capabilities`.
* 3. Keep enforcement in sim: the Go gating is advertisement-only (the payload
* is forgeable), so the sim-side tool handler must re-check the same
* predicate at execution time.
*/
const ENTITLEMENT_EVALUATORS: Record<
string,
(workspaceId: string, userId?: string) => Promise<boolean>
> = {
[CUSTOM_BLOCKS_ENTITLEMENT]: isCustomBlocksEligible,
}
const entitlementsCache = new LRUCache<string, Promise<string[]>>({
max: 500,
ttl: 5_000,
})
/**
* The entitlements to send to the mothership for a request in this workspace.
* Each evaluator fails closed (an error means the entitlement is absent).
* Cached briefly so the several per-message callers collapse to one evaluation.
*/
export function computeWorkspaceEntitlements(
workspaceId: string,
userId?: string
): Promise<string[]> {
const cacheKey = `${workspaceId}:${userId ?? ''}`
const cached = entitlementsCache.get(cacheKey)
if (cached) return cached
const promise = Promise.all(
Object.entries(ENTITLEMENT_EVALUATORS).map(async ([name, evaluate]) => {
try {
return (await evaluate(workspaceId, userId)) ? name : null
} catch (error) {
logger.warn('Entitlement evaluation failed; treating as absent', {
entitlement: name,
workspaceId,
error: getErrorMessage(error),
})
return null
}
})
).then((names) => names.filter((name): name is string => name !== null))
entitlementsCache.set(cacheKey, promise)
return promise
}