Refactor project structure and remove unused modules
- Removed performance monitoring, tools service, and polyfills modules - Moved formatToolCall helper function to test file - Simplified imports in index.ts by removing polyfills import - Cleaned up unnecessary files and consolidated code structure
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
export const formatToolCall = (obj: any, isError: boolean = false) => {
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(obj, null, 2), isError }],
|
||||
};
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
* @module index
|
||||
*/
|
||||
|
||||
import './polyfills.js';
|
||||
import express from 'express';
|
||||
import { APP_CONFIG } from './config/app.config.js';
|
||||
import { apiRoutes } from './routes/index.js';
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
import { performance } from 'perf_hooks';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
// Performance metrics types
|
||||
export interface Metric {
|
||||
name: string;
|
||||
value: number;
|
||||
timestamp: number;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface PerformanceThresholds {
|
||||
responseTime: number; // milliseconds
|
||||
memoryUsage: number; // bytes
|
||||
cpuUsage: number; // percentage
|
||||
}
|
||||
|
||||
// Performance monitoring class
|
||||
export class PerformanceMonitor extends EventEmitter {
|
||||
private metrics: Metric[] = [];
|
||||
private thresholds: PerformanceThresholds;
|
||||
private samplingInterval: number;
|
||||
private retentionPeriod: number;
|
||||
private intervalId?: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
thresholds: Partial<PerformanceThresholds> = {},
|
||||
samplingInterval = 5000, // 5 seconds
|
||||
retentionPeriod = 24 * 60 * 60 * 1000 // 24 hours
|
||||
) {
|
||||
super();
|
||||
this.thresholds = {
|
||||
responseTime: thresholds.responseTime || 1000, // 1 second
|
||||
memoryUsage: thresholds.memoryUsage || 1024 * 1024 * 1024, // 1 GB
|
||||
cpuUsage: thresholds.cpuUsage || 80 // 80%
|
||||
};
|
||||
this.samplingInterval = samplingInterval;
|
||||
this.retentionPeriod = retentionPeriod;
|
||||
}
|
||||
|
||||
// Start monitoring
|
||||
public start(): void {
|
||||
this.intervalId = setInterval(() => {
|
||||
this.collectMetrics();
|
||||
this.cleanOldMetrics();
|
||||
}, this.samplingInterval);
|
||||
}
|
||||
|
||||
// Stop monitoring
|
||||
public stop(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect system metrics
|
||||
private collectMetrics(): void {
|
||||
const now = Date.now();
|
||||
const memoryUsage = process.memoryUsage();
|
||||
const cpuUsage = process.cpuUsage();
|
||||
|
||||
// Memory metrics
|
||||
this.addMetric({
|
||||
name: 'memory.heapUsed',
|
||||
value: memoryUsage.heapUsed,
|
||||
timestamp: now
|
||||
});
|
||||
|
||||
this.addMetric({
|
||||
name: 'memory.heapTotal',
|
||||
value: memoryUsage.heapTotal,
|
||||
timestamp: now
|
||||
});
|
||||
|
||||
this.addMetric({
|
||||
name: 'memory.rss',
|
||||
value: memoryUsage.rss,
|
||||
timestamp: now
|
||||
});
|
||||
|
||||
// CPU metrics
|
||||
this.addMetric({
|
||||
name: 'cpu.user',
|
||||
value: cpuUsage.user,
|
||||
timestamp: now
|
||||
});
|
||||
|
||||
this.addMetric({
|
||||
name: 'cpu.system',
|
||||
value: cpuUsage.system,
|
||||
timestamp: now
|
||||
});
|
||||
|
||||
// Check thresholds
|
||||
this.checkThresholds();
|
||||
}
|
||||
|
||||
// Add a metric
|
||||
public addMetric(metric: Metric): void {
|
||||
this.metrics.push(metric);
|
||||
this.emit('metric', metric);
|
||||
}
|
||||
|
||||
// Clean old metrics
|
||||
private cleanOldMetrics(): void {
|
||||
const cutoff = Date.now() - this.retentionPeriod;
|
||||
this.metrics = this.metrics.filter(metric => metric.timestamp > cutoff);
|
||||
}
|
||||
|
||||
// Check if metrics exceed thresholds
|
||||
private checkThresholds(): void {
|
||||
const memoryUsage = process.memoryUsage().heapUsed;
|
||||
if (memoryUsage > this.thresholds.memoryUsage) {
|
||||
this.emit('threshold_exceeded', {
|
||||
type: 'memory',
|
||||
value: memoryUsage,
|
||||
threshold: this.thresholds.memoryUsage
|
||||
});
|
||||
}
|
||||
|
||||
const cpuUsage = process.cpuUsage();
|
||||
const totalCPU = cpuUsage.user + cpuUsage.system;
|
||||
const cpuPercentage = (totalCPU / (process.uptime() * 1000000)) * 100;
|
||||
if (cpuPercentage > this.thresholds.cpuUsage) {
|
||||
this.emit('threshold_exceeded', {
|
||||
type: 'cpu',
|
||||
value: cpuPercentage,
|
||||
threshold: this.thresholds.cpuUsage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get metrics for a specific time range
|
||||
public getMetrics(
|
||||
startTime: number,
|
||||
endTime: number = Date.now(),
|
||||
metricName?: string
|
||||
): Metric[] {
|
||||
return this.metrics.filter(metric =>
|
||||
metric.timestamp >= startTime &&
|
||||
metric.timestamp <= endTime &&
|
||||
(!metricName || metric.name === metricName)
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate average for a metric
|
||||
public calculateAverage(
|
||||
metricName: string,
|
||||
startTime: number,
|
||||
endTime: number = Date.now()
|
||||
): number {
|
||||
const metrics = this.getMetrics(startTime, endTime, metricName);
|
||||
if (metrics.length === 0) return 0;
|
||||
return metrics.reduce((sum, metric) => sum + metric.value, 0) / metrics.length;
|
||||
}
|
||||
}
|
||||
|
||||
// Performance optimization utilities
|
||||
export class PerformanceOptimizer {
|
||||
private static readonly GC_THRESHOLD = 0.9; // 90% heap usage
|
||||
|
||||
// Optimize memory usage
|
||||
public static async optimizeMemory(): Promise<void> {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
const heapUsageRatio = memoryUsage.heapUsed / memoryUsage.heapTotal;
|
||||
|
||||
if (heapUsageRatio > this.GC_THRESHOLD) {
|
||||
if (global.gc) {
|
||||
global.gc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Batch processing utility
|
||||
public static async processBatch<T, R>(
|
||||
items: T[],
|
||||
batchSize: number,
|
||||
processor: (batch: T[]) => Promise<R[]>
|
||||
): Promise<R[]> {
|
||||
const results: R[] = [];
|
||||
for (let i = 0; i < items.length; i += batchSize) {
|
||||
const batch = items.slice(i, i + batchSize);
|
||||
const batchResults = await processor(batch);
|
||||
results.push(...batchResults);
|
||||
await new Promise(resolve => setTimeout(resolve, 0)); // Yield to event loop
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// Debounce utility
|
||||
public static debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return (...args: Parameters<T>) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Throttle utility
|
||||
public static throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
limit: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let inThrottle = false;
|
||||
return (...args: Parameters<T>) => {
|
||||
if (!inThrottle) {
|
||||
func(...args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export performance monitoring instance
|
||||
export const performanceMonitor = new PerformanceMonitor();
|
||||
|
||||
// Start monitoring on module load
|
||||
performanceMonitor.start();
|
||||
@@ -1,24 +0,0 @@
|
||||
// Extend global Array interface to include toSorted and toReversed methods
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
toSorted(compareFn?: (a: T, b: T) => number): T[];
|
||||
toReversed(): T[];
|
||||
}
|
||||
}
|
||||
|
||||
// Polyfill for toSorted method
|
||||
if (typeof Array.prototype.toSorted !== 'function') {
|
||||
Array.prototype.toSorted = function <T>(compareFn?: (a: T, b: T) => number): T[] {
|
||||
return [...this].sort(compareFn);
|
||||
};
|
||||
}
|
||||
|
||||
// Polyfill for toReversed method
|
||||
if (typeof Array.prototype.toReversed !== 'function') {
|
||||
Array.prototype.toReversed = function <T>(): T[] {
|
||||
return [...this].reverse();
|
||||
};
|
||||
}
|
||||
|
||||
// Export an empty object to make this a module
|
||||
export { };
|
||||
@@ -1,100 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
import { Tool, HassEntity } from '../interfaces/index.js';
|
||||
import { get_hass } from '../hass/index.js';
|
||||
import { DomainSchema } from '../schemas.js';
|
||||
|
||||
// Define tools array
|
||||
export const tools: Tool[] = [
|
||||
{
|
||||
name: 'list_devices',
|
||||
description: 'List all devices connected to Home Assistant',
|
||||
parameters: z.object({
|
||||
domain: DomainSchema.optional(),
|
||||
area: z.string().optional(),
|
||||
floor: z.string().optional()
|
||||
}),
|
||||
execute: async (params) => {
|
||||
const hass = await get_hass();
|
||||
const states = await hass.states.get();
|
||||
|
||||
// Filter by domain if specified
|
||||
let filteredStates = states;
|
||||
if (params.domain) {
|
||||
filteredStates = states.filter((state: HassEntity) => state.entity_id.startsWith(`${params.domain}.`));
|
||||
}
|
||||
|
||||
// Filter by area if specified
|
||||
if (params.area) {
|
||||
filteredStates = filteredStates.filter((state: HassEntity) =>
|
||||
state.attributes.area_id === params.area ||
|
||||
state.attributes.area === params.area
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by floor if specified
|
||||
if (params.floor) {
|
||||
filteredStates = filteredStates.filter((state: HassEntity) =>
|
||||
state.attributes.floor === params.floor
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
devices: filteredStates.map((state: HassEntity) => ({
|
||||
entity_id: state.entity_id,
|
||||
state: state.state,
|
||||
attributes: state.attributes
|
||||
}))
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'control',
|
||||
description: 'Control a Home Assistant device',
|
||||
parameters: z.object({
|
||||
command: z.string(),
|
||||
entity_id: z.string(),
|
||||
state: z.string().optional(),
|
||||
brightness: z.number().min(0).max(255).optional(),
|
||||
color_temp: z.number().optional(),
|
||||
rgb_color: z.tuple([z.number(), z.number(), z.number()]).optional(),
|
||||
position: z.number().min(0).max(100).optional(),
|
||||
tilt_position: z.number().min(0).max(100).optional(),
|
||||
temperature: z.number().optional(),
|
||||
target_temp_high: z.number().optional(),
|
||||
target_temp_low: z.number().optional(),
|
||||
hvac_mode: z.string().optional(),
|
||||
fan_mode: z.string().optional(),
|
||||
humidity: z.number().min(0).max(100).optional()
|
||||
}),
|
||||
execute: async (params) => {
|
||||
const hass = await get_hass();
|
||||
const domain = params.entity_id.split('.')[0];
|
||||
|
||||
const serviceData: Record<string, any> = {
|
||||
entity_id: params.entity_id
|
||||
};
|
||||
|
||||
// Add optional parameters if they exist
|
||||
if (params.state) serviceData.state = params.state;
|
||||
if (params.brightness) serviceData.brightness = params.brightness;
|
||||
if (params.color_temp) serviceData.color_temp = params.color_temp;
|
||||
if (params.rgb_color) serviceData.rgb_color = params.rgb_color;
|
||||
if (params.position) serviceData.position = params.position;
|
||||
if (params.tilt_position) serviceData.tilt_position = params.tilt_position;
|
||||
if (params.temperature) serviceData.temperature = params.temperature;
|
||||
if (params.target_temp_high) serviceData.target_temp_high = params.target_temp_high;
|
||||
if (params.target_temp_low) serviceData.target_temp_low = params.target_temp_low;
|
||||
if (params.hvac_mode) serviceData.hvac_mode = params.hvac_mode;
|
||||
if (params.fan_mode) serviceData.fan_mode = params.fan_mode;
|
||||
if (params.humidity) serviceData.humidity = params.humidity;
|
||||
|
||||
await hass.services.call(domain, params.command, serviceData);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Command '${params.command}' executed on ${params.entity_id}`
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -1,17 +1,15 @@
|
||||
import { z } from 'zod';
|
||||
import { get_hass } from '../hass/index.js';
|
||||
import { Tool } from '../types/index.js';
|
||||
import { listDevicesTool } from './list-devices.tool.js';
|
||||
import { controlTool } from './control.tool.js';
|
||||
import { historyTool } from './history.tool.js';
|
||||
import { sceneTool } from './scene.tool.js';
|
||||
import { notifyTool } from './notify.tool.js';
|
||||
import { automationTool } from './automation.tool.js';
|
||||
import { addonTool } from './addon.tool.js';
|
||||
import { packageTool } from './package.tool.js';
|
||||
import { automationConfigTool } from './automation-config.tool.js';
|
||||
import { subscribeEventsTool } from './subscribe-events.tool.js';
|
||||
import { getSSEStatsTool } from './sse-stats.tool.js';
|
||||
import { Tool } from '../types/index';
|
||||
import { listDevicesTool } from './list-devices.tool';
|
||||
import { controlTool } from './control.tool';
|
||||
import { historyTool } from './history.tool';
|
||||
import { sceneTool } from './scene.tool';
|
||||
import { notifyTool } from './notify.tool';
|
||||
import { automationTool } from './automation.tool';
|
||||
import { addonTool } from './addon.tool';
|
||||
import { packageTool } from './package.tool';
|
||||
import { automationConfigTool } from './automation-config.tool';
|
||||
import { subscribeEventsTool } from './subscribe-events.tool';
|
||||
import { getSSEStatsTool } from './sse-stats.tool';
|
||||
|
||||
// Tool category types
|
||||
export enum ToolCategory {
|
||||
@@ -27,16 +25,6 @@ export enum ToolPriority {
|
||||
LOW = 'low'
|
||||
}
|
||||
|
||||
interface ToolParameters {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Tool<Params extends ToolParameters = ToolParameters> {
|
||||
name: string;
|
||||
description: string;
|
||||
execute(params: Params): Promise<any>;
|
||||
}
|
||||
|
||||
interface ToolMetadata {
|
||||
category: ToolCategory;
|
||||
platform: string;
|
||||
@@ -47,161 +35,6 @@ interface ToolMetadata {
|
||||
};
|
||||
}
|
||||
|
||||
// Enhanced tool interface
|
||||
export interface EnhancedTool extends Tool {
|
||||
metadata: ToolMetadata;
|
||||
validate?: (params: any) => Promise<boolean>;
|
||||
preExecute?: (params: any) => Promise<void>;
|
||||
postExecute?: (result: any) => Promise<void>;
|
||||
}
|
||||
|
||||
// Tool registry for managing and organizing tools
|
||||
export class ToolRegistry {
|
||||
private tools: Map<string, EnhancedTool> = new Map();
|
||||
private categories: Map<ToolCategory, Set<string>> = new Map();
|
||||
private cache: Map<string, { data: any; timestamp: number }> = new Map();
|
||||
|
||||
constructor() {
|
||||
// Initialize categories
|
||||
Object.values(ToolCategory).forEach(category => {
|
||||
this.categories.set(category, new Set());
|
||||
});
|
||||
}
|
||||
|
||||
// Register a new tool
|
||||
public registerTool(tool: EnhancedTool): void {
|
||||
this.tools.set(tool.name, tool);
|
||||
this.categories.get(tool.metadata.category)?.add(tool.name);
|
||||
}
|
||||
|
||||
// Get tool by name
|
||||
public getTool(name: string): EnhancedTool | undefined {
|
||||
return this.tools.get(name);
|
||||
}
|
||||
|
||||
// Get all tools in a category
|
||||
public getToolsByCategory(category: ToolCategory): EnhancedTool[] {
|
||||
const toolNames = this.categories.get(category);
|
||||
if (!toolNames) return [];
|
||||
return Array.from(toolNames).map(name => this.tools.get(name)!);
|
||||
}
|
||||
|
||||
// Execute a tool with validation and hooks
|
||||
public async executeTool(name: string, params: any): Promise<any> {
|
||||
const tool = this.tools.get(name);
|
||||
if (!tool) {
|
||||
throw new Error(`Tool ${name} not found`);
|
||||
}
|
||||
|
||||
// Check cache if enabled
|
||||
if (tool.metadata.caching?.enabled) {
|
||||
const cacheKey = `${name}:${JSON.stringify(params)}`;
|
||||
const cached = this.cache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < tool.metadata.caching.ttl) {
|
||||
return cached.data;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if (tool.validate) {
|
||||
const isValid = await tool.validate(params);
|
||||
if (!isValid) {
|
||||
throw new Error('Invalid parameters');
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-execution hook
|
||||
if (tool.preExecute) {
|
||||
await tool.preExecute(params);
|
||||
}
|
||||
|
||||
// Execute tool
|
||||
const result = await tool.execute(params);
|
||||
|
||||
// Post-execution hook
|
||||
if (tool.postExecute) {
|
||||
await tool.postExecute(result);
|
||||
}
|
||||
|
||||
// Update cache if enabled
|
||||
if (tool.metadata.caching?.enabled) {
|
||||
const cacheKey = `${name}:${JSON.stringify(params)}`;
|
||||
this.cache.set(cacheKey, {
|
||||
data: result,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Clean up expired cache entries
|
||||
public cleanCache(): void {
|
||||
const now = Date.now();
|
||||
for (const [key, value] of this.cache.entries()) {
|
||||
const tool = this.tools.get(key.split(':')[0]);
|
||||
if (tool?.metadata.caching?.ttl && now - value.timestamp > tool.metadata.caching.ttl) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the global tool registry
|
||||
export const toolRegistry = new ToolRegistry();
|
||||
|
||||
// Tool decorator for easy registration
|
||||
function registerTool(metadata: Partial<ToolMetadata>) {
|
||||
return function (constructor: any) {
|
||||
return constructor;
|
||||
};
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
@registerTool({
|
||||
category: ToolCategory.DEVICE,
|
||||
platform: 'hass',
|
||||
version: '1.0.0',
|
||||
caching: {
|
||||
enabled: true,
|
||||
ttl: 60000
|
||||
}
|
||||
})
|
||||
export class LightControlTool implements EnhancedTool {
|
||||
name = 'light_control';
|
||||
description = 'Control light devices';
|
||||
metadata: ToolMetadata = {
|
||||
category: ToolCategory.DEVICE,
|
||||
platform: 'hass',
|
||||
version: '1.0.0',
|
||||
caching: {
|
||||
enabled: true,
|
||||
ttl: 60000
|
||||
}
|
||||
};
|
||||
parameters = z.object({
|
||||
command: z.enum(['turn_on', 'turn_off', 'toggle']),
|
||||
entity_id: z.string(),
|
||||
brightness: z.number().min(0).max(255).optional(),
|
||||
color_temp: z.number().optional(),
|
||||
rgb_color: z.tuple([z.number(), z.number(), z.number()]).optional()
|
||||
});
|
||||
|
||||
async validate(params: any): Promise<boolean> {
|
||||
try {
|
||||
this.parameters.parse(params);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async execute(params: any): Promise<any> {
|
||||
// Implementation here
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
// Array to track all tools
|
||||
const tools: Tool[] = [
|
||||
listDevicesTool,
|
||||
|
||||
Reference in New Issue
Block a user