🗂️ feat: Add Deployment Skill Directory (#13523)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions

* feat: Add deployment skill directory

* chore: Address deployment skill review feedback

* fix: Include deployment skill file metadata

* test: Add deployment skills e2e smoke test
This commit is contained in:
Danny Avila 2026-06-05 10:24:28 -04:00 • committed by GitHub
parent 6357ea10c1
commit 2c8d54e18c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1836 additions and 69 deletions

View file

@ -5,6 +5,8 @@ const {
toSkillStatesRecord,
validateSkillStatesPayload,
pruneOrphanSkillStates,
getDeploymentSkillIds,
mergeDeploymentSkillIds,
} = require('@librechat/api');
const { ResourceType, PermissionBits } = require('librechat-data-provider');
const { findAccessibleResources } = require('~/server/services/PermissionService');
@ -21,15 +23,20 @@ function buildPruneDeps(user) {
const existing = await Skill.find({ _id: { $in: validIds } })
.select('_id')
.lean();
return existing.map((doc) => doc._id.toString());
const deploymentIds = getDeploymentSkillIds()
.map((id) => id.toString())
.filter((id) => validIds.includes(id));
return [...existing.map((doc) => doc._id.toString()), ...deploymentIds];
},
findAccessibleSkillIds: () =>
findAccessibleResources({
userId: user.id,
role: user.role,
resourceType: ResourceType.SKILL,
requiredPermissions: PermissionBits.VIEW,
}),
findAccessibleSkillIds: async () =>
mergeDeploymentSkillIds(
await findAccessibleResources({
userId: user.id,
role: user.role,
resourceType: ResourceType.SKILL,
requiredPermissions: PermissionBits.VIEW,
}),
),
};
}

View file

@ -150,7 +150,9 @@ jest.mock('~/server/services/Files/permissions', () => ({
jest.mock('~/server/services/Endpoints/agents/skillDeps', () => ({
getSkillToolDeps: mockGetSkillToolDeps,
getSkillDbMethods: jest.fn(() => ({})),
canAuthorSkillFiles: mockCanAuthorSkillFiles,
withDeploymentSkillIds: jest.fn((ids = []) => ids),
enrichWithSkillConfigurable: mockEnrichWithSkillConfigurable,
buildSkillPrimedIdsByName: mockBuildSkillPrimedIdsByName,
buildAgentToolContext: mockBuildAgentToolContext,

View file

@ -200,7 +200,9 @@ jest.mock('~/server/services/Files/permissions', () => ({
jest.mock('~/server/services/Endpoints/agents/skillDeps', () => ({
getSkillToolDeps: mockGetSkillToolDeps,
getSkillDbMethods: jest.fn(() => ({})),
canAuthorSkillFiles: mockCanAuthorSkillFiles,
withDeploymentSkillIds: jest.fn((ids = []) => ids),
enrichWithSkillConfigurable: mockEnrichWithSkillConfigurable,
buildSkillPrimedIdsByName: mockBuildSkillPrimedIdsByName,
buildAgentToolContext: mockBuildAgentToolContext,

View file

@ -47,7 +47,9 @@ const {
} = require('~/server/services/PermissionService');
const {
getSkillToolDeps,
getSkillDbMethods,
canAuthorSkillFiles,
withDeploymentSkillIds,
buildAgentToolContext,
enrichLoadedToolsWithAgentContext,
} = require('~/server/services/Endpoints/agents/skillDeps');
@ -229,6 +231,7 @@ const OpenAIChatCompletionController = async (req, res) => {
endpoint: agent.provider,
model_parameters: agent.model_parameters ?? {},
};
const skillDbMethods = getSkillDbMethods();
// `filterFilesByAgentAccess` is intentionally omitted: it calls
// `checkPermission` with `resourceType: AGENT`, but this route
@ -246,21 +249,23 @@ const OpenAIChatCompletionController = async (req, res) => {
getUserCodeFiles: db.getUserCodeFiles,
getToolFilesByIds: db.getToolFilesByIds,
getCodeGeneratedFiles: db.getCodeGeneratedFiles,
listSkillsByAccess: db.listSkillsByAccess,
listAlwaysApplySkills: db.listAlwaysApplySkills,
getSkillByName: db.getSkillByName,
listSkillsByAccess: skillDbMethods.listSkillsByAccess,
listAlwaysApplySkills: skillDbMethods.listAlwaysApplySkills,
getSkillByName: skillDbMethods.getSkillByName,
};
const enabledCapabilities = new Set(agentsEConfig?.capabilities);
const skillsCapabilityEnabled = enabledCapabilities.has(AgentCapabilities.skills);
const ephemeralSkillsToggle = req.body?.ephemeralAgent?.skills === true;
const accessibleSkillIds = skillsCapabilityEnabled
? await findAccessibleResources({
userId: req.user.id,
role: req.user.role,
resourceType: ResourceType.SKILL,
requiredPermissions: PermissionBits.VIEW,
})
? withDeploymentSkillIds(
await findAccessibleResources({
userId: req.user.id,
role: req.user.role,
resourceType: ResourceType.SKILL,
requiredPermissions: PermissionBits.VIEW,
}),
)
: [];
const editableSkillIds = skillsCapabilityEnabled
? await findAccessibleResources({

View file

@ -56,7 +56,9 @@ const {
} = require('~/server/services/PermissionService');
const {
getSkillToolDeps,
getSkillDbMethods,
canAuthorSkillFiles,
withDeploymentSkillIds,
buildAgentToolContext,
enrichLoadedToolsWithAgentContext,
} = require('~/server/services/Endpoints/agents/skillDeps');
@ -351,6 +353,7 @@ const createResponse = async (req, res) => {
// Create tool loader
const loadTools = createToolLoader(abortController.signal);
const skillDbMethods = getSkillDbMethods();
// Initialize the agent first to check for disableStreaming
const endpointOption = {
@ -374,9 +377,9 @@ const createResponse = async (req, res) => {
getUserCodeFiles: db.getUserCodeFiles,
getToolFilesByIds: db.getToolFilesByIds,
getCodeGeneratedFiles: db.getCodeGeneratedFiles,
listSkillsByAccess: db.listSkillsByAccess,
listAlwaysApplySkills: db.listAlwaysApplySkills,
getSkillByName: db.getSkillByName,
listSkillsByAccess: skillDbMethods.listSkillsByAccess,
listAlwaysApplySkills: skillDbMethods.listAlwaysApplySkills,
getSkillByName: skillDbMethods.getSkillByName,
};
const enabledCapabilities = new Set(
@ -385,12 +388,14 @@ const createResponse = async (req, res) => {
const skillsCapabilityEnabled = enabledCapabilities.has(AgentCapabilities.skills);
const ephemeralSkillsToggle = req.body?.ephemeralAgent?.skills === true;
const accessibleSkillIds = skillsCapabilityEnabled
? await findAccessibleResources({
userId: req.user.id,
role: req.user.role,
resourceType: ResourceType.SKILL,
requiredPermissions: PermissionBits.VIEW,
})
? withDeploymentSkillIds(
await findAccessibleResources({
userId: req.user.id,
role: req.user.role,
resourceType: ResourceType.SKILL,
requiredPermissions: PermissionBits.VIEW,
}),
)
: [];
const editableSkillIds = skillsCapabilityEnabled
? await findAccessibleResources({