@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
JavaScript
/**
* 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