mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 04:00:46 +08:00
Sync dsh-netxops 0.1.41 with NetX async exec jobs and capability hints.
Add netx__getNeExecJob, route execManagedNe async to /exec-jobs, and document getManagedNe.capability for agents. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
4dd628b7cc
commit
30d4df23fa
14 changed files with 740 additions and 58 deletions
|
|
@ -18,6 +18,7 @@ var TOOLS_BY_GROUP = Object.freeze({
|
|||
"netx__listManagedNe",
|
||||
"netx__getManagedNe",
|
||||
"netx__execManagedNe",
|
||||
"netx__getNeExecJob",
|
||||
"netx__listCliTargets",
|
||||
"netx__findTopologyPaths"
|
||||
]),
|
||||
|
|
@ -1133,7 +1134,60 @@ function quoteNeId(neId) {
|
|||
}
|
||||
|
||||
// src/netx/handlers.ts
|
||||
var EXEC_MAX_COMMANDS = 5;
|
||||
var EXEC_MAX_COMMANDS_CAP = 50;
|
||||
var EXEC_MAX_COMMANDS_DEFAULT = 5;
|
||||
function execMaxCommands() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_MAX_COMMANDS ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : EXEC_MAX_COMMANDS_DEFAULT;
|
||||
return Math.max(1, Math.min(EXEC_MAX_COMMANDS_CAP, n));
|
||||
}
|
||||
function asyncMinNes() {
|
||||
const raw = Number.parseInt(String(process.env.NETX_NE_EXEC_ASYNC_MIN_NES ?? ""), 10);
|
||||
const n = Number.isFinite(raw) ? raw : 4;
|
||||
return Math.max(0, Math.min(50, n));
|
||||
}
|
||||
function truthyAsyncFlag(raw) {
|
||||
if (raw === undefined || raw === null)
|
||||
return;
|
||||
if (typeof raw === "boolean")
|
||||
return raw;
|
||||
const text = String(raw).trim().toLowerCase();
|
||||
if (!text)
|
||||
return;
|
||||
if (["1", "true", "yes", "on"].includes(text))
|
||||
return true;
|
||||
if (["0", "false", "no", "off"].includes(text))
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
function countExecNeTargets(args) {
|
||||
let n = 0;
|
||||
for (const key of ["ne_ids", "nms_ne_ids", "ume_ne_ids"]) {
|
||||
const val = strList(args, key);
|
||||
if (val.length > 0)
|
||||
n = Math.max(n, val.length);
|
||||
}
|
||||
const targets = args.targets;
|
||||
if (Array.isArray(targets)) {
|
||||
n = Math.max(n, targets.filter((t) => typeof t === "object" && t !== null && !Array.isArray(t)).length);
|
||||
}
|
||||
if (n === 0) {
|
||||
if (str2(args, "ne_id").trim() || nmsOrUme(args, "nms_ne_id", "ume_ne_id"))
|
||||
return 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
function shouldRunExecAsync(args) {
|
||||
const flag = truthyAsyncFlag(args.async);
|
||||
if (flag === false)
|
||||
return false;
|
||||
if (flag === true)
|
||||
return true;
|
||||
const minN = asyncMinNes();
|
||||
if (minN <= 0)
|
||||
return false;
|
||||
return countExecNeTargets(args) >= minN;
|
||||
}
|
||||
var UME_RAW_FIELD_PRESETS = {
|
||||
brief: [
|
||||
"alarm_alarm_key",
|
||||
|
|
@ -1375,6 +1429,8 @@ async function getManagedNe(client, args, signal) {
|
|||
return out;
|
||||
}
|
||||
async function execManagedNe(client, args, signal) {
|
||||
const maxCmds = execMaxCommands();
|
||||
const wantAsync = shouldRunExecAsync(args);
|
||||
const targetsRaw = args.targets;
|
||||
const neIds = strList(args, "ne_ids");
|
||||
const umeNeIds = nmsOrUmeList(args, "nms_ne_ids", "ume_ne_ids");
|
||||
|
|
@ -1408,7 +1464,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (umeNeIds.length > 0)
|
||||
body2.ume_ne_ids = umeNeIds;
|
||||
if (sharedCommands.length > 0) {
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
body2.commands = sharedCommands;
|
||||
|
|
@ -1417,6 +1473,13 @@ async function execManagedNe(client, args, signal) {
|
|||
const concurrency = num2(args, "concurrency");
|
||||
if (concurrency !== undefined)
|
||||
body2.concurrency = clampInt(concurrency, 4, 1, 8);
|
||||
if (wantAsync) {
|
||||
const out3 = await client.post("/v1/managed-ne/exec-jobs", body2, signal, 60000);
|
||||
if (out3.ok !== true)
|
||||
return out3;
|
||||
const data3 = asRecord(out3.data);
|
||||
return Object.keys(data3).length > 0 ? data3 : out3;
|
||||
}
|
||||
const out2 = await client.post("/v1/managed-ne/exec-batch", body2, signal, 600000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
|
|
@ -1439,7 +1502,7 @@ async function execManagedNe(client, args, signal) {
|
|||
if (sharedCommands.length === 0) {
|
||||
return { ok: false, error: "commands_required", error_code: "commands_required" };
|
||||
}
|
||||
if (sharedCommands.length > EXEC_MAX_COMMANDS) {
|
||||
if (sharedCommands.length > maxCmds) {
|
||||
return { ok: false, error: "too_many_commands", error_code: "too_many_commands" };
|
||||
}
|
||||
const body = {
|
||||
|
|
@ -1450,6 +1513,13 @@ async function execManagedNe(client, args, signal) {
|
|||
body.ne_id = neId;
|
||||
if (umeNeId)
|
||||
body.ume_ne_id = umeNeId;
|
||||
if (wantAsync) {
|
||||
const out2 = await client.post("/v1/managed-ne/exec-jobs", body, signal, 60000);
|
||||
if (out2.ok !== true)
|
||||
return out2;
|
||||
const data2 = asRecord(out2.data);
|
||||
return Object.keys(data2).length > 0 ? data2 : out2;
|
||||
}
|
||||
const out = await client.post("/v1/managed-ne/exec", body, signal, 300000);
|
||||
if (out.ok !== true)
|
||||
return out;
|
||||
|
|
@ -1459,6 +1529,18 @@ async function execManagedNe(client, args, signal) {
|
|||
}
|
||||
return { ok: true, data };
|
||||
}
|
||||
async function getNeExecJob(client, args, signal) {
|
||||
const jobId = str2(args, "job_id").trim();
|
||||
if (!jobId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "job_id_required",
|
||||
error_code: "job_id_required",
|
||||
hint: "Pass job_id from execManagedNe async ack."
|
||||
};
|
||||
}
|
||||
return client.get(`/v1/managed-ne/exec-jobs/${quoteNeId(jobId)}`, undefined, signal);
|
||||
}
|
||||
async function listCliTargets(client, args, signal) {
|
||||
const params = {
|
||||
page: clampInt(num2(args, "page"), 1, 1, Number.MAX_SAFE_INTEGER),
|
||||
|
|
@ -2614,12 +2696,12 @@ function registerNetxTools(ctx, connection, options) {
|
|||
page: num4(),
|
||||
page_size: num4()
|
||||
}, listManagedNe, getClient, t),
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Do NOT pass NMS inventory UUID here.", {
|
||||
tool2("netx__getManagedNe", "Get one managed NE by managed ne_id (from listManagedNe / listCliTargets source=managed). Response includes capability (device_family, exec_policy_effective, recommended_mode, hints) — read before complex execManagedNe. Do NOT pass NMS inventory UUID here.", {
|
||||
ne_id: str4("Managed NE id"),
|
||||
managed_ne_id: str4("Alias for ne_id"),
|
||||
id: str4("Alias for ne_id")
|
||||
}, getManagedNe, getClient, t),
|
||||
tool2("netx__execManagedNe", "Run read-only CLI via netx (show/display/ping/traceroute). Single NE: ne_id OR ume_ne_id + commands. Many NEs: ne_ids[]/ume_ne_ids[] + shared commands, or targets[{ume_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work.", {
|
||||
tool2("netx__execManagedNe", "Run CLI via netx (readonly show/display/ping/traceroute; linux_shell on Linux/MikroTik allows scripts). Single NE: ne_id OR nms_ne_id + commands. Many NEs: ne_ids[]/nms_ne_ids[] + shared commands, or targets[{nms_ne_id|ne_id, commands}]. Do NOT loop one-NE calls for multi-NE work. Long or multi-NE: async=true (or auto when ≥4 NEs) returns job_id — poll getNeExecJob. Read getManagedNe.capability first.", {
|
||||
ne_id: str4(),
|
||||
nms_ne_id: str4("NMS inventory id; alias ume_ne_id"),
|
||||
ume_ne_id: str4("Legacy alias of nms_ne_id"),
|
||||
|
|
@ -2628,7 +2710,7 @@ function registerNetxTools(ctx, connection, options) {
|
|||
ume_ne_ids: strArr("Legacy alias of nms_ne_ids"),
|
||||
targets: {
|
||||
type: "array",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR ume_ne_id) with commands[].",
|
||||
description: "Per-NE command sets: each item is one NE (ne_id OR nms_ne_id) with commands[].",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
|
|
@ -2643,8 +2725,11 @@ function registerNetxTools(ctx, connection, options) {
|
|||
commands: strArr("Commands for single NE, or shared commands for batch."),
|
||||
read_timeout_sec: num4("Per-command read timeout (default 60; use 90–120 for slow show)."),
|
||||
concurrency: num4("Parallel NEs for batch mode (1–8, default 4)."),
|
||||
async: bool4("oclaw-only async hint; ignored by native REST client.")
|
||||
async: bool4("true=background job_id + getNeExecJob; false=force sync; omit=auto for ~4+ NEs (NETX_NE_EXEC_ASYNC_MIN_NES).")
|
||||
}, execManagedNe, getClient, Math.max(t, 300000)),
|
||||
tool2("netx__getNeExecJob", "Poll a background execManagedNe job (job_id from async ack). When terminal=true, result holds exec/exec-batch payload. Do not busy-wait in the same turn.", {
|
||||
job_id: str4("Job id from execManagedNe async response.")
|
||||
}, getNeExecJob, getClient, Math.max(t, 60000)),
|
||||
tool2("netx__listCliTargets", "List CLI-capable targets (managed NE and/or NMS inventory). Call once per session with keyword/source, cache ids, then execManagedNe.", {
|
||||
source: str4("managed | nms | ume | all"),
|
||||
keyword: str4(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue