chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
const { logger, getTenantId } = require('@librechat/data-schemas');
|
||||
const { EModelEndpoint, openAISettings, anthropicSettings } = require('librechat-data-provider');
|
||||
const { getModelsConfig } = require('~/server/controllers/ModelController');
|
||||
|
||||
/**
|
||||
* Last-resort hardcoded defaults used only when the runtime models config is
|
||||
* unavailable or returns no models for the endpoint.
|
||||
*/
|
||||
const FALLBACK_MODEL_BY_ENDPOINT = {
|
||||
[EModelEndpoint.openAI]: openAISettings.model.default,
|
||||
[EModelEndpoint.anthropic]: anthropicSettings.model.default,
|
||||
};
|
||||
|
||||
/**
|
||||
* Picks the first available model for an endpoint from a runtime models config.
|
||||
*
|
||||
* @param {string} endpoint - The endpoint key (e.g. EModelEndpoint.anthropic).
|
||||
* @param {TModelsConfig} [modelsConfig] - Map of endpoint -> available model list.
|
||||
* @returns {string | undefined} The first model for the endpoint, or undefined.
|
||||
*/
|
||||
function pickFirstConfiguredModel(endpoint, modelsConfig) {
|
||||
const models = modelsConfig?.[endpoint];
|
||||
if (!Array.isArray(models)) {
|
||||
return undefined;
|
||||
}
|
||||
for (const model of models) {
|
||||
if (typeof model === 'string' && model.length > 0) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the default model that imported conversations should be saved with
|
||||
* for a given endpoint. Prefers the first model exposed by the runtime models
|
||||
* config (admin-configured / provider-discovered), and only falls back to the
|
||||
* hardcoded per-endpoint default if the runtime config is empty or fails.
|
||||
*
|
||||
* @param {object} args
|
||||
* @param {string} args.endpoint - The endpoint key the import is targeting.
|
||||
* @param {string} args.requestUserId - The id of the importing user.
|
||||
* @param {string} [args.userRole] - The role of the importing user.
|
||||
* @returns {Promise<string>} The default model name to persist on the conversation.
|
||||
*/
|
||||
async function resolveImportDefaultModel({ endpoint, requestUserId, userRole }) {
|
||||
try {
|
||||
const modelsConfig = await getModelsConfig({
|
||||
user: { id: requestUserId, role: userRole, tenantId: getTenantId() },
|
||||
});
|
||||
const configured = pickFirstConfiguredModel(endpoint, modelsConfig);
|
||||
if (configured) {
|
||||
return configured;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[import] Failed to resolve default model from modelsConfig for ${endpoint}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
return FALLBACK_MODEL_BY_ENDPOINT[endpoint] ?? openAISettings.model.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preferred endpoint order for conversations cloned without a known source
|
||||
* endpoint. OpenAI is first so deployments that expose it keep prior behavior;
|
||||
* any other configured endpoint is still selected when these are unavailable.
|
||||
*/
|
||||
const DEFAULT_ENDPOINT_PREFERENCE = [
|
||||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.anthropic,
|
||||
EModelEndpoint.google,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.bedrock,
|
||||
];
|
||||
|
||||
/**
|
||||
* Endpoints excluded as fork targets because they are stateful: each
|
||||
* conversation needs an assistant_id and thread_id that a cloned conversation
|
||||
* never creates, so the assistants chat controller rejects the first follow-up
|
||||
* ("Missing thread_id for existing conversation"). A fork must land on a
|
||||
* stateless chat endpoint. These can still surface in the runtime models config
|
||||
* (e.g. a deployment exposing only assistant models), so filter them out.
|
||||
*/
|
||||
const EXCLUDED_FORK_ENDPOINTS = new Set([
|
||||
EModelEndpoint.assistants,
|
||||
EModelEndpoint.azureAssistants,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Resolves an endpoint and model the requesting user can actually use, for
|
||||
* conversations cloned without a known source endpoint (shared forks, whose
|
||||
* original endpoint is stripped from the sanitized payload). Picks the first
|
||||
* preferred endpoint exposing models, then any other configured endpoint
|
||||
* (excluding stateful assistant endpoints, which a fork cannot resume), so a
|
||||
* deployment that doesn't expose OpenAI doesn't produce a conversation whose
|
||||
* first message is rejected by model validation. Falls back to OpenAI defaults
|
||||
* only when the runtime models config is empty or unavailable.
|
||||
*
|
||||
* @param {object} args
|
||||
* @param {string} args.requestUserId - The id of the requesting user.
|
||||
* @param {string} [args.userRole] - The role of the requesting user.
|
||||
* @returns {Promise<{ endpoint: string, model: string }>} A usable endpoint and model.
|
||||
*/
|
||||
async function resolveImportDefaultEndpoint({ requestUserId, userRole }) {
|
||||
try {
|
||||
const modelsConfig = await getModelsConfig({
|
||||
user: { id: requestUserId, role: userRole, tenantId: getTenantId() },
|
||||
});
|
||||
if (modelsConfig) {
|
||||
const orderedEndpoints = [
|
||||
...DEFAULT_ENDPOINT_PREFERENCE,
|
||||
...Object.keys(modelsConfig).filter(
|
||||
(endpoint) => !DEFAULT_ENDPOINT_PREFERENCE.includes(endpoint),
|
||||
),
|
||||
];
|
||||
for (const endpoint of orderedEndpoints) {
|
||||
if (EXCLUDED_FORK_ENDPOINTS.has(endpoint)) {
|
||||
continue;
|
||||
}
|
||||
const model = pickFirstConfiguredModel(endpoint, modelsConfig);
|
||||
if (model) {
|
||||
return { endpoint, model };
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[import] Failed to resolve a default endpoint from modelsConfig: ${error.message}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
endpoint: EModelEndpoint.openAI,
|
||||
model: FALLBACK_MODEL_BY_ENDPOINT[EModelEndpoint.openAI] ?? openAISettings.model.default,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FALLBACK_MODEL_BY_ENDPOINT,
|
||||
pickFirstConfiguredModel,
|
||||
resolveImportDefaultModel,
|
||||
resolveImportDefaultEndpoint,
|
||||
};
|
||||
Reference in New Issue
Block a user