6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (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 / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
484 lines
16 KiB
TypeScript
484 lines
16 KiB
TypeScript
/**
|
|
* Non-secret deployment-capability status derived from the same definitions the
|
|
* application uses at runtime.
|
|
*
|
|
* @packageDocumentation
|
|
*/
|
|
import {
|
|
ASYNC_JOBS_CAPABILITY,
|
|
CACHE_CAPABILITY,
|
|
EMAIL_CAPABILITY,
|
|
type EnvCapabilityConfigurationError,
|
|
type EnvCapabilityValues,
|
|
getCapabilityConfigurationError,
|
|
hasEnvCapabilityValue,
|
|
inspectCapability,
|
|
inspectOAuthClientCapability,
|
|
isTruthyEnvCapabilityValue,
|
|
LLM_KEY_POOLS,
|
|
OAUTH_CLIENT_CAPABILITIES,
|
|
type OAuthClientCapabilityId,
|
|
OCR_CAPABILITY,
|
|
type ProviderConfigurationState,
|
|
type ProviderInspection,
|
|
SANDBOX_CAPABILITY,
|
|
STORAGE_CAPABILITY,
|
|
} from '../../apps/sim/lib/core/config/env-capabilities.ts'
|
|
import { SETUP_FEATURES, type SetupFeatureId } from './capability-config.ts'
|
|
|
|
export type SetupStatusFeatureId = Exclude<SetupFeatureId, 'integration'>
|
|
export type CapabilityStatusState = 'default' | 'configured' | 'missing' | 'partial' | 'invalid'
|
|
|
|
export interface CapabilityStatusIssue {
|
|
state: Extract<CapabilityStatusState, 'missing' | 'partial' | 'invalid'>
|
|
message: string
|
|
}
|
|
|
|
interface FeatureStatusBase<TId extends SetupStatusFeatureId> {
|
|
id: TId
|
|
label: string
|
|
setupCommand: `bun run setup ${TId}`
|
|
state: CapabilityStatusState
|
|
issue?: CapabilityStatusIssue
|
|
}
|
|
|
|
type EmailProviderId = (typeof EMAIL_CAPABILITY.providers)[number]['id']
|
|
type StorageProviderId =
|
|
| (typeof STORAGE_CAPABILITY)['defaultProvider']['id']
|
|
| (typeof STORAGE_CAPABILITY.providers)[number]['id']
|
|
type LlmKeyPoolId = keyof typeof LLM_KEY_POOLS
|
|
|
|
export interface EmailCapabilityStatus extends FeatureStatusBase<'email'> {
|
|
strategy: 'fallback'
|
|
providerIds: readonly EmailProviderId[]
|
|
providers: readonly ProviderInspection<EmailProviderId>[]
|
|
}
|
|
|
|
export interface StorageCapabilityStatus extends FeatureStatusBase<'storage'> {
|
|
strategy: 'selected'
|
|
providerId: StorageProviderId | null
|
|
defaultProviderId: (typeof STORAGE_CAPABILITY)['defaultProvider']['id']
|
|
providers: readonly ProviderInspection<(typeof STORAGE_CAPABILITY.providers)[number]['id']>[]
|
|
}
|
|
|
|
export interface SandboxCapabilityStatus extends FeatureStatusBase<'sandbox'> {
|
|
providerId: 'disabled' | 'e2b' | 'daytona' | null
|
|
}
|
|
|
|
export interface JobsCapabilityStatus extends FeatureStatusBase<'jobs'> {
|
|
providerId: 'database' | 'trigger-dev'
|
|
}
|
|
|
|
export interface CacheCapabilityStatus extends FeatureStatusBase<'cache'> {
|
|
providerId: 'database' | 'redis'
|
|
}
|
|
|
|
export interface KnowledgeCapabilityStatus extends FeatureStatusBase<'knowledge'> {
|
|
providerId: 'local' | 'mistral' | 'azure-mistral' | null
|
|
}
|
|
|
|
export interface LlmKeyPoolStatus {
|
|
id: LlmKeyPoolId
|
|
state: 'configured' | 'missing'
|
|
configuredKeyCount: number
|
|
effectiveKeyCount: number
|
|
fallbackKeyConfigured: boolean
|
|
}
|
|
|
|
export interface LlmCapabilityStatus extends FeatureStatusBase<'llm'> {
|
|
pools: Readonly<Record<LlmKeyPoolId, LlmKeyPoolStatus>>
|
|
configuredPoolCount: number
|
|
configuredKeyCount: number
|
|
effectiveKeyCount: number
|
|
}
|
|
|
|
interface FeatureStatusById {
|
|
email: EmailCapabilityStatus
|
|
storage: StorageCapabilityStatus
|
|
sandbox: SandboxCapabilityStatus
|
|
jobs: JobsCapabilityStatus
|
|
cache: CacheCapabilityStatus
|
|
knowledge: KnowledgeCapabilityStatus
|
|
llm: LlmCapabilityStatus
|
|
}
|
|
|
|
export type EnvCapabilityFeatureStatuses = Pick<FeatureStatusById, SetupStatusFeatureId>
|
|
|
|
export interface OAuthClientStatus {
|
|
id: OAuthClientCapabilityId
|
|
state: ProviderConfigurationState
|
|
configuredFieldCount: number
|
|
requiredFieldCount: number
|
|
missingFields: readonly string[]
|
|
setupCommand: string
|
|
}
|
|
|
|
export interface OAuthClientStatuses {
|
|
clients: Readonly<Record<OAuthClientCapabilityId, OAuthClientStatus>>
|
|
readyCount: number
|
|
partialCount: number
|
|
absentCount: number
|
|
invalidCount: number
|
|
}
|
|
|
|
export interface EnvCapabilityStatusSnapshot {
|
|
features: EnvCapabilityFeatureStatuses
|
|
oauthClients: OAuthClientStatuses
|
|
}
|
|
|
|
function featureMetadata<TId extends SetupStatusFeatureId>(id: TId) {
|
|
const definition = SETUP_FEATURES.find((feature) => feature.id === id)
|
|
if (!definition) throw new Error(`Missing setup feature definition for ${id}`)
|
|
return {
|
|
id,
|
|
label: definition.label,
|
|
setupCommand: `bun run setup ${id}` as const,
|
|
}
|
|
}
|
|
|
|
function readConfiguredString(values: EnvCapabilityValues, key: string): string | null {
|
|
if (!hasEnvCapabilityValue(values, key)) return null
|
|
const value =
|
|
values instanceof Map ? values.get(key) : (values as Readonly<Record<string, unknown>>)[key]
|
|
return String(value).trim().toLowerCase()
|
|
}
|
|
|
|
function issue(
|
|
state: CapabilityStatusIssue['state'],
|
|
error: EnvCapabilityConfigurationError,
|
|
safeMessage = error.message
|
|
): CapabilityStatusIssue {
|
|
return { state, message: safeMessage }
|
|
}
|
|
|
|
function brokenProviderState(
|
|
providers: readonly ProviderInspection[]
|
|
): Extract<CapabilityStatusState, 'partial' | 'invalid'> | null {
|
|
if (providers.some((provider) => provider.state === 'invalid')) return 'invalid'
|
|
if (providers.some((provider) => provider.state === 'partial')) return 'partial'
|
|
return null
|
|
}
|
|
|
|
function inspectEmail(values: EnvCapabilityValues): EmailCapabilityStatus {
|
|
const inspection = inspectCapability(EMAIL_CAPABILITY, values)
|
|
const brokenState = brokenProviderState(inspection.providers)
|
|
const state = inspection.configured ? 'configured' : (brokenState ?? 'missing')
|
|
const configurationError =
|
|
inspection.error ?? getCapabilityConfigurationError(EMAIL_CAPABILITY, inspection.providers)
|
|
|
|
return {
|
|
...featureMetadata('email'),
|
|
strategy: 'fallback',
|
|
state,
|
|
providerIds: inspection.providerIds,
|
|
providers: inspection.providers,
|
|
...(configurationError ? { issue: issue(brokenState ?? 'invalid', configurationError) } : {}),
|
|
}
|
|
}
|
|
|
|
function inspectStorage(values: EnvCapabilityValues): StorageCapabilityStatus {
|
|
const inspection = inspectCapability(STORAGE_CAPABILITY, values)
|
|
|
|
if (!inspection.error && inspection.providerId) {
|
|
const providerId = inspection.providerId as StorageProviderId
|
|
return {
|
|
...featureMetadata('storage'),
|
|
strategy: 'selected',
|
|
state: providerId === STORAGE_CAPABILITY.defaultProvider.id ? 'default' : 'configured',
|
|
providerId,
|
|
defaultProviderId: STORAGE_CAPABILITY.defaultProvider.id,
|
|
providers: inspection.providers,
|
|
}
|
|
}
|
|
|
|
const selectedId = readConfiguredString(values, STORAGE_CAPABILITY.selectorKey)
|
|
const selected = inspection.providers.find((provider) => provider.id === selectedId)
|
|
const state =
|
|
selected && !selected.active
|
|
? 'missing'
|
|
: selected?.state === 'partial'
|
|
? 'partial'
|
|
: selected?.state === 'invalid'
|
|
? 'invalid'
|
|
: (brokenProviderState(inspection.providers) ?? 'invalid')
|
|
const error = inspection.error
|
|
if (!error) throw new Error('Storage resolution failed without a configuration error')
|
|
const providerId: StorageProviderId | null =
|
|
selectedId === STORAGE_CAPABILITY.defaultProvider.id
|
|
? STORAGE_CAPABILITY.defaultProvider.id
|
|
: (selected?.id ?? null)
|
|
const safeMessage =
|
|
selectedId && !providerId
|
|
? `Unknown ${STORAGE_CAPABILITY.selectorKey}. Expected one of: ${[
|
|
STORAGE_CAPABILITY.defaultProvider.id,
|
|
...STORAGE_CAPABILITY.providers.map((provider) => provider.id),
|
|
].join(', ')}`
|
|
: error.message
|
|
|
|
return {
|
|
...featureMetadata('storage'),
|
|
strategy: 'selected',
|
|
state,
|
|
providerId,
|
|
defaultProviderId: STORAGE_CAPABILITY.defaultProvider.id,
|
|
providers: inspection.providers,
|
|
issue: issue(state, error, safeMessage),
|
|
}
|
|
}
|
|
|
|
function inspectSandbox(values: EnvCapabilityValues): SandboxCapabilityStatus {
|
|
const inspection = inspectCapability(SANDBOX_CAPABILITY, values)
|
|
const requestedProvider =
|
|
readConfiguredString(values, SANDBOX_CAPABILITY.selectorKey) ??
|
|
SANDBOX_CAPABILITY.defaultProvider.id
|
|
const providerId =
|
|
inspection.providerId === 'e2b' && !isTruthyEnvCapabilityValue(values, 'E2B_ENABLED')
|
|
? 'disabled'
|
|
: inspection.providerId
|
|
|
|
if (!inspection.error) {
|
|
const coherenceProblems: string[] = []
|
|
if (
|
|
isTruthyEnvCapabilityValue(values, 'E2B_ENABLED') !==
|
|
isTruthyEnvCapabilityValue(values, 'NEXT_PUBLIC_E2B_ENABLED')
|
|
) {
|
|
coherenceProblems.push('E2B_ENABLED and NEXT_PUBLIC_E2B_ENABLED disagree')
|
|
}
|
|
const remoteAvailable = providerId !== null && providerId !== 'disabled'
|
|
if (remoteAvailable !== isTruthyEnvCapabilityValue(values, 'NEXT_PUBLIC_SANDBOXES_ENABLED')) {
|
|
coherenceProblems.push(
|
|
'remote sandbox availability and NEXT_PUBLIC_SANDBOXES_ENABLED disagree'
|
|
)
|
|
}
|
|
if (coherenceProblems.length > 0) {
|
|
return {
|
|
...featureMetadata('sandbox'),
|
|
state: 'partial',
|
|
providerId,
|
|
issue: {
|
|
state: 'partial',
|
|
message: `${coherenceProblems.join('; ')}. Server and browser configuration must match.`,
|
|
},
|
|
}
|
|
}
|
|
return {
|
|
...featureMetadata('sandbox'),
|
|
state: providerId === 'disabled' ? 'default' : 'configured',
|
|
providerId,
|
|
}
|
|
}
|
|
|
|
const knownProvider = requestedProvider === 'e2b' || requestedProvider === 'daytona'
|
|
const selectedProvider = inspection.providers.find(
|
|
(provider) => provider.id === requestedProvider
|
|
)
|
|
const state = !knownProvider || selectedProvider?.state === 'invalid' ? 'invalid' : 'missing'
|
|
|
|
return {
|
|
...featureMetadata('sandbox'),
|
|
state,
|
|
providerId: knownProvider ? requestedProvider : null,
|
|
issue: issue(
|
|
state,
|
|
inspection.error,
|
|
knownProvider
|
|
? inspection.error.message
|
|
: 'Unknown SANDBOX_PROVIDER. Expected one of: e2b, daytona'
|
|
),
|
|
}
|
|
}
|
|
|
|
function inspectJobs(values: EnvCapabilityValues): JobsCapabilityStatus {
|
|
const inspection = inspectCapability(ASYNC_JOBS_CAPABILITY, values)
|
|
if (!inspection.error && inspection.providerId) {
|
|
return {
|
|
...featureMetadata('jobs'),
|
|
state: inspection.providerId === 'database' ? 'default' : 'configured',
|
|
providerId: inspection.providerId,
|
|
}
|
|
}
|
|
|
|
if (!inspection.error) {
|
|
throw new Error('Async jobs inspection failed without a configuration error')
|
|
}
|
|
const configuredFieldCount = ['TRIGGER_SECRET_KEY', 'TRIGGER_PROJECT_ID'].filter((key) =>
|
|
hasEnvCapabilityValue(values, key)
|
|
).length
|
|
const state = configuredFieldCount === 0 ? 'missing' : 'partial'
|
|
return {
|
|
...featureMetadata('jobs'),
|
|
state,
|
|
providerId: 'trigger-dev',
|
|
issue: issue(state, inspection.error),
|
|
}
|
|
}
|
|
|
|
function inspectCache(values: EnvCapabilityValues): CacheCapabilityStatus {
|
|
const inspection = inspectCapability(CACHE_CAPABILITY, values)
|
|
if (!inspection.error && inspection.providerId) {
|
|
return {
|
|
...featureMetadata('cache'),
|
|
state: inspection.providerId === 'database' ? 'default' : 'configured',
|
|
providerId: inspection.providerId,
|
|
}
|
|
}
|
|
|
|
if (!inspection.error) {
|
|
throw new Error('Cache inspection failed without a configuration error')
|
|
}
|
|
const redis = inspection.providers.find((provider) => provider.id === 'redis')
|
|
const state: CapabilityStatusIssue['state'] = redis?.state === 'invalid' ? 'invalid' : 'partial'
|
|
return {
|
|
...featureMetadata('cache'),
|
|
state,
|
|
providerId: 'redis',
|
|
issue: issue(state, inspection.error),
|
|
}
|
|
}
|
|
|
|
function inspectKnowledge(values: EnvCapabilityValues): KnowledgeCapabilityStatus {
|
|
const inspection = inspectCapability(OCR_CAPABILITY, values)
|
|
if (!inspection.error && inspection.providerId) {
|
|
return {
|
|
...featureMetadata('knowledge'),
|
|
state: inspection.providerId === 'local' ? 'default' : 'configured',
|
|
providerId: inspection.providerId,
|
|
}
|
|
}
|
|
|
|
if (!inspection.error) {
|
|
throw new Error('OCR inspection failed without a configuration error')
|
|
}
|
|
const selected = readConfiguredString(values, OCR_CAPABILITY.selectorKey)
|
|
const azureFields = ['OCR_AZURE_API_KEY', 'OCR_AZURE_ENDPOINT', 'OCR_AZURE_MODEL_NAME'] as const
|
|
const azureFieldCount = azureFields.filter((key) => hasEnvCapabilityValue(values, key)).length
|
|
const knownProvider =
|
|
selected === null ||
|
|
selected === 'local' ||
|
|
selected === 'mistral' ||
|
|
selected === 'azure-mistral'
|
|
const resolvedProvider = inspection.providerId
|
|
const selectedInspection = inspection.providers.find(
|
|
(provider) => provider.id === resolvedProvider
|
|
)
|
|
const state =
|
|
!knownProvider || selectedInspection?.state === 'invalid'
|
|
? 'invalid'
|
|
: selected === 'mistral' || selected === 'azure-mistral'
|
|
? azureFieldCount === 0 && selected === 'azure-mistral'
|
|
? 'missing'
|
|
: selected === 'mistral' && !hasEnvCapabilityValue(values, 'MISTRAL_API_KEY')
|
|
? 'missing'
|
|
: 'partial'
|
|
: 'partial'
|
|
|
|
return {
|
|
...featureMetadata('knowledge'),
|
|
state,
|
|
providerId:
|
|
selected === 'local' || selected === 'mistral' || selected === 'azure-mistral'
|
|
? selected
|
|
: selected === null
|
|
? resolvedProvider
|
|
: null,
|
|
issue: issue(
|
|
state,
|
|
inspection.error,
|
|
knownProvider
|
|
? inspection.error.message
|
|
: 'Unknown OCR_PROVIDER. Expected one of: local, mistral, azure-mistral'
|
|
),
|
|
}
|
|
}
|
|
|
|
function inspectLlm(values: EnvCapabilityValues): LlmCapabilityStatus {
|
|
const pools = {} as Record<LlmKeyPoolId, LlmKeyPoolStatus>
|
|
let configuredPoolCount = 0
|
|
let configuredKeyCount = 0
|
|
let effectiveKeyCount = 0
|
|
|
|
for (const id of Object.keys(LLM_KEY_POOLS) as LlmKeyPoolId[]) {
|
|
const definition = LLM_KEY_POOLS[id]
|
|
const rotationKeyCount = definition.keys.filter((key) =>
|
|
hasEnvCapabilityValue(values, key)
|
|
).length
|
|
const fallbackKeyConfigured =
|
|
'fallbackKey' in definition && hasEnvCapabilityValue(values, definition.fallbackKey)
|
|
const poolConfiguredKeyCount = rotationKeyCount + (fallbackKeyConfigured ? 1 : 0)
|
|
const poolEffectiveKeyCount = rotationKeyCount || (fallbackKeyConfigured ? 1 : 0)
|
|
const state = poolEffectiveKeyCount > 0 ? 'configured' : 'missing'
|
|
if (state === 'configured') configuredPoolCount += 1
|
|
configuredKeyCount += poolConfiguredKeyCount
|
|
effectiveKeyCount += poolEffectiveKeyCount
|
|
pools[id] = {
|
|
id,
|
|
state,
|
|
configuredKeyCount: poolConfiguredKeyCount,
|
|
effectiveKeyCount: poolEffectiveKeyCount,
|
|
fallbackKeyConfigured,
|
|
}
|
|
}
|
|
|
|
return {
|
|
...featureMetadata('llm'),
|
|
state: configuredPoolCount > 0 ? 'configured' : 'missing',
|
|
pools,
|
|
configuredPoolCount,
|
|
configuredKeyCount,
|
|
effectiveKeyCount,
|
|
}
|
|
}
|
|
|
|
function inspectOAuthClients(values: EnvCapabilityValues): OAuthClientStatuses {
|
|
const clients = {} as Record<OAuthClientCapabilityId, OAuthClientStatus>
|
|
let readyCount = 0
|
|
let partialCount = 0
|
|
let absentCount = 0
|
|
let invalidCount = 0
|
|
|
|
for (const id of Object.keys(OAUTH_CLIENT_CAPABILITIES) as OAuthClientCapabilityId[]) {
|
|
const inspection = inspectOAuthClientCapability(id, values)
|
|
if (inspection.state === 'ready') readyCount += 1
|
|
else if (inspection.state === 'partial') partialCount += 1
|
|
else if (inspection.state === 'absent') absentCount += 1
|
|
else invalidCount += 1
|
|
|
|
clients[id] = {
|
|
id,
|
|
state: inspection.state,
|
|
configuredFieldCount: OAUTH_CLIENT_CAPABILITIES[id].length - inspection.missingFields.length,
|
|
requiredFieldCount: OAUTH_CLIENT_CAPABILITIES[id].length,
|
|
missingFields: inspection.missingFields,
|
|
setupCommand: inspection.setupCommand,
|
|
}
|
|
}
|
|
|
|
return { clients, readyCount, partialCount, absentCount, invalidCount }
|
|
}
|
|
|
|
const FEATURE_STATUS_BUILDERS = {
|
|
email: inspectEmail,
|
|
storage: inspectStorage,
|
|
sandbox: inspectSandbox,
|
|
jobs: inspectJobs,
|
|
cache: inspectCache,
|
|
knowledge: inspectKnowledge,
|
|
llm: inspectLlm,
|
|
} satisfies Record<SetupStatusFeatureId, (values: EnvCapabilityValues) => unknown>
|
|
|
|
/** Builds a status snapshot without returning any configured secret values. */
|
|
export function buildEnvCapabilityStatus(values: EnvCapabilityValues): EnvCapabilityStatusSnapshot {
|
|
return {
|
|
features: {
|
|
email: FEATURE_STATUS_BUILDERS.email(values),
|
|
storage: FEATURE_STATUS_BUILDERS.storage(values),
|
|
sandbox: FEATURE_STATUS_BUILDERS.sandbox(values),
|
|
jobs: FEATURE_STATUS_BUILDERS.jobs(values),
|
|
cache: FEATURE_STATUS_BUILDERS.cache(values),
|
|
knowledge: FEATURE_STATUS_BUILDERS.knowledge(values),
|
|
llm: FEATURE_STATUS_BUILDERS.llm(values),
|
|
},
|
|
oauthClients: inspectOAuthClients(values),
|
|
}
|
|
}
|