// ===== HANDLER ROUTER =====
// Routes tool calls to appropriate service handlers
import { SimpleAppDeveloperService } from '../services/app-developer-service-simple.js';
import { AutomationService } from '../services/automation-service.js';
import { QlikApiAdapter } from '../adapters/base-adapter.js';
import { ApiClient } from '../utils/api-client.js';
import { CacheManager } from '../utils/cache-manager.js';
import { logger } from '../utils/logger.js';
import { formatErrorForUser } from '../utils/errors.js';
import * as reloadHandlers from '../handlers/reload-handlers.js';
import * as catalogHandlers from '../handlers/catalog-handlers.js';
import * as governanceHandlers from '../handlers/governance-handlers.js';
import * as lineageHandlers from '../handlers/lineage-handlers.js';
import * as dataHandlers from '../handlers/data-handlers.js';
import * as miscHandlers from '../handlers/misc-handlers.js';
import * as answersHandlers from '../handlers/answers-handlers.js';
import * as alertsHandlers from '../handlers/alerts-handlers.js';
import * as automlHandlers from '../handlers/automl-handlers.js';
import * as searchHandlers from '../handlers/search-handlers.js';
/**
* Handler router that maps tool names to service methods
*/
export class HandlerRouter {
private appDeveloperService: SimpleAppDeveloperService;
private automationService: AutomationService;
private adapter: QlikApiAdapter;
private apiClient: ApiClient;
private cacheManager: CacheManager;
private tenantUrl: string;
private platform: 'cloud' | 'on-premise';
constructor(
appDeveloperService: SimpleAppDeveloperService,
automationService: AutomationService,
adapter: QlikApiAdapter,
apiClient: ApiClient,
cacheManager: CacheManager,
tenantUrl: string,
platform: 'cloud' | 'on-premise' = 'cloud'
) {
this.appDeveloperService = appDeveloperService;
this.automationService = automationService;
this.adapter = adapter;
this.apiClient = apiClient;
this.cacheManager = cacheManager;
this.tenantUrl = tenantUrl;
this.platform = platform;
}
/**
* Route a tool call to the appropriate handler
*/
async route(toolName: string, args: any): Promise<any> {
try {
switch (toolName) {
// ===== APP TOOLS =====
case 'qlik_generate_app':
return await this.handleGenerateApp(args);
// ===== AUTOMATION TOOLS =====
case 'qlik_automation_list':
return await this.handleAutomationList(args);
case 'qlik_automation_get_details':
return await this.handleAutomationGetDetails(args);
case 'qlik_automation_run':
return await this.handleAutomationRun(args);
case 'qlik_automation_list_runs':
return await this.handleAutomationListRuns(args);
// ===== RELOAD TOOLS =====
case 'qlik_trigger_app_reload':
return await this.handleReloadTool('trigger_app_reload', args);
case 'qlik_get_reload_status':
return await this.handleReloadTool('get_reload_status', args);
case 'qlik_cancel_reload':
return await this.handleReloadTool('cancel_reload', args);
// ===== UNIFIED SEARCH TOOL =====
case 'qlik_search':
return await searchHandlers.handleUnifiedSearch(
args,
this.apiClient,
this.cacheManager,
this.platform,
this.tenantUrl
);
// ===== CATALOG TOOLS =====
case 'qlik_get_spaces_catalog':
return await this.handleCatalogTool('get_spaces_catalog', args);
// ===== GOVERNANCE TOOLS =====
case 'qlik_get_tenant_info':
return await this.handleGovernanceTool('handleGetTenantInfo', args);
case 'qlik_get_user_info':
return await this.handleGovernanceTool('handleGetUserInfo', args);
case 'qlik_search_users':
return await this.handleGovernanceTool('handleSearchUsers', args);
case 'qlik_health_check':
return await this.handleGovernanceTool('handleHealthCheck', args);
case 'qlik_get_license_info':
return await this.handleGovernanceTool('handleGetLicenseInfo', args);
// ===== LINEAGE TOOLS (Cloud-only) =====
case 'qlik_get_lineage':
return await this.handleLineageTool('handleGetLineage', args);
// ===== DATA TOOLS =====
case 'qlik_get_dataset_details':
return await this.handleDataTool('handleGetDatasetDetails', args);
case 'qlik_apply_selections':
return await this.handleDataTool('handleApplySelections', args);
case 'qlik_clear_selections':
return await this.handleDataTool('handleClearSelections', args);
case 'qlik_get_current_selections':
return await this.handleDataTool('handleGetCurrentSelections', args);
case 'qlik_get_available_fields':
return await this.handleDataTool('handleGetAvailableFields', args);
// ===== MISC TOOLS =====
case 'qlik_insight_advisor':
return await this.handleMiscTool('handleInsightAdvisor', args);
case 'qlik_get_reload_info':
return await this.handleMiscTool('handleGetReloadInfo', args);
// ===== QLIK ANSWERS TOOLS =====
case 'qlik_answers_list_assistants':
return await this.handleAnswersTool('handleAnswersListAssistants', args);
case 'qlik_answers_get_assistant':
return await this.handleAnswersTool('handleAnswersGetAssistant', args);
case 'qlik_answers_ask_question':
return await this.handleAnswersTool('handleAnswersAskQuestion', args);
// ===== QLIK ALERTS TOOLS =====
case 'qlik_alert_list':
return await this.handleAlertsTool('handleAlertList', args);
case 'qlik_alert_get':
return await this.handleAlertsTool('handleAlertGet', args);
case 'qlik_alert_trigger':
return await this.handleAlertsTool('handleAlertTrigger', args);
case 'qlik_alert_delete':
return await this.handleAlertsTool('handleAlertDelete', args);
// ===== AUTOML TOOLS =====
case 'qlik_automl_get_experiments':
return await this.handleAutomlTool('handleAutoMLGetExperiments', args);
case 'qlik_automl_get_experiment':
return await this.handleAutomlTool('handleAutoMLGetExperiment', args);
case 'qlik_automl_list_deployments':
return await this.handleAutomlTool('handleAutoMLListDeployments', args);
case 'qlik_automl_get_deployment':
return await this.handleAutomlTool('handleAutoMLGetDeployment', args);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
} catch (error) {
return {
success: false,
error: formatErrorForUser(error),
};
}
}
// ===== RELOAD TOOL HANDLER =====
private async handleReloadTool(toolName: string, args: any): Promise<any> {
const handler = (reloadHandlers as any)[`handle${this.toCamelCase(toolName)}`];
if (!handler) {
throw new Error(`Reload handler not found: ${toolName}`);
}
// Pass platform and tenantUrl for on-premise support
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== GOVERNANCE TOOL HANDLER =====
private async handleGovernanceTool(handlerName: string, args: any): Promise<any> {
const handler = (governanceHandlers as any)[handlerName];
if (!handler) {
throw new Error(`Governance handler not found: ${handlerName}`);
}
// Pass platform and tenantUrl for on-premise support
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== LINEAGE TOOL HANDLER (Cloud-only) =====
private async handleLineageTool(handlerName: string, args: any): Promise<any> {
const handler = (lineageHandlers as any)[handlerName];
if (!handler) {
throw new Error(`Lineage handler not found: ${handlerName}`);
}
// Pass platform for Cloud-only check
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== CATALOG TOOL HANDLER =====
private async handleCatalogTool(toolName: string, args: any): Promise<any> {
const handler = (catalogHandlers as any)[`handle${this.toCamelCase(toolName)}`];
if (!handler) {
throw new Error(`Catalog handler not found: ${toolName}`);
}
// Pass platform and tenantUrl for on-premise support
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== DATA TOOL HANDLER =====
private async handleDataTool(handlerName: string, args: any): Promise<any> {
const handler = (dataHandlers as any)[handlerName];
if (!handler) {
throw new Error(`Data handler not found: ${handlerName}`);
}
// Pass platform and tenantUrl for platform-specific behavior
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== MISC TOOL HANDLER =====
private async handleMiscTool(handlerName: string, args: any): Promise<any> {
const handler = (miscHandlers as any)[handlerName];
if (!handler) {
throw new Error(`Misc handler not found: ${handlerName}`);
}
// Pass platform and tenantUrl for platform-specific behavior
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== ANSWERS TOOL HANDLER =====
private async handleAnswersTool(handlerName: string, args: any): Promise<any> {
const handler = (answersHandlers as any)[handlerName];
if (!handler) {
throw new Error(`Answers handler not found: ${handlerName}`);
}
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== ALERTS TOOL HANDLER =====
private async handleAlertsTool(handlerName: string, args: any): Promise<any> {
const handler = (alertsHandlers as any)[handlerName];
if (!handler) {
throw new Error(`Alerts handler not found: ${handlerName}`);
}
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== AUTOML TOOL HANDLER =====
private async handleAutomlTool(handlerName: string, args: any): Promise<any> {
const handler = (automlHandlers as any)[handlerName];
if (!handler) {
throw new Error(`AutoML handler not found: ${handlerName}`);
}
const result = await handler(this.apiClient, this.cacheManager, args, this.platform, this.tenantUrl);
return result;
}
// ===== GENERIC TOOL HANDLER =====
private async handleGenericTool(handlers: any, handlerName: string, args: any): Promise<any> {
const handler = handlers[handlerName];
if (!handler) {
throw new Error(`Handler not found: ${handlerName}`);
}
const result = await handler(this.apiClient, this.cacheManager, args);
return result;
}
// ===== UTILITY =====
private toCamelCase(str: string): string {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
.replace(/^([a-z])/, (_, letter) => letter.toUpperCase());
}
// ===== APP GENERATION HANDLERS =====
private async handleGenerateApp(args: any): Promise<any> {
const isUpdate = !!args.appId;
const isListConnections = !!args.listConnections;
const isListOdbcDsns = !!args.listOdbcDsns;
logger.info('Handling generate_app', {
mode: isUpdate ? 'update' : 'create',
hasLoadScript: !!args.loadScript,
listConnections: isListConnections,
listOdbcDsns: isListOdbcDsns,
});
// Pass all parameters including on-premise discovery features
const result = await this.appDeveloperService.createOrUpdateApp({
appName: args.appName,
appId: args.appId,
loadScript: args.loadScript,
dataConnection: args.dataConnection,
listConnections: args.listConnections,
listOdbcDsns: args.listOdbcDsns,
});
return result;
}
// ===== AUTOMATION HANDLERS =====
private async handleAutomationList(args: any): Promise<any> {
logger.info('Handling automation_list');
const automations = await this.automationService.listAutomations({
filter: args.filter,
sort: args.sort,
limit: args.limit,
fields: args.fields,
});
return {
success: true,
count: automations.length,
automations,
};
}
private async handleAutomationGetDetails(args: any): Promise<any> {
logger.info('Handling automation_get_details', {
automationId: args.automationId,
});
const automation = await this.automationService.getAutomationDetails(
args.automationId
);
return {
success: true,
automation,
};
}
private async handleAutomationListRuns(args: any): Promise<any> {
logger.info('Handling automation_list_runs', {
automationId: args.automationId,
});
const runs = await this.automationService.listAutomationRuns(
args.automationId,
{
filter: args.filter,
sort: args.sort,
limit: args.limit,
fields: args.fields,
}
);
return {
success: true,
count: runs.length,
runs,
};
}
private async handleAutomationRun(args: any): Promise<any> {
logger.info('Handling automation_run', {
automationId: args.automationId,
});
const run = await this.automationService.runAutomation(args.automationId);
return {
success: true,
run,
};
}
}