evalstate's picture
download
raw
16.1 kB
import express, {} from 'express';
import cors from 'cors';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { settingsService } from '../shared/settings.js';
import { logger } from './utils/logger.js';
import { formatMetricsForAPI } from '../shared/transport-metrics.js';
import { ALL_BUILTIN_TOOL_IDS } from '@llmindset/hf-mcp';
import { CORS_ALLOWED_ORIGINS, CORS_EXPOSED_HEADERS } from '../shared/constants.js';
import { apiMetrics } from './utils/api-metrics.js';
import { gradioMetrics } from './utils/gradio-metrics.js';
import { formatCacheMetricsForAPI } from './utils/gradio-cache.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export class WebServer {
app;
server = null;
transportInfo = {
transport: 'unknown',
defaultHfTokenSet: false,
externalApiMode: false,
stdioClient: null,
};
localSharedToolStates = new Map();
transport;
apiClient;
constructor() {
this.app = express();
this.setupMiddleware();
}
setupMiddleware() {
this.app.disable('x-powered-by');
this.app.set('trust proxy', true);
this.app.use(express.json({ limit: '1mb' }));
this.app.use((_, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Referrer-Policy', 'no-referrer');
if (process.env.HSTS === 'true') {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
next();
});
const envOrigins = (process.env.CORS_ALLOWED_ORIGINS || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const normalize = (s) => s.replace(/\/+$/, '');
const envOriginsNorm = envOrigins.map(normalize);
const allowedOrigins = (envOriginsNorm.length > 0 ? envOriginsNorm : CORS_ALLOWED_ORIGINS).map(normalize);
let originSetting;
if (allowedOrigins.length === 1 && allowedOrigins[0] === '*') {
originSetting = '*';
}
else if (allowedOrigins.some((o) => o.includes('*'))) {
const exact = new Set(allowedOrigins.filter((o) => !o.includes('*')));
const patterns = allowedOrigins.filter((o) => o.includes('*'));
originSetting = (requestOrigin, cb) => {
if (!requestOrigin)
return cb(null, true);
const reqOrigin = normalize(requestOrigin);
if (exact.has(reqOrigin))
return cb(null, true);
try {
const u = new URL(requestOrigin);
for (const p of patterns) {
let scheme;
let hostPattern = p;
if (p.startsWith('http://') || p.startsWith('https://')) {
scheme = p.split('://', 1)[0];
hostPattern = p.slice((scheme + '://').length);
}
if (!hostPattern.startsWith('*.'))
continue;
const suffix = hostPattern.slice(2);
const host = u.hostname;
if (scheme && u.protocol !== scheme + ':')
continue;
if (host.endsWith('.' + suffix) && host !== suffix) {
return cb(null, true);
}
}
return cb(null, false);
}
catch {
return cb(null, false);
}
};
}
else {
originSetting = allowedOrigins;
}
const corsOptions = {
origin: originSetting,
exposedHeaders: CORS_EXPOSED_HEADERS,
};
this.app.use(cors(corsOptions));
this.app.options('*', cors(corsOptions));
}
getApp() {
return this.app;
}
setTransportInfo(info) {
this.transportInfo = info;
}
setClientInfo(clientInfo) {
this.transportInfo.stdioClient = clientInfo;
}
initializeToolStates() {
const currentSettings = settingsService.getSettings();
for (const toolId of ALL_BUILTIN_TOOL_IDS) {
const isEnabled = currentSettings.builtInTools.includes(toolId);
this.localSharedToolStates.set(toolId, isEnabled);
}
}
setTransport(transport) {
this.transport = transport;
}
setApiClient(apiClient) {
this.apiClient = apiClient;
}
getTransportInfo() {
return this.transportInfo;
}
async start(port) {
if (this.server) {
throw new Error('Server is already running');
}
return new Promise((resolve, reject) => {
this.server = this.app
.listen(port, () => {
this.transportInfo.port = port;
resolve();
})
.on('error', reject);
});
}
async stop() {
if (!this.server) {
return;
}
return new Promise((resolve, reject) => {
this.server?.close((err) => {
if (err) {
reject(err);
}
else {
this.server = null;
resolve();
}
});
});
}
async setupStaticFiles(isDevelopment) {
if (isDevelopment) {
try {
const { createServer: createViteServer } = await import('vite');
const rootDir = path.resolve(__dirname, '..', '..', '..', 'app', 'src', 'web');
const vite = await createViteServer({
configFile: path.resolve(__dirname, '..', '..', '..', 'app', 'vite.config.ts'),
server: {
middlewareMode: true,
hmr: true,
},
appType: 'spa',
root: rootDir,
});
this.app.use(vite.middlewares);
logger.info('Using Vite middleware in development mode with HMR enabled');
logger.info({ rootDir }, 'Vite root directory');
}
catch (err) {
logger.error({ err }, 'Error setting up Vite middleware');
throw err;
}
}
else {
const staticPath = path.join(__dirname, '..', 'web');
this.app.use(express.static(staticPath));
this.app.get('*', (req, res) => {
if (!req.path.startsWith('/api/')) {
res.sendFile(path.join(staticPath, 'index.html'));
}
});
}
}
setupApiRoutes() {
this.app.get('/api/transport', (_req, res) => {
res.json(this.transportInfo);
});
this.app.get('/api/sessions', (_req, res) => {
if (!this.transport) {
res.json([]);
return;
}
const sessions = this.transport.getSessions();
if (this.transportInfo.transport === 'stdio' && sessions.length > 0) {
const stdioSession = sessions[0];
if (stdioSession?.clientInfo && !this.transportInfo.stdioClient) {
this.transportInfo.stdioClient = {
name: stdioSession.clientInfo.name,
version: stdioSession.clientInfo.version,
};
}
}
res.json(sessions);
});
this.app.get('/api/transport-metrics', (req, res) => {
if (!this.transport) {
res.status(503).json({ error: 'Transport not initialized' });
return;
}
try {
const tempLogParam = req.query.templog;
let tempLogStatus = undefined;
if (tempLogParam && this.transportInfo.transport === 'streamableHttpJson') {
const statelessTransport = this.transport;
if (statelessTransport.activateTempLogging && statelessTransport.getTempLogStatus) {
const requestedCount = parseInt(tempLogParam, 10);
if (!isNaN(requestedCount) && requestedCount > 0) {
const activated = statelessTransport.activateTempLogging(requestedCount);
tempLogStatus = {
activated: true,
remaining: activated,
maxAllowed: statelessTransport.getTempLogStatus().maxAllowed,
};
}
}
}
const metrics = this.transport.getMetrics();
const isStateless = this.transportInfo.transport === 'streamableHttpJson';
const config = this.transport.getConfiguration();
const sessions = this.transport.getSessions().map((session) => {
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
const hasRecentActivity = session.lastActivity > fiveMinutesAgo;
const hasPingFailures = (session.pingFailures || 0) >= 1;
let connectionStatus;
if (!hasRecentActivity) {
connectionStatus = 'Disconnected';
}
else if (hasPingFailures) {
connectionStatus = 'Distressed';
}
else {
connectionStatus = 'Connected';
}
return {
id: session.id,
connectedAt: session.connectedAt.toISOString(),
lastActivity: session.lastActivity.toISOString(),
requestCount: session.requestCount,
clientInfo: session.clientInfo,
isConnected: hasRecentActivity,
connectionStatus,
pingFailures: session.pingFailures || 0,
lastPingAttempt: session.lastPingAttempt?.toISOString(),
ipAddress: session.ipAddress,
};
});
const formattedMetrics = formatMetricsForAPI(metrics, this.transportInfo.transport, isStateless, sessions);
if (!isStateless && config.staleCheckInterval && config.staleTimeout) {
formattedMetrics.configuration = {
heartbeatInterval: config.heartbeatInterval || 30000,
staleCheckInterval: config.staleCheckInterval,
staleTimeout: config.staleTimeout,
pingEnabled: config.pingEnabled,
pingInterval: config.pingInterval,
pingFailureThreshold: config.pingFailureThreshold || 1,
};
}
if (this.transportInfo.externalApiMode) {
formattedMetrics.apiMetrics = apiMetrics.getMetrics();
}
formattedMetrics.gradioMetrics = gradioMetrics.getMetrics();
formattedMetrics.gradioCacheMetrics = formatCacheMetricsForAPI();
const extendedMetrics = formattedMetrics;
if (tempLogStatus) {
extendedMetrics.tempLogStatus = tempLogStatus;
}
else if (this.transportInfo.transport === 'streamableHttpJson') {
const statelessTransport = this.transport;
if (statelessTransport.getTempLogStatus) {
const status = statelessTransport.getTempLogStatus();
if (status.enabled) {
extendedMetrics.tempLogStatus = status;
}
}
}
res.json(formattedMetrics);
}
catch (error) {
logger.error({ error }, 'Error retrieving transport metrics');
res.status(500).json({ error: 'Failed to retrieve transport metrics' });
}
});
this.app.get('/api/settings', (_req, res) => {
res.json(settingsService.getSettings());
});
this.app.post('/api/settings', express.json(), (req, res) => {
const { builtInTools, spaceTools } = req.body;
let updatedSettings = settingsService.getSettings();
if (builtInTools !== undefined) {
updatedSettings = settingsService.updateBuiltInTools(builtInTools);
}
if (spaceTools !== undefined) {
updatedSettings = settingsService.updateSpaceTools(spaceTools);
}
if (builtInTools !== undefined) {
for (const toolId of ALL_BUILTIN_TOOL_IDS) {
const shouldBeEnabled = builtInTools.includes(toolId);
const currentlyEnabled = this.localSharedToolStates.get(toolId) ?? false;
if (currentlyEnabled !== shouldBeEnabled) {
this.localSharedToolStates.set(toolId, shouldBeEnabled);
if (this.apiClient) {
this.apiClient.emit('toolStateChange', toolId, shouldBeEnabled);
}
logger.info(`Tool ${toolId} has been ${shouldBeEnabled ? 'enabled' : 'disabled'} via API`);
}
}
}
res.json(updatedSettings);
});
this.app.get('/api/gradio-endpoints', (_req, res) => {
if (!this.apiClient) {
res.json([]);
return;
}
res.json(this.apiClient.getGradioEndpoints());
});
this.app.post('/api/gradio-endpoints/:index', express.json(), (req, res) => {
const index = parseInt(req.params.index);
const { enabled } = req.body;
if (!this.apiClient) {
res.status(500).json({ error: 'API client not initialized' });
return;
}
const endpoints = this.apiClient.getGradioEndpoints();
if (index < 0 || index >= endpoints.length) {
res.status(404).json({ error: 'Endpoint not found' });
return;
}
this.apiClient.updateGradioEndpointState(index, enabled);
const endpoint = endpoints[index];
if (endpoint) {
const toolId = `gradio_${endpoint.subdomain}`;
this.apiClient.emit('toolStateChange', toolId, enabled);
}
const updatedEndpoint = endpoints[index];
res.json(updatedEndpoint);
});
this.app.put('/api/gradio-endpoints/:index', express.json(), (req, res) => {
const index = parseInt(req.params.index);
const { name, subdomain, id, emoji } = req.body;
if (!this.apiClient) {
res.status(500).json({ error: 'API client not initialized' });
return;
}
const endpoints = this.apiClient.getGradioEndpoints();
if (index < 0 || index >= endpoints.length) {
res.status(404).json({ error: 'Endpoint not found' });
return;
}
if (!name || !subdomain) {
res.status(400).json({ error: 'Name and subdomain are required' });
return;
}
const updatedEndpoint = { name, subdomain, id, emoji };
this.apiClient.updateGradioEndpoint(index, updatedEndpoint);
res.json(updatedEndpoint);
});
}
}
//# sourceMappingURL=web-server.js.map

Xet Storage Details

Size:
16.1 kB
·
Xet hash:
9b1717bc83d9e0e97602f6c9d703af117c9436f6df960d9582be302036faa670

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.