UNPKG

openclaw-grafana-lens

Version:

OpenClaw plugin that gives AI agents full Grafana access — 18 composable tools for PromQL/LogQL/TraceQL queries, dashboard creation, alerting, SRE investigation, security monitoring, data collection pipeline management via Grafana Alloy (29 recipes), and

104 lines (103 loc) 4.53 kB
/** * Pipeline Recipe Types * * A recipe is a parameterized pipeline template that the agent matches * to user intent. Each recipe knows how to: * 1. Validate its required parameters * 2. Generate a complete, self-contained .alloy config * 3. Provide sample queries for the data it produces * 4. Map to an appropriate Grafana dashboard template * * The recipe system is the abstraction layer between the LLM (which * understands "monitor my Postgres") and Alloy (which needs specific * component configuration). The agent never needs to know 188+ Alloy * component types — it matches intent to ~18 recipe names. */ // ── Recipe Validation ─────────────────────────────────────────────── /** * Common synonyms the LLM might use instead of the actual param name. * Maps the synonym → candidate real param names (checked against recipe). */ const PARAM_SYNONYMS = { port: ["listenPort", "grpcPort", "httpPort", "listenAddress"], host: ["listenAddress", "url"], address: ["listenAddress", "url"], interval: ["scrapeInterval"], timeout: ["scrapeInterval"], user: ["basicAuth"], username: ["basicAuth"], token: ["bearerToken", "basicAuth"], auth: ["basicAuth", "bearerToken", "kafkaAuth"], }; /** * Validate and resolve recipe parameters. * Returns resolved params with defaults applied and warnings about * unrecognized parameter names. Throws on missing required params. */ export function resolveParams(recipe, rawParams) { const params = rawParams ?? {}; const resolved = {}; const warnings = []; // Check required params for (const p of recipe.requiredParams) { if (params[p.name] === undefined || params[p.name] === null || params[p.name] === "") { const example = p.example ? ` Example: ${p.example}` : ""; throw new Error(`Recipe '${recipe.name}' requires '${p.name}' parameter (${p.description}).${example}`); } resolved[p.name] = params[p.name]; } // Apply optional params with defaults for (const p of recipe.optionalParams) { resolved[p.name] = params[p.name] ?? p.default; } // Detect unknown params and generate suggestions const knownNames = new Set([ ...recipe.requiredParams.map((p) => p.name), ...recipe.optionalParams.map((p) => p.name), "jobName", // implicit param used by many recipes ]); for (const key of Object.keys(params)) { if (knownNames.has(key)) continue; // Check synonym map for suggestions const candidates = PARAM_SYNONYMS[key.toLowerCase()]; if (candidates) { const match = candidates.find((c) => knownNames.has(c)); if (match) { const def = [...recipe.requiredParams, ...recipe.optionalParams].find((p) => p.name === match); const defaultHint = def?.default !== undefined ? ` (default: '${def.default}')` : ""; warnings.push(`Unknown param '${key}'. Did you mean '${match}'${defaultHint}?`); continue; } } // No synonym match — list available optional params const available = recipe.optionalParams.map((p) => p.name); warnings.push(`Unknown param '${key}' for recipe '${recipe.name}'. Available optional params: ${available.join(", ") || "none"}.`); } return { params: resolved, warnings }; } /** * Generate env var name for a credential parameter. * Convention: ALLOY_{RECIPE_TYPE}_{PIPELINE_NAME}_{PARAM} */ export function credentialEnvVar(recipeName, pipelineName, paramName) { const prefix = recipeName.replace(/-/g, "_").toUpperCase(); const name = pipelineName.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase(); const param = paramName.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase(); return `ALLOY_${prefix}_${name}_${param}`; } /** * Generate credential references for a recipe's sensitive parameters. */ export function generateCredentialRefs(recipe, pipelineName, params) { return recipe.credentialParams .filter((name) => params[name] !== undefined) .map((name) => { const def = [...recipe.requiredParams, ...recipe.optionalParams].find((p) => p.name === name); return { envVar: credentialEnvVar(recipe.name, pipelineName, name), description: def?.description ?? name, example: def?.example, }; }); }