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
JavaScript
/**
* 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,
};
});
}