🔃 refactor: Decouple Effects from AppService, move to data-schemas (#9974)

* chore: linting for `loadCustomConfig`

* refactor: decouple CDN init and variable/health checks from AppService

* refactor: move AppService to packages/data-schemas

* chore: update AppConfig import path to use data-schemas

* chore: update JsonSchemaType import path to use data-schemas

* refactor: update UserController to import webSearchKeys and redefine FunctionTool typedef

* chore: remove AppService.js

* refactor: update AppConfig interface to use Partial<TCustomConfig> and make paths and fileStrategies optional

* refactor: update checkConfig function to accept Partial<TCustomConfig>

* chore: fix types

* refactor: move handleRateLimits to startup checks as is an effect

* test: remove outdated rate limit tests from AppService.spec and add new handleRateLimits tests in checks.spec
This commit is contained in:
Danny Avila 2025-10-05 06:37:57 -04:00 committed by GitHub
parent 9ff608e6af
commit 838fb53208
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
73 changed files with 1383 additions and 1326 deletions

View file

@ -0,0 +1,54 @@
import { logger } from '@librechat/data-schemas';
import { DefaultAzureCredential } from '@azure/identity';
import type { ContainerClient, BlobServiceClient } from '@azure/storage-blob';
let blobServiceClient: BlobServiceClient | null = null;
let azureWarningLogged = false;
/**
* Initializes the Azure Blob Service client.
* This function establishes a connection by checking if a connection string is provided.
* If available, the connection string is used; otherwise, Managed Identity (via DefaultAzureCredential) is utilized.
* Note: Container creation (and its public access settings) is handled later in the CRUD functions.
* @returns The initialized client, or null if the required configuration is missing.
*/
export const initializeAzureBlobService = async (): Promise<BlobServiceClient | null> => {
if (blobServiceClient) {
return blobServiceClient;
}
const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
if (connectionString) {
const { BlobServiceClient } = await import('@azure/storage-blob');
blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
logger.info('Azure Blob Service initialized using connection string');
} else {
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
if (!accountName) {
if (!azureWarningLogged) {
logger.error(
'[initializeAzureBlobService] Azure Blob Service not initialized. Connection string missing and AZURE_STORAGE_ACCOUNT_NAME not provided.',
);
azureWarningLogged = true;
}
return null;
}
const url = `https://${accountName}.blob.core.windows.net`;
const credential = new DefaultAzureCredential();
const { BlobServiceClient } = await import('@azure/storage-blob');
blobServiceClient = new BlobServiceClient(url, credential);
logger.info('Azure Blob Service initialized using Managed Identity');
}
return blobServiceClient;
};
/**
* Retrieves the Azure ContainerClient for the given container name.
* @param [containerName=process.env.AZURE_CONTAINER_NAME || 'files'] - The container name.
* @returns The Azure ContainerClient.
*/
export const getAzureContainerClient = async (
containerName = process.env.AZURE_CONTAINER_NAME || 'files',
): Promise<ContainerClient | null> => {
const serviceClient = await initializeAzureBlobService();
return serviceClient ? serviceClient.getContainerClient(containerName) : null;
};

View file

@ -0,0 +1,42 @@
import firebase from 'firebase/app';
import { getStorage } from 'firebase/storage';
import { logger } from '@librechat/data-schemas';
import type { FirebaseStorage } from 'firebase/storage';
import type { FirebaseApp } from 'firebase/app';
let firebaseInitCount = 0;
let firebaseApp: FirebaseApp | null = null;
export const initializeFirebase = () => {
if (firebaseApp) {
return firebaseApp;
}
const firebaseConfig = {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.FIREBASE_APP_ID,
};
if (Object.values(firebaseConfig).some((value) => !value)) {
if (firebaseInitCount === 0) {
logger.info(
'[Optional] Firebase CDN not initialized. To enable, set FIREBASE_API_KEY, FIREBASE_AUTH_DOMAIN, FIREBASE_PROJECT_ID, FIREBASE_STORAGE_BUCKET, FIREBASE_MESSAGING_SENDER_ID, and FIREBASE_APP_ID environment variables.',
);
}
firebaseInitCount++;
return null;
}
firebaseApp = firebase.initializeApp(firebaseConfig);
logger.info('Firebase CDN initialized');
return firebaseApp;
};
export const getFirebaseStorage = (): FirebaseStorage | null => {
const app = initializeFirebase();
return app ? getStorage(app) : null;
};

View file

@ -0,0 +1,3 @@
export * from './azure';
export * from './firebase';
export * from './s3';

View file

@ -0,0 +1,51 @@
import { S3Client } from '@aws-sdk/client-s3';
import { logger } from '@librechat/data-schemas';
let s3: S3Client | null = null;
/**
* Initializes and returns an instance of the AWS S3 client.
*
* If AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are provided, they will be used.
* Otherwise, the AWS SDK's default credentials chain (including IRSA) is used.
*
* If AWS_ENDPOINT_URL is provided, it will be used as the endpoint.
*
* @returns An instance of S3Client if the region is provided; otherwise, null.
*/
export const initializeS3 = (): S3Client | null => {
if (s3) {
return s3;
}
const region = process.env.AWS_REGION;
if (!region) {
logger.error('[initializeS3] AWS_REGION is not set. Cannot initialize S3.');
return null;
}
// Read the custom endpoint if provided.
const endpoint = process.env.AWS_ENDPOINT_URL;
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
const config = {
region,
// Conditionally add the endpoint if it is provided
...(endpoint ? { endpoint } : {}),
};
if (accessKeyId && secretAccessKey) {
s3 = new S3Client({
...config,
credentials: { accessKeyId, secretAccessKey },
});
logger.info('[initializeS3] S3 initialized with provided credentials.');
} else {
// When using IRSA, credentials are automatically provided via the IAM Role attached to the ServiceAccount.
s3 = new S3Client(config);
logger.info('[initializeS3] S3 initialized using default credentials (IRSA).');
}
return s3;
};