refactor(nodes-cli): share node.invoke param builder

This commit is contained in:
Peter Steinberger
2026-02-16 02:03:15 +00:00
parent 966957fc66
commit d4bdcda324
4 changed files with 56 additions and 61 deletions

View File

@@ -1,6 +1,5 @@
import type { Command } from "commander";
import type { NodesRpcOpts } from "./types.js";
import { randomIdempotencyKey } from "../../gateway/call.js";
import { defaultRuntime } from "../../runtime.js";
import { renderTable } from "../../terminal/table.js";
import { shortenHomePath } from "../../utils.js";
@@ -14,7 +13,7 @@ import {
} from "../nodes-camera.js";
import { parseDurationMs } from "../parse-duration.js";
import { getNodesTheme, runNodesCommand } from "./cli-utils.js";
import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
import { buildNodeInvokeParams, callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
const parseFacing = (value: string): CameraFacing => {
const v = String(value ?? "")
@@ -37,12 +36,15 @@ export function registerNodesCameraCommands(nodes: Command) {
.action(async (opts: NodesRpcOpts) => {
await runNodesCommand("camera list", async () => {
const nodeId = await resolveNodeId(opts, String(opts.node ?? ""));
const raw = await callGatewayCli("node.invoke", opts, {
nodeId,
command: "camera.list",
params: {},
idempotencyKey: randomIdempotencyKey(),
});
const raw = await callGatewayCli(
"node.invoke",
opts,
buildNodeInvokeParams({
nodeId,
command: "camera.list",
params: {},
}),
);
const res = typeof raw === "object" && raw !== null ? (raw as { payload?: unknown }) : {};
const payload =
@@ -130,7 +132,7 @@ export function registerNodesCameraCommands(nodes: Command) {
}> = [];
for (const facing of facings) {
const invokeParams: Record<string, unknown> = {
const invokeParams = buildNodeInvokeParams({
nodeId,
command: "camera.snap",
params: {
@@ -141,11 +143,8 @@ export function registerNodesCameraCommands(nodes: Command) {
delayMs: Number.isFinite(delayMs) ? delayMs : undefined,
deviceId: deviceId || undefined,
},
idempotencyKey: randomIdempotencyKey(),
};
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
invokeParams.timeoutMs = timeoutMs;
}
timeoutMs,
});
const raw = await callGatewayCli("node.invoke", opts, invokeParams);
const res =
@@ -204,7 +203,7 @@ export function registerNodesCameraCommands(nodes: Command) {
: undefined;
const deviceId = opts.deviceId ? String(opts.deviceId).trim() : undefined;
const invokeParams: Record<string, unknown> = {
const invokeParams = buildNodeInvokeParams({
nodeId,
command: "camera.clip",
params: {
@@ -214,11 +213,8 @@ export function registerNodesCameraCommands(nodes: Command) {
format: "mp4",
deviceId: deviceId || undefined,
},
idempotencyKey: randomIdempotencyKey(),
};
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
invokeParams.timeoutMs = timeoutMs;
}
timeoutMs,
});
const raw = await callGatewayCli("node.invoke", opts, invokeParams);
const res = typeof raw === "object" && raw !== null ? (raw as { payload?: unknown }) : {};

View File

@@ -1,7 +1,6 @@
import type { Command } from "commander";
import fs from "node:fs/promises";
import type { NodesRpcOpts } from "./types.js";
import { randomIdempotencyKey } from "../../gateway/call.js";
import { defaultRuntime } from "../../runtime.js";
import { shortenHomePath } from "../../utils.js";
import { writeBase64ToFile } from "../nodes-camera.js";
@@ -9,21 +8,21 @@ import { canvasSnapshotTempPath, parseCanvasSnapshotPayload } from "../nodes-can
import { parseTimeoutMs } from "../nodes-run.js";
import { buildA2UITextJsonl, validateA2UIJsonl } from "./a2ui-jsonl.js";
import { getNodesTheme, runNodesCommand } from "./cli-utils.js";
import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
import { buildNodeInvokeParams, callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
async function invokeCanvas(opts: NodesRpcOpts, command: string, params?: Record<string, unknown>) {
const nodeId = await resolveNodeId(opts, String(opts.node ?? ""));
const invokeParams: Record<string, unknown> = {
nodeId,
command,
params,
idempotencyKey: randomIdempotencyKey(),
};
const timeoutMs = parseTimeoutMs(opts.invokeTimeout);
if (typeof timeoutMs === "number") {
invokeParams.timeoutMs = timeoutMs;
}
return await callGatewayCli("node.invoke", opts, invokeParams);
return await callGatewayCli(
"node.invoke",
opts,
buildNodeInvokeParams({
nodeId,
command,
params,
timeoutMs: typeof timeoutMs === "number" ? timeoutMs : undefined,
}),
);
}
export function registerNodesCanvasCommands(nodes: Command) {
@@ -42,7 +41,6 @@ export function registerNodesCanvasCommands(nodes: Command) {
.option("--invoke-timeout <ms>", "Node invoke timeout in ms (default 20000)", "20000")
.action(async (opts: NodesRpcOpts) => {
await runNodesCommand("canvas snapshot", async () => {
const nodeId = await resolveNodeId(opts, String(opts.node ?? ""));
const formatOpt = String(opts.format ?? "jpg")
.trim()
.toLowerCase();
@@ -54,25 +52,11 @@ export function registerNodesCanvasCommands(nodes: Command) {
const maxWidth = opts.maxWidth ? Number.parseInt(String(opts.maxWidth), 10) : undefined;
const quality = opts.quality ? Number.parseFloat(String(opts.quality)) : undefined;
const timeoutMs = opts.invokeTimeout
? Number.parseInt(String(opts.invokeTimeout), 10)
: undefined;
const invokeParams: Record<string, unknown> = {
nodeId,
command: "canvas.snapshot",
params: {
format: formatForParams,
maxWidth: Number.isFinite(maxWidth) ? maxWidth : undefined,
quality: Number.isFinite(quality) ? quality : undefined,
},
idempotencyKey: randomIdempotencyKey(),
};
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
invokeParams.timeoutMs = timeoutMs;
}
const raw = await callGatewayCli("node.invoke", opts, invokeParams);
const raw = await invokeCanvas(opts, "canvas.snapshot", {
format: formatForParams,
maxWidth: Number.isFinite(maxWidth) ? maxWidth : undefined,
quality: Number.isFinite(quality) ? quality : undefined,
});
const res = typeof raw === "object" && raw !== null ? (raw as { payload?: unknown }) : {};
const payload = parseCanvasSnapshotPayload(res.payload);
const filePath = canvasSnapshotTempPath({

View File

@@ -1,6 +1,5 @@
import type { Command } from "commander";
import type { NodesRpcOpts } from "./types.js";
import { randomIdempotencyKey } from "../../gateway/call.js";
import { defaultRuntime } from "../../runtime.js";
import { shortenHomePath } from "../../utils.js";
import {
@@ -10,7 +9,7 @@ import {
} from "../nodes-screen.js";
import { parseDurationMs } from "../parse-duration.js";
import { runNodesCommand } from "./cli-utils.js";
import { callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
import { buildNodeInvokeParams, callGatewayCli, nodesCallOpts, resolveNodeId } from "./rpc.js";
export function registerNodesScreenCommands(nodes: Command) {
const screen = nodes
@@ -38,7 +37,7 @@ export function registerNodesScreenCommands(nodes: Command) {
? Number.parseInt(String(opts.invokeTimeout), 10)
: undefined;
const invokeParams: Record<string, unknown> = {
const invokeParams = buildNodeInvokeParams({
nodeId,
command: "screen.record",
params: {
@@ -48,11 +47,8 @@ export function registerNodesScreenCommands(nodes: Command) {
format: "mp4",
includeAudio: opts.audio !== false,
},
idempotencyKey: randomIdempotencyKey(),
};
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
invokeParams.timeoutMs = timeoutMs;
}
timeoutMs,
});
const raw = await callGatewayCli("node.invoke", opts, invokeParams);
const res = typeof raw === "object" && raw !== null ? (raw as { payload?: unknown }) : {};

View File

@@ -1,6 +1,6 @@
import type { Command } from "commander";
import type { NodeListNode, NodesRpcOpts } from "./types.js";
import { callGateway } from "../../gateway/call.js";
import { callGateway, randomIdempotencyKey } from "../../gateway/call.js";
import { resolveNodeIdFromCandidates } from "../../shared/node-match.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
import { withProgress } from "../progress.js";
@@ -37,6 +37,25 @@ export const callGatewayCli = async (
}),
);
export function buildNodeInvokeParams(params: {
nodeId: string;
command: string;
params?: Record<string, unknown>;
timeoutMs?: number;
idempotencyKey?: string;
}): Record<string, unknown> {
const invokeParams: Record<string, unknown> = {
nodeId: params.nodeId,
command: params.command,
params: params.params,
idempotencyKey: params.idempotencyKey ?? randomIdempotencyKey(),
};
if (typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)) {
invokeParams.timeoutMs = params.timeoutMs;
}
return invokeParams;
}
export function unauthorizedHintForMessage(message: string): string | null {
const haystack = message.toLowerCase();
if (