mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-11-12 09:45:47 +00:00
* 🔭 feat: Admin Config Read APIs (Resolved and Effective Config, Scope Catalog, Env Status) Admin clients could read one config document at a time, or the deployment's merged config, but nothing showed what a profile or a user resolves to: a client had to re-implement the merge (Base-first precedence, merge keys, tombstones, the interface permission filter, base-only fields, runtime stops, secret rebinding, the env allowlist) to preview it, and had no way to see why part of a profile is ignored. Endpoint, model and tool pickers came from the end-user routes, called as the signed-in admin, so they showed the admin's own scope and keys and needed a browser User-Agent. The admin config API now has four read routes. GET /resolved/:principalType/:principalId and GET /effective?userId= return librechat.yaml, the documents that merge over it in merge order, each with the parts the merge does not apply and why, and the result, in librechat.yaml shape. They come from resolveConfigLayers, which tracks the same fold the served merge runs and is pinned to it by parity tests, and from the stored documents rather than the override cache. GET /catalog lists the endpoints (with the reason one is not served), models (served, configured and defaults), MCP servers and tools a scope serves, resolved for the scope rather than the caller and never with a user's stored key; GET /env-status reports whether each env name the config uses is set, never its value. Every read is filtered to the sections the caller can read, redacted as reads redact, and scoped to the caller's tenant. The admin Langfuse routes now say why a deployment offers no connection settings. Meta reports resolvedApi, catalogApi, envStatus and limits.catalogModelsTtlMs. * 🪞 fix: Report Duplicate Custom Endpoints from the Served Definition When librechat.yaml defines a custom endpoint name twice, or both `Ollama` and `ollama`, the catalog kept the first definition for a name's env names, reason and models, while `loadCustomEndpointsConfig` serves the last servable one. An entry could pair the served config of one definition with the env names of another. `resolveServedCustomEndpoints` now holds the runtime rule (the last definition with a name, key, base URL and models wins under its served name), and `loadCustomEndpointsConfig`, `loadConfigModels` and the catalog all use it. A name with no servable definition is reported from its last one. * ⏱️ feat: Make the Admin Catalog Model-List Lifetime Configurable `GET /api/admin/config/catalog` reused a scope's model lists for a fixed two minutes. The new librechat.yaml field `endpoints.catalogModelsTtlMs` sets that lifetime (milliseconds, `0` to one hour, two minutes by default); `0` resolves the lists on every read. It is a base-only path, so a profile cannot change it and `GET /meta` reports one value, now read from the deployment's config on each request as `limits.catalogModelsTtlMs`.
56 lines
2 KiB
JavaScript
56 lines
2 KiB
JavaScript
const { logger } = require('@librechat/data-schemas');
|
|
const { listAvailableTools } = require('@librechat/api');
|
|
const { getCachedTools, setCachedTools } = require('~/server/services/Config');
|
|
const { availableTools, toolkits } = require('~/app/clients/tools');
|
|
const { getAppConfig } = require('~/server/services/Config');
|
|
|
|
const getAvailableTools = async (req, res) => {
|
|
try {
|
|
const userId = req.user?.id;
|
|
if (!userId) {
|
|
logger.warn('[getAvailableTools] User ID not found in request');
|
|
return res.status(401).json({ message: 'Unauthorized' });
|
|
}
|
|
|
|
const appConfig =
|
|
req.config ??
|
|
(await getAppConfig({
|
|
role: req.user?.role,
|
|
userId: req.user?.id,
|
|
tenantId: req.user?.tenantId,
|
|
}));
|
|
|
|
let toolDefinitions = await getCachedTools();
|
|
|
|
if (toolDefinitions == null && appConfig?.availableTools != null) {
|
|
logger.warn('[getAvailableTools] Tool cache was empty, re-initializing from app config');
|
|
await setCachedTools(appConfig.availableTools);
|
|
toolDefinitions = appConfig.availableTools;
|
|
}
|
|
|
|
/**
|
|
* `getAvailableTools` serves BOTH tool dialogs — /api/agents/tools and
|
|
* /api/assistants/tools. Tools flagged `agentsOnly` in the manifest (e.g.
|
|
* ask_user_question, which pauses an agents run via a LangGraph interrupt)
|
|
* cannot work on the assistants runtime: it executes tools directly with no
|
|
* run to pause and no resume surface, so attaching one there guarantees a
|
|
* permanent tool error. Scope them out of the assistants listing by route.
|
|
*/
|
|
const toolsOutput = listAvailableTools({
|
|
manifest: availableTools,
|
|
toolkits,
|
|
toolDefinitions,
|
|
githubCompare: appConfig?.githubCompare,
|
|
forAssistants: req.baseUrl?.includes('/assistants') === true,
|
|
});
|
|
|
|
res.status(200).json(toolsOutput);
|
|
} catch (error) {
|
|
logger.error('[getAvailableTools]', error);
|
|
res.status(500).json({ message: error.message });
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
getAvailableTools,
|
|
};
|