Add config value type converters for numbers and booleans

This commit is contained in:
Cohee
2025-02-20 21:53:48 +02:00
parent 7571552fef
commit 3f03936125
18 changed files with 88 additions and 53 deletions

View File

@ -261,26 +261,26 @@ app.use(responseTime());
/** @type {number} */
const server_port = cliArguments.port ?? getConfigValue('port', DEFAULT_PORT);
const server_port = cliArguments.port ?? getConfigValue('port', DEFAULT_PORT, 'number');
/** @type {boolean} */
const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN, 'boolean')) && !cliArguments.ssl;
/** @type {boolean} */
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN, 'boolean');
/** @type {string} */
const listenAddressIPv6 = cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', DEFAULT_LISTEN_ADDRESS_IPV6);
/** @type {string} */
const listenAddressIPv4 = cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', DEFAULT_LISTEN_ADDRESS_IPV4);
/** @type {boolean} */
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY, 'boolean');
const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST, 'boolean');
/** @type {string} */
globalThis.DATA_ROOT = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
/** @type {boolean} */
const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED, 'boolean');
const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH, 'boolean');
const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH, 'boolean');
/** @type {boolean} */
const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS, 'boolean');
const uploadsPath = path.join(globalThis.DATA_ROOT, UPLOADS_DIRECTORY);
@ -293,15 +293,15 @@ let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protoc
/** @type {string} */
const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);
/** @type {number} */
const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT);
const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT, 'number');
/** @type {boolean} */
const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6);
const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6, 'boolean');
/** @type {boolean} */
const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST);
const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST, 'boolean');
const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED);
const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED, 'boolean');
const proxyUrl = cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', DEFAULT_PROXY_URL);
const proxyBypass = cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', DEFAULT_PROXY_BYPASS);
@ -395,7 +395,7 @@ if (enableCorsProxy) {
function getSessionCookieAge() {
// Defaults to "no expiration" if not set
const configValue = getConfigValue('sessionTimeout', -1);
const configValue = getConfigValue('sessionTimeout', -1, 'number');
// Convert to milliseconds
if (configValue > 0) {
@ -924,7 +924,7 @@ function setWindowTitle(title) {
function logSecurityAlert(message) {
if (basicAuthMode || enableWhitelist) return; // safe!
console.error(color.red(message));
if (getConfigValue('securityOverride', false)) {
if (getConfigValue('securityOverride', false, 'boolean')) {
console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
return;
}

View File

@ -106,8 +106,8 @@ async function sendClaudeRequest(request, response) {
const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString();
const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE);
const divider = '-'.repeat(process.stdout.columns);
const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false) && request.body.model.startsWith('claude-3');
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1);
const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean') && request.body.model.startsWith('claude-3');
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
// Disabled if not an integer or negative, or if the model doesn't support it
if (!Number.isInteger(cachingAtDepth) || cachingAtDepth < 0 || !request.body.model.startsWith('claude-3')) {
cachingAtDepth = -1;
@ -969,7 +969,7 @@ router.post('/generate', jsonParser, function (request, response) {
bodyParams.logprobs = true;
}
if (getConfigValue('openai.randomizeUserId', false)) {
if (getConfigValue('openai.randomizeUserId', false, 'boolean')) {
bodyParams['user'] = uuidv4();
}
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
@ -1008,7 +1008,7 @@ router.post('/generate', jsonParser, function (request, response) {
bodyParams['include_reasoning'] = true;
}
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1);
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) {
cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth);
}

View File

@ -372,8 +372,8 @@ router.post('/generate', jsonParser, async function (request, response) {
}
if (request.body.api_type === TEXTGEN_TYPES.OLLAMA) {
const keepAlive = getConfigValue('ollama.keepAlive', -1);
const numBatch = getConfigValue('ollama.batchSize', -1);
const keepAlive = getConfigValue('ollama.keepAlive', -1, 'number');
const numBatch = getConfigValue('ollama.batchSize', -1, 'number');
if (numBatch > 0) {
request.body['num_batch'] = numBatch;
}

View File

@ -24,7 +24,7 @@ import { importRisuSprites } from './sprites.js';
const defaultAvatarPath = './public/img/ai4.png';
// KV-store for parsed character data
const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100)); // MB
const cacheCapacity = getConfigValue('cardsCacheCapacity', 100, 'number'); // MB
// With 100 MB limit it would take roughly 3000 characters to reach this limit
const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity);
// Some Android devices require tighter memory management

View File

@ -19,9 +19,9 @@ import {
formatBytes,
} from '../util.js';
const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true);
const maxTotalChatBackups = Number(getConfigValue('backups.chat.maxTotalBackups', -1));
const throttleInterval = Number(getConfigValue('backups.chat.throttleInterval', 10_000));
const isBackupEnabled = getConfigValue('backups.chat.enabled', true, 'boolean');
const maxTotalChatBackups = getConfigValue('backups.chat.maxTotalBackups', -1, 'number');
const throttleInterval = getConfigValue('backups.chat.throttleInterval', 10_000, 'number');
/**
* Saves a chat to the backups directory.

View File

@ -165,7 +165,7 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
*/
export async function checkForNewContent(directoriesList, forceCategories = []) {
try {
const contentCheckSkip = getConfigValue('skipContentCheck', false);
const contentCheckSkip = getConfigValue('skipContentCheck', false, 'boolean');
if (contentCheckSkip && forceCategories?.length === 0) {
return;
}

View File

@ -183,7 +183,7 @@ router.post('/read', jsonParser, (request, response) => {
});
router.post('/view', jsonParser, async (request, response) => {
const allowKeysExposure = getConfigValue('allowKeysExposure', false);
const allowKeysExposure = getConfigValue('allowKeysExposure', false, 'boolean');
if (!allowKeysExposure) {
console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.yaml is set to true');
@ -205,7 +205,7 @@ router.post('/view', jsonParser, async (request, response) => {
});
router.post('/find', jsonParser, (request, response) => {
const allowKeysExposure = getConfigValue('allowKeysExposure', false);
const allowKeysExposure = getConfigValue('allowKeysExposure', false, 'boolean');
const key = request.body.key;
if (!allowKeysExposure && !EXPORTABLE_KEYS.includes(key)) {

View File

@ -11,9 +11,9 @@ import { jsonParser } from '../express-common.js';
import { getAllUserHandles, getUserDirectories } from '../users.js';
import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
const ENABLE_EXTENSIONS = !!getConfigValue('extensions.enabled', true);
const ENABLE_EXTENSIONS_AUTO_UPDATE = !!getConfigValue('extensions.autoUpdate', true);
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
const ENABLE_EXTENSIONS = getConfigValue('extensions.enabled', true, 'boolean');
const ENABLE_EXTENSIONS_AUTO_UPDATE = getConfigValue('extensions.autoUpdate', true, 'boolean');
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean');
// 10 minutes
const AUTOSAVE_INTERVAL = 10 * 60 * 1000;

View File

@ -12,8 +12,8 @@ import { getAllUserHandles, getUserDirectories } from '../users.js';
import { getConfigValue } from '../util.js';
import { jsonParser } from '../express-common.js';
const thumbnailsEnabled = !!getConfigValue('thumbnails.enabled', true);
const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnails.quality', 95))));
const thumbnailsEnabled = !!getConfigValue('thumbnails.enabled', true, 'boolean');
const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnails.quality', 95, 'number'))));
const pngFormat = String(getConfigValue('thumbnails.format', 'jpg')).toLowerCase().trim() === 'png';
/** @type {Record<string, number[]>} */

View File

@ -56,7 +56,7 @@ export const TEXT_COMPLETION_MODELS = [
];
const CHARS_PER_TOKEN = 3.35;
const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true);
const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true, 'boolean');
/**
* Gets a path to the tokenizer model. Downloads the model if it's a URL.

View File

@ -7,7 +7,7 @@ import { jsonParser, getIpFromRequest } from '../express-common.js';
import { color, Cache, getConfigValue } from '../util.js';
import { KEY_PREFIX, getUserAvatar, toKey, getPasswordHash, getPasswordSalt } from '../users.js';
const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false);
const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false, 'boolean');
const MFA_CACHE = new Cache(5 * 60 * 1000);
export const router = express.Router();

View File

@ -7,8 +7,8 @@ import storage from 'node-persist';
import { getAllUserHandles, toKey, getPasswordHash } from '../users.js';
import { getConfig, getConfigValue, safeReadFileSync } from '../util.js';
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean');
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean');
const basicAuthMiddleware = async function (request, response, callback) {
const unauthorizedWebpage = safeReadFileSync('./public/error/unauthorized.html') ?? '';

View File

@ -8,7 +8,7 @@ import { getIpFromRequest } from '../express-common.js';
import { color, getConfigValue, safeReadFileSync } from '../util.js';
const whitelistPath = path.join(process.cwd(), './whitelist.txt');
const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false);
const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false, 'boolean');
let whitelist = getConfigValue('whitelist', []);
let knownIPs = new Set();

View File

@ -7,8 +7,8 @@ import { default as git } from 'simple-git';
import { sync as commandExistsSync } from 'command-exists';
import { getConfigValue, color } from './util.js';
const enableServerPlugins = !!getConfigValue('enableServerPlugins', false);
const enableServerPluginsAutoUpdate = !!getConfigValue('enableServerPluginsAutoUpdate', true);
const enableServerPlugins = getConfigValue('enableServerPlugins', false, 'boolean');
const enableServerPluginsAutoUpdate = getConfigValue('enableServerPluginsAutoUpdate', true, 'boolean');
/**
* Map of loaded plugins.

View File

@ -572,7 +572,7 @@ export function convertMistralMessages(messages, names) {
}
// Make the last assistant message a prefill
const prefixEnabled = getConfigValue('mistral.enablePrefix', false);
const prefixEnabled = getConfigValue('mistral.enablePrefix', false, 'boolean');
const lastMsg = messages[messages.length - 1];
if (prefixEnabled && messages.length > 0 && lastMsg?.role === 'assistant') {
lastMsg.prefix = true;

View File

@ -132,7 +132,7 @@ export async function getPipeline(task, forceModel = '') {
const cacheDir = path.join(globalThis.DATA_ROOT, '_cache');
const model = forceModel || getModelForTask(task);
const localOnly = !getConfigValue('extensions.models.autoDownload', true);
const localOnly = !getConfigValue('extensions.models.autoDownload', true, 'boolean');
console.log('Initializing transformers.js pipeline for task', task, 'with model', model);
const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });
tasks[task].pipeline = instance;

View File

@ -21,9 +21,9 @@ import { getContentOfType } from './endpoints/content-manager.js';
export const KEY_PREFIX = 'user:';
const AVATAR_PREFIX = 'avatar:';
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false);
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean');
const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false, 'boolean');
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean');
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
/**

View File

@ -54,19 +54,33 @@ export function getConfig() {
}
}
/**
* Returns the value for the given key from the config object.
* @param {string} key - Key to get from the config object
* @param {any} defaultValue - Default value to return if the key is not found
* @param {'number'|'boolean'|null} typeConverter - Type to convert the value to
* @returns {any} Value for the given key
*/
export function getConfigValue(key, defaultValue = null) {
const envKey = keyToEnv(key);
if (envKey in process.env) {
return process.env[envKey];
export function getConfigValue(key, defaultValue = null, typeConverter = null) {
function _getValue() {
const envKey = keyToEnv(key);
if (envKey in process.env) {
return process.env[envKey];
}
const config = getConfig();
return _.get(config, key, defaultValue);
}
const value = _getValue();
switch (typeConverter) {
case 'number':
return Number(value);
case 'boolean':
return toBoolean(value);
default:
return value;
}
const config = getConfig();
return _.get(config, key, defaultValue);
}
/**
@ -392,7 +406,7 @@ export function generateTimestamp() {
* @param {number?} limit Maximum number of backups to keep. If null, the limit is determined by the `backups.common.numberOfBackups` config value.
*/
export function removeOldBackups(directory, prefix, limit = null) {
const MAX_BACKUPS = limit ?? Number(getConfigValue('backups.common.numberOfBackups', 50));
const MAX_BACKUPS = limit ?? Number(getConfigValue('backups.common.numberOfBackups', 50, 'number'));
let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix));
if (files.length > MAX_BACKUPS) {
@ -745,6 +759,27 @@ export async function canResolve(name, useIPv6 = true, useIPv4 = true) {
}
}
/**
* Converts various JavaScript primitives to boolean values.
* Handles special case for "true"/"false" strings (case-insensitive)
*
* @param {any} value - The value to convert to boolean
* @returns {boolean} - The boolean representation of the value
*/
export function toBoolean(value) {
// Handle string values case-insensitively
if (typeof value === 'string') {
// Trim and convert to lowercase for case-insensitive comparison
const trimmedLower = value.trim().toLowerCase();
// Handle explicit "true"/"false" strings
if (trimmedLower === 'true') return true;
if (trimmedLower === 'false') return false;
}
// Handle all other JavaScript values based on their "truthiness"
return Boolean(value);
}
/**
* converts string to boolean accepts 'true' or 'false' else it returns the string put in
@ -761,7 +796,7 @@ export function stringToBool(str) {
* Setup the minimum log level
*/
export function setupLogLevel() {
const logLevel = getConfigValue('minLogLevel', LOG_LEVELS.DEBUG);
const logLevel = getConfigValue('minLogLevel', LOG_LEVELS.DEBUG, 'number');
globalThis.console.debug = logLevel <= LOG_LEVELS.DEBUG ? console.debug : () => {};
globalThis.console.info = logLevel <= LOG_LEVELS.INFO ? console.info : () => {};