UNPKG

lynkr

Version:

Self-hosted LLM gateway and tier-routing proxy for Claude Code, Cursor, and Codex. Routes across Ollama, AWS Bedrock, OpenRouter, Databricks, Azure OpenAI, llama.cpp, and LM Studio with prompt caching, MCP tools, and 60-80% cost savings.

189 lines (179 loc) 5.44 kB
/** * Preflight Checks * * Runs user-supplied commands before invoking the model. If they all * exit 0, the work is already done — we skip the LLM call entirely * and return a synthetic "preflight_satisfied" response at zero cost. * * Typical use case: a fix-the-failing-test request that arrives after * the test already passes (CI lag, retry-after-fix, idempotent agent * retries). * * The request opts in by including a top-level `preflight_commands` * array on the Anthropic-format payload, e.g.: * * { * "model": "...", * "messages": [...], * "preflight_commands": ["pnpm test -- user-service"] * } * * Disabled by default — gated on LYNKR_PREFLIGHT_ENABLED=true. The * commands run with the same permissions as the Lynkr server, so * operators should only enable this on workspaces where that is OK. * * @module orchestrator/preflight */ const { spawnSync } = require('child_process'); const path = require('path'); const config = require('../config'); const logger = require('../logger'); const MAX_COMMANDS = 10; const MAX_OUTPUT_BYTES = 4000; /** * Extract the preflight command list from a request payload. * Accepts either `preflight_commands` (Lynkr-specific) or * `metadata.lynkr_preflight_commands` (for clients that strip unknown * top-level fields). * * @param {object} payload * @returns {string[]} */ function extractCommands(payload) { if (!payload) return []; const raw = payload.preflight_commands || payload.metadata?.lynkr_preflight_commands || []; if (!Array.isArray(raw)) return []; return raw .filter(cmd => typeof cmd === 'string' && cmd.trim().length > 0) .slice(0, MAX_COMMANDS); } /** * Resolve the workspace path for command execution. Falls back to * process.cwd() if no workspace is supplied (the caller should usually * pass one explicitly). * * @param {string|null|undefined} cwd * @returns {string|null} absolute path, or null if invalid */ function resolveCwd(cwd) { if (!cwd || typeof cwd !== 'string') return null; if (!path.isAbsolute(cwd)) return null; return cwd; } /** * Run a single command, returning a structured result. * * @param {string} command * @param {string} cwd * @param {number} timeoutMs * @returns {{ command: string, exit_code: number|null, stdout: string, stderr: string, timed_out: boolean }} */ function runCommand(command, cwd, timeoutMs) { const result = spawnSync(command, { cwd, shell: true, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024, }); return { command, exit_code: result.status, stdout: (result.stdout || '').slice(-MAX_OUTPUT_BYTES), stderr: (result.stderr || '').slice(-MAX_OUTPUT_BYTES), timed_out: result.signal === 'SIGTERM', }; } /** * Try the preflight pass. Returns null when preflight should be * skipped (disabled, no commands, missing cwd). Returns a result * object otherwise. * * @param {object} args * @param {object} args.payload - Anthropic-format request payload * @param {string} [args.cwd] - Workspace cwd (absolute path) * @returns {null | { * satisfied: boolean, * results: object[], * failedCommand: string|null, * reason: string, * }} */ function tryPreflight({ payload, cwd }) { if (!config.routing?.preflightEnabled) return null; const commands = extractCommands(payload); if (commands.length === 0) return null; const workspaceCwd = resolveCwd(cwd); if (!workspaceCwd) { logger.debug({ cwd }, '[Preflight] No valid cwd, skipping'); return null; } const timeoutMs = config.routing?.preflightTimeoutMs || 120000; const results = []; for (const command of commands) { const r = runCommand(command, workspaceCwd, timeoutMs); results.push(r); if (r.exit_code !== 0) { return { satisfied: false, results, failedCommand: command, reason: r.timed_out ? `Preflight command timed out: ${command}` : `Preflight command exited ${r.exit_code}: ${command}`, }; } } return { satisfied: true, results, failedCommand: null, reason: 'All preflight commands passed.', }; } /** * Build a synthetic "preflight satisfied" Anthropic Message response * that processMessage can return without hitting the model. * * @param {object} args * @param {string} args.model * @param {object} args.preflightResult * @returns {object} The full processMessage return value. */ function buildSatisfiedResponse({ model, preflightResult }) { const summary = `Preflight satisfied — work appears already complete (${preflightResult.results.length} command${preflightResult.results.length === 1 ? '' : 's'} passed).`; return { response: { json: { id: `msg_preflight_${Date.now()}`, type: 'message', role: 'assistant', content: [{ type: 'text', text: summary }], model, stop_reason: 'end_turn', stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 }, lynkr_preflight: { satisfied: true, reason: preflightResult.reason, results: preflightResult.results, }, }, ok: true, status: 200, }, steps: 0, durationMs: 0, terminationReason: 'preflight_satisfied', }; } module.exports = { tryPreflight, buildSatisfiedResponse, extractCommands, // Exposed for tests resolveCwd, };