UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

109 lines (108 loc) 5.59 kB
/** * Research configuration validation and resolution. */ import { existsSync, statSync } from "node:fs"; import path from "node:path"; import { logger } from "../utils/logger.js"; import { AutoresearchError } from "./errors.js"; import { AUTORESEARCH_DEFAULTS } from "../types/index.js"; export function resolveConfig(partial) { const config = { repoPath: path.resolve(partial.repoPath), programPath: partial.programPath ?? AUTORESEARCH_DEFAULTS.programPath, mutablePaths: partial.mutablePaths, immutablePaths: partial.immutablePaths ?? [], resultsPath: partial.resultsPath ?? AUTORESEARCH_DEFAULTS.resultsPath, statePath: partial.statePath ?? AUTORESEARCH_DEFAULTS.statePath, runCommand: partial.runCommand, logPath: partial.logPath ?? AUTORESEARCH_DEFAULTS.logPath, metric: partial.metric, memoryMetric: partial.memoryMetric, timeoutMs: partial.timeoutMs ?? AUTORESEARCH_DEFAULTS.timeoutMs, branchPrefix: partial.branchPrefix ?? AUTORESEARCH_DEFAULTS.branchPrefix, provider: partial.provider, model: partial.model, maxExperiments: partial.maxExperiments, thinkingLevel: partial.thinkingLevel ?? AUTORESEARCH_DEFAULTS.thinkingLevel, }; return config; } export function validateConfig(config) { // Verify repoPath exists and is a directory if (!existsSync(config.repoPath) || !statSync(config.repoPath).isDirectory()) { throw AutoresearchError.create("CONFIG_INVALID", `repoPath does not exist or is not a directory: ${config.repoPath}`); } // Verify it's a git repo if (!existsSync(path.join(config.repoPath, ".git"))) { throw AutoresearchError.create("REPO_NOT_FOUND", `repoPath is not a git repository: ${config.repoPath}`); } // Verify mutablePaths is non-empty if (config.mutablePaths.length === 0) { throw AutoresearchError.create("CONFIG_INVALID", "mutablePaths must contain at least one path"); } // Verify mutable and immutable don't overlap (normalize paths first) const normMutable = config.mutablePaths.map((p) => path.normalize(p).replace(/\/+$/, "")); const normImmutable = config.immutablePaths.map((p) => path.normalize(p).replace(/\/+$/, "")); const overlap = normMutable.filter((p) => normImmutable.includes(p)); if (overlap.length > 0) { throw AutoresearchError.create("CONFIG_INVALID", `mutablePaths and immutablePaths overlap: ${overlap.join(", ")}`); } // Verify metric pattern — length limit and basic ReDoS safety const MAX_PATTERN_LENGTH = 200; if (config.metric.pattern.length > MAX_PATTERN_LENGTH) { throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern exceeds ${MAX_PATTERN_LENGTH} characters: ${config.metric.pattern.slice(0, 50)}...`); } // Reject patterns with obvious catastrophic backtracking (nested quantifiers) if (/(\+|\*|\{)\s*(\+|\*|\{)/.test(config.metric.pattern) || /\(\?[^)]*\)\+\+/.test(config.metric.pattern)) { throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern contains nested quantifiers (potential ReDoS): ${config.metric.pattern}`); } // Verify metric pattern is valid regex with exactly one capture group try { const _regex = new RegExp(config.metric.pattern); const match = new RegExp(config.metric.pattern + "|").exec(""); const groupCount = (match?.length ?? 1) - 1; if (groupCount !== 1) { throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern must have exactly one capture group, found ${groupCount}: ${config.metric.pattern}`); } } catch (error) { if (error instanceof Error && "code" in error && typeof error.code === "string") { throw error; // Rethrow structured AutoresearchError } throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern is not a valid regex: ${config.metric.pattern}`); } // Validate memoryMetric.pattern with the same checks if present if (config.memoryMetric) { if (config.memoryMetric.pattern.length > MAX_PATTERN_LENGTH) { throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern exceeds ${MAX_PATTERN_LENGTH} characters`); } if (/(\+|\*|\{)\s*(\+|\*|\{)/.test(config.memoryMetric.pattern) || /\(\?[^)]*\)\+\+/.test(config.memoryMetric.pattern)) { throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern contains nested quantifiers (potential ReDoS)`); } try { const _memRegex = new RegExp(config.memoryMetric.pattern); const memMatch = new RegExp(config.memoryMetric.pattern + "|").exec(""); const memGroupCount = (memMatch?.length ?? 1) - 1; if (memGroupCount !== 1) { throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern must have exactly one capture group, found ${memGroupCount}`); } } catch (error) { if (error instanceof Error && "code" in error && typeof error.code === "string") { throw error; // Rethrow structured AutoresearchError } throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern is not a valid regex: ${config.memoryMetric.pattern}`); } } logger.info("[Autoresearch] Config validated", { repoPath: config.repoPath, mutablePaths: config.mutablePaths, }); }