Files
prowler/ui/lib/lighthouse/workflow.ts
2025-12-18 12:45:42 +01:00

156 lines
4.1 KiB
TypeScript

import { createAgent } from "langchain";
import {
getProviderCredentials,
getTenantConfig,
} from "@/actions/lighthouse/lighthouse";
import { TOOLS_UNAVAILABLE_MESSAGE } from "@/lib/lighthouse/constants";
import type { ProviderType } from "@/lib/lighthouse/llm-factory";
import { createLLM } from "@/lib/lighthouse/llm-factory";
import {
getMCPTools,
initializeMCPClient,
isMCPAvailable,
} from "@/lib/lighthouse/mcp-client";
import {
generateUserDataSection,
LIGHTHOUSE_SYSTEM_PROMPT_TEMPLATE,
} from "@/lib/lighthouse/system-prompt";
import { describeTool, executeTool } from "@/lib/lighthouse/tools/meta-tool";
import { getModelParams } from "@/lib/lighthouse/utils";
export interface RuntimeConfig {
model?: string;
provider?: string;
businessContext?: string;
currentData?: string;
}
/**
* Truncate description to specified length
*/
function truncateDescription(desc: string | undefined, maxLen: number): string {
if (!desc) return "No description available";
const cleaned = desc.replace(/\n/g, " ").replace(/\s+/g, " ").trim();
if (cleaned.length <= maxLen) return cleaned;
return cleaned.substring(0, maxLen) + "...";
}
/**
* Tools that are blocked from being listed and executed by the LLM.
* These are destructive or sensitive operations that should only be
* performed through the UI with explicit user action.
*/
const BLOCKED_TOOLS = new Set([
"prowler_app_connect_provider",
"prowler_app_delete_provider",
"prowler_app_trigger_scan",
"prowler_app_schedule_daily_scan",
"prowler_app_update_scan",
"prowler_app_delete_mutelist",
"prowler_app_set_mutelist",
"prowler_app_create_mute_rule",
"prowler_app_update_mute_rule",
"prowler_app_delete_mute_rule",
]);
/**
* Check if a tool is blocked
*/
export function isBlockedTool(toolName: string): boolean {
return BLOCKED_TOOLS.has(toolName);
}
/**
* Generate dynamic tool listing from MCP tools
* Filters out blocked/destructive tools
*/
function generateToolListing(): string {
if (!isMCPAvailable()) {
return TOOLS_UNAVAILABLE_MESSAGE;
}
const mcpTools = getMCPTools();
if (mcpTools.length === 0) {
return TOOLS_UNAVAILABLE_MESSAGE;
}
// Filter out blocked tools
const safeTools = mcpTools.filter((tool) => !isBlockedTool(tool.name));
let listing = "\n## Available Prowler Tools\n\n";
listing += `${safeTools.length} tools loaded from Prowler MCP\n\n`;
for (const tool of safeTools) {
const desc = truncateDescription(tool.description, 150);
listing += `- **${tool.name}**: ${desc}\n`;
}
listing +=
"\nUse describe_tool with exact tool name to see full schema and parameters.\n";
return listing;
}
export async function initLighthouseWorkflow(runtimeConfig?: RuntimeConfig) {
await initializeMCPClient();
const toolListing = generateToolListing();
let systemPrompt = LIGHTHOUSE_SYSTEM_PROMPT_TEMPLATE.replace(
"{{TOOL_LISTING}}",
toolListing,
);
// Add user-provided data section if available
const userDataSection = generateUserDataSection(
runtimeConfig?.businessContext,
runtimeConfig?.currentData,
);
if (userDataSection) {
systemPrompt += userDataSection;
}
const tenantConfigResult = await getTenantConfig();
const tenantConfig = tenantConfigResult?.data?.attributes;
const defaultProvider = tenantConfig?.default_provider || "openai";
const defaultModels = tenantConfig?.default_models || {};
const defaultModel = defaultModels[defaultProvider] || "gpt-5.2";
const providerType = (runtimeConfig?.provider ||
defaultProvider) as ProviderType;
const modelId = runtimeConfig?.model || defaultModel;
// Get credentials
const providerConfig = await getProviderCredentials(providerType);
const { credentials, base_url: baseUrl } = providerConfig;
// Get model params
const modelParams = getModelParams({ model: modelId });
// Initialize LLM
const llm = createLLM({
provider: providerType,
model: modelId,
credentials,
baseUrl,
streaming: true,
tags: ["lighthouse-agent"],
modelParams,
});
const agent = createAgent({
model: llm,
tools: [describeTool, executeTool],
systemPrompt,
});
return agent;
}