UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

115 lines • 5.34 kB
/** * CLI helper: parse --tool-routing-* flags into a ToolRoutingConfig. * * This module is intentionally side-effect-free so it can be unit-tested * without a running NeuroLink instance. */ import fs from "node:fs"; import { logger } from "../../utils/logger.js"; /** * Build a {@link ToolRoutingConfig} from the parsed CLI flags. * * Returns `undefined` when `--tool-routing` is absent or false, so callers * can skip the setter entirely with a simple truthiness check — behaviour is * identical to routing being disabled. * * `--tool-routing-servers` is parsed permissively: * - If the value looks like an existing file path, the file is read and JSON-parsed. * - Otherwise the value is treated as an inline JSON string. * - On any parse error a warning is logged and `servers` is omitted (fail open). */ export function buildToolRoutingConfigFromCli(flags) { if (!flags.toolRouting) { return undefined; } const config = { enabled: true }; if (flags.toolRoutingTimeout !== undefined) { const t = flags.toolRoutingTimeout; if (Number.isFinite(t) && t > 0) { config.timeoutMs = t; } else { logger.warn(`[tool-routing] --tool-routing-timeout value ${String(t)} is not a positive finite number; ignoring (SDK default applies).`); } } const hasRouterProvider = typeof flags.toolRoutingRouterProvider === "string"; const hasRouterModel = typeof flags.toolRoutingRouterModel === "string"; const hasRouterRegion = typeof flags.toolRoutingRouterRegion === "string"; if (hasRouterProvider || hasRouterModel || hasRouterRegion) { config.routerModel = { ...(hasRouterProvider && { provider: flags.toolRoutingRouterProvider, }), ...(hasRouterModel && { model: flags.toolRoutingRouterModel, }), ...(hasRouterRegion && { region: flags.toolRoutingRouterRegion, }), }; } if (Array.isArray(flags.toolRoutingAlwaysInclude) && flags.toolRoutingAlwaysInclude.length > 0) { config.alwaysIncludeServerIds = flags.toolRoutingAlwaysInclude; } if (typeof flags.toolRoutingServers === "string" && flags.toolRoutingServers.trim() !== "") { config.servers = parseServersFlag(flags.toolRoutingServers); } return config; } // Trust model: --tool-routing-servers is a local, user-supplied CLI flag. // The invoking user already holds the process's OS permissions, so absolute // and home-dir config paths are fully supported — there is no cross-trust- // boundary to enforce. The caps below are a robustness guard against // accidentally pointing at a huge file (e.g. a log or binary) that would // silently OOM the process or stall the CLI, not a security boundary. const MAX_SERVERS_INPUT_BYTES = 1_000_000; // 1 MB const MAX_SERVERS_ENTRIES = 1_000; /** * Parse the `--tool-routing-servers` value. * * Tries file-path first (if the string exists on disk), then inline JSON. * Logs a warning and returns `undefined` on any error (fail open). */ function parseServersFlag(value) { try { // File-path branch: resolve relative to cwd, check existence. const resolved = fs.existsSync(value) ? value : null; if (resolved !== null) { const { size } = fs.statSync(resolved); if (size > MAX_SERVERS_INPUT_BYTES) { logger.warn(`[tool-routing] --tool-routing-servers file exceeds ${MAX_SERVERS_INPUT_BYTES} bytes (got ${size}); ignoring to avoid excessive memory use.`); return undefined; } } else if (Buffer.byteLength(value, "utf8") > MAX_SERVERS_INPUT_BYTES) { logger.warn(`[tool-routing] --tool-routing-servers inline JSON exceeds ${MAX_SERVERS_INPUT_BYTES} bytes; ignoring to avoid excessive memory use.`); return undefined; } const jsonText = resolved ? fs.readFileSync(resolved, "utf8") : value; const parsed = JSON.parse(jsonText); if (!Array.isArray(parsed)) { logger.warn("[tool-routing] --tool-routing-servers must be a JSON array; ignoring."); return undefined; } // Validate shape loosely — only keep entries with id + description strings. const valid = parsed.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string" && typeof entry.description === "string"); if (valid.length !== parsed.length) { logger.warn(`[tool-routing] ${parsed.length - valid.length} server descriptor(s) skipped — each must have string "id" and "description" fields.`); } if (valid.length > MAX_SERVERS_ENTRIES) { logger.warn(`[tool-routing] --tool-routing-servers contains ${valid.length} entries; truncating to ${MAX_SERVERS_ENTRIES} to prevent pathological inputs.`); return valid.slice(0, MAX_SERVERS_ENTRIES); } return valid.length > 0 ? valid : undefined; } catch (err) { logger.warn(`[tool-routing] Failed to parse --tool-routing-servers: ${err.message}. Omitting servers (fail open).`); return undefined; } } //# sourceMappingURL=toolRoutingFlags.js.map