e115934061
Publish `@librechat/data-schemas` to NPM / pack (push) Failing after 8s
Publish `@librechat/client` to NPM / pack (push) Failing after 0s
GitNexus Index / index (push) Failing after 1s
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been skipped
Sync Helm Chart Tags / Sync chart tags (push) Failing after 2s
Publish `librechat-data-provider` to NPM / pack (push) Failing after 1s
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Failing after 1s
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Failing after 0s
Sync Helm Chart Tags / Ignore non-main push (push) Has been skipped
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Failing after 1s
Publish `@librechat/client` to NPM / publish-npm (push) Has been cancelled
Publish `librechat-data-provider` to NPM / publish-npm (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Publish `@librechat/data-schemas` to NPM / publish-npm (push) Has been cancelled
122 lines
4.7 KiB
JavaScript
122 lines
4.7 KiB
JavaScript
const express = require('express');
|
|
const { logger } = require('@librechat/data-schemas');
|
|
const {
|
|
createMessageFilterPii,
|
|
generateCheckAccess,
|
|
skipAgentCheck,
|
|
applyResumeContext,
|
|
GenerationJobManager,
|
|
} = require('@librechat/api');
|
|
const { PermissionTypes, Permissions, PermissionBits } = require('librechat-data-provider');
|
|
const {
|
|
moderateText,
|
|
// validateModel,
|
|
validateConvoAccess,
|
|
buildEndpointOption,
|
|
canAccessAgentFromBody,
|
|
} = require('~/server/middleware');
|
|
const { initializeClient } = require('~/server/services/Endpoints/agents');
|
|
const AgentController = require('~/server/controllers/agents/request');
|
|
const ResumeController = require('~/server/controllers/agents/resume');
|
|
const addTitle = require('~/server/services/Endpoints/agents/title');
|
|
const { getRoleByName } = require('~/models');
|
|
|
|
const router = express.Router();
|
|
|
|
const checkAgentAccess = generateCheckAccess({
|
|
permissionType: PermissionTypes.AGENTS,
|
|
permissions: [Permissions.USE],
|
|
skipCheck: skipAgentCheck,
|
|
getRoleByName,
|
|
});
|
|
const checkAgentResourceAccess = canAccessAgentFromBody({
|
|
requiredPermission: PermissionBits.VIEW,
|
|
});
|
|
|
|
/**
|
|
* Replay the paused turn's graph-determining config onto a resume request BEFORE the
|
|
* rest of the chain (PII filter, agent-access, buildEndpointOption) reads it. The client
|
|
* can't reliably re-send the ephemeral-agent config after a reload/cross-session, so the
|
|
* server restores it from the pending action — the resume then rebuilds the SAME
|
|
* agent/graph the run paused on (and a crafted resume can't swap the tool set). No-op for
|
|
* every non-resume route.
|
|
*/
|
|
const restoreResumeContext = async (req, res, next) => {
|
|
if (req.path !== '/resume') {
|
|
return next();
|
|
}
|
|
try {
|
|
const streamId = req.body?.conversationId;
|
|
if (streamId) {
|
|
const job = await GenerationJobManager.getJob(streamId);
|
|
const resumeContext = job?.metadata?.pendingAction?.resumeContext;
|
|
applyResumeContext(req.body, resumeContext);
|
|
// Replay the paused turn's resolved model parameters. Ephemeral agents derive these
|
|
// (temperature, max tokens, custom endpoint params) from the request body, which the
|
|
// resume payload omits — without this the continuation runs with defaults. They're
|
|
// scattered top-level fields (folded into model_parameters by buildOptions' rest
|
|
// spread), not part of the RESUME_CONTEXT_KEYS allowlist, so merge them back here.
|
|
// Authoritative: overwrites any client-supplied values with the captured set.
|
|
// `model` is excluded — it's replayed via RESUME_CONTEXT_KEYS to the exact value the
|
|
// resume fingerprint was pinned on, so overwriting it here could trip that check.
|
|
const resumedModelParameters = resumeContext?.model_parameters;
|
|
if (resumedModelParameters && typeof resumedModelParameters === 'object') {
|
|
const { model: _replayedModel, ...replayParams } = resumedModelParameters;
|
|
Object.assign(req.body, replayParams);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
logger.warn('[agents/chat] Failed to restore resume context', err?.message ?? err);
|
|
}
|
|
next();
|
|
};
|
|
|
|
router.use(restoreResumeContext);
|
|
router.use(createMessageFilterPii({ getConfig: (req) => req.config?.messageFilter?.pii }));
|
|
router.use(moderateText);
|
|
router.use(checkAgentAccess);
|
|
router.use(checkAgentResourceAccess);
|
|
router.use(validateConvoAccess);
|
|
router.use(buildEndpointOption);
|
|
|
|
const controller = async (req, res, next) => {
|
|
await AgentController(req, res, next, initializeClient, addTitle);
|
|
};
|
|
|
|
const resumeController = async (req, res, next) => {
|
|
await ResumeController(req, res, next, initializeClient, addTitle);
|
|
};
|
|
|
|
/**
|
|
* @route POST /resume
|
|
* @desc Resume a generation paused for human-in-the-loop review (tool approval or
|
|
* ask-user answer). Shares this router's middleware so the agent/endpoint are
|
|
* reconstructed from the request exactly like a normal turn. Declared before
|
|
* `/:endpoint` so it is not captured as an ephemeral endpoint name.
|
|
* @access Private
|
|
* @returns {void}
|
|
*/
|
|
router.post('/resume', resumeController);
|
|
|
|
/**
|
|
* @route POST / (regular endpoint)
|
|
* @desc Chat with an assistant
|
|
* @access Public
|
|
* @param {express.Request} req - The request object, containing the request data.
|
|
* @param {express.Response} res - The response object, used to send back a response.
|
|
* @returns {void}
|
|
*/
|
|
router.post('/', controller);
|
|
|
|
/**
|
|
* @route POST /:endpoint (ephemeral agents)
|
|
* @desc Chat with an assistant
|
|
* @access Public
|
|
* @param {express.Request} req - The request object, containing the request data.
|
|
* @param {express.Response} res - The response object, used to send back a response.
|
|
* @returns {void}
|
|
*/
|
|
router.post('/:endpoint', controller);
|
|
|
|
module.exports = router;
|