LibreChat/api/server/services/Config/app.js
Danny Avila fcae1025c0
🧳 feat: Register Principal-Owned Code Environments (#15365)
* feat: add principal-owned code environments

* fix: address code environment CI coverage

* fix: expose principal code environments in endpoint config

* fix: reject missing code environment bodies

* fix: harden code environment lifecycle

* fix: revalidate principal code environments

* fix: synchronize code environment authorization

* fix: bind code environments to current principals

* fix: fail closed on code ACL cache errors

* fix: prevent code environment override shadowing

* fix: preserve principal environment defaults

* fix: suppress revoked code environment aliases

* fix: fail closed on environment augmentation

* fix: narrow code environment defaults

* fix: isolate code environment fallback

* style: sort code config imports
2026-08-30 19:33:19 -04:00

104 lines
3.2 KiB
JavaScript

const mongoose = require('mongoose');
const { CacheKeys } = require('librechat-data-provider');
const { AppService, logger } = require('@librechat/data-schemas');
const {
createAppConfigService,
clearMcpConfigCache,
createCodeEnvironmentRegistry,
mergeAccessibleCodeEnvironments,
cacheConfig,
standardCache,
} = require('@librechat/api');
const { setCachedTools, invalidateCachedTools } = require('./getCachedTools');
const { loadAndFormatTools } = require('~/server/services/start/tools');
const loadCustomConfig = require('./loadCustomConfig');
const getLogStores = require('~/cache/getLogStores');
const paths = require('~/config/paths');
const db = require('~/models');
let codeEnvironmentRegistry;
function getCodeEnvironmentRegistry() {
if (codeEnvironmentRegistry == null) {
codeEnvironmentRegistry = createCodeEnvironmentRegistry(mongoose, {
configurationCache: cacheConfig.USE_REDIS
? standardCache('CODE_ENVIRONMENT_CONFIG')
: undefined,
});
}
return codeEnvironmentRegistry;
}
async function invalidateCodeEnvironmentConfigCache(tenantId) {
await getCodeEnvironmentRegistry().invalidateAccessibleConfigurations(tenantId);
}
const loadBaseConfig = async () => {
/** @type {TCustomConfig} */
const config = (await loadCustomConfig()) ?? {};
/** @type {Record<string, FunctionTool>} */
const systemTools = loadAndFormatTools({
adminFilter: config.filteredTools,
adminIncluded: config.includedTools,
directory: paths.structuredTools,
});
return AppService({ config, paths, systemTools });
};
const { getAppConfig, clearAppConfigCache, clearOverrideCache } = createAppConfigService({
loadBaseConfig,
setCachedTools,
getCache: getLogStores,
cacheKeys: CacheKeys,
getApplicableConfigs: db.getApplicableConfigs,
getUserPrincipals: db.getUserPrincipals,
augmentConfig: ({ appConfig, baseConfig, principals, options }) => {
if (!options.userId) return appConfig;
return mergeAccessibleCodeEnvironments({
appConfig,
deploymentConfig: baseConfig,
actor: {
userId: options.userId,
role: options.role ?? null,
idOnTheSource: options.idOnTheSource ?? null,
principals,
},
registry: getCodeEnvironmentRegistry(),
});
},
});
/**
* Invalidate all config-related caches after an admin config mutation.
* Clears the base config, per-principal override caches, tool caches,
* and the MCP config-source server cache.
* @param {string} [tenantId] - Optional tenant ID to scope override cache clearing.
*/
async function invalidateConfigCaches(tenantId) {
const results = await Promise.allSettled([
clearAppConfigCache(),
clearOverrideCache(tenantId),
invalidateCachedTools({ invalidateGlobal: true }),
clearMcpConfigCache(),
]);
const labels = [
'clearAppConfigCache',
'clearOverrideCache',
'invalidateCachedTools',
'clearMcpConfigCache',
];
for (let i = 0; i < results.length; i++) {
if (results[i].status === 'rejected') {
logger.error(`[invalidateConfigCaches] ${labels[i]} failed:`, results[i].reason);
}
}
}
module.exports = {
getAppConfig,
clearAppConfigCache,
clearOverrideCache,
invalidateConfigCaches,
getCodeEnvironmentRegistry,
invalidateCodeEnvironmentConfigCache,
};