mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 10:42:43 +00:00
refactor(nodes): dedupe camera payload and node resolve helpers
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
readFileUtf8AndCleanup,
|
||||
stubFetchTextResponse,
|
||||
} from "../test-utils/camera-url-test-helpers.js";
|
||||
|
||||
const { callGateway } = vi.hoisted(() => ({
|
||||
callGateway: vi.fn(),
|
||||
@@ -206,10 +209,7 @@ describe("nodes camera_snap", () => {
|
||||
});
|
||||
|
||||
it("downloads camera_snap url payloads when node remoteIp is available", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => new Response("url-image", { status: 200 })),
|
||||
);
|
||||
stubFetchTextResponse("url-image");
|
||||
setupNodeInvokeMock({
|
||||
remoteIp: "198.51.100.42",
|
||||
invokePayload: {
|
||||
@@ -230,18 +230,11 @@ describe("nodes camera_snap", () => {
|
||||
const mediaPath = String((result.content?.[0] as { text?: string } | undefined)?.text ?? "")
|
||||
.replace(/^MEDIA:/, "")
|
||||
.trim();
|
||||
try {
|
||||
await expect(fs.readFile(mediaPath, "utf8")).resolves.toBe("url-image");
|
||||
} finally {
|
||||
await fs.unlink(mediaPath).catch(() => {});
|
||||
}
|
||||
await expect(readFileUtf8AndCleanup(mediaPath)).resolves.toBe("url-image");
|
||||
});
|
||||
|
||||
it("rejects camera_snap url payloads when node remoteIp is missing", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => new Response("url-image", { status: 200 })),
|
||||
);
|
||||
stubFetchTextResponse("url-image");
|
||||
setupNodeInvokeMock({
|
||||
invokePayload: {
|
||||
format: "jpg",
|
||||
@@ -263,10 +256,7 @@ describe("nodes camera_snap", () => {
|
||||
|
||||
describe("nodes camera_clip", () => {
|
||||
it("downloads camera_clip url payloads when node remoteIp is available", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => new Response("url-clip", { status: 200 })),
|
||||
);
|
||||
stubFetchTextResponse("url-clip");
|
||||
setupNodeInvokeMock({
|
||||
remoteIp: "198.51.100.42",
|
||||
invokePayload: {
|
||||
@@ -285,18 +275,11 @@ describe("nodes camera_clip", () => {
|
||||
const filePath = String((result.content?.[0] as { text?: string } | undefined)?.text ?? "")
|
||||
.replace(/^FILE:/, "")
|
||||
.trim();
|
||||
try {
|
||||
await expect(fs.readFile(filePath, "utf8")).resolves.toBe("url-clip");
|
||||
} finally {
|
||||
await fs.unlink(filePath).catch(() => {});
|
||||
}
|
||||
await expect(readFileUtf8AndCleanup(filePath)).resolves.toBe("url-clip");
|
||||
});
|
||||
|
||||
it("rejects camera_clip url payloads when node remoteIp is missing", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => new Response("url-clip", { status: 200 })),
|
||||
);
|
||||
stubFetchTextResponse("url-clip");
|
||||
setupNodeInvokeMock({
|
||||
invokePayload: {
|
||||
format: "mp4",
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
parseCameraClipPayload,
|
||||
parseCameraSnapPayload,
|
||||
writeCameraClipPayloadToFile,
|
||||
writeBase64ToFile,
|
||||
writeUrlToFile,
|
||||
writeCameraPayloadToFile,
|
||||
} from "../../cli/nodes-camera.js";
|
||||
import { parseEnvPairs, parseTimeoutMs } from "../../cli/nodes-run.js";
|
||||
import {
|
||||
@@ -295,16 +294,12 @@ export function createNodesTool(options?: {
|
||||
facing,
|
||||
ext: isJpeg ? "jpg" : "png",
|
||||
});
|
||||
if (payload.url) {
|
||||
if (!resolvedNode.remoteIp) {
|
||||
throw new Error("camera URL payload requires node remoteIp");
|
||||
}
|
||||
await writeUrlToFile(filePath, payload.url, {
|
||||
expectedHost: resolvedNode.remoteIp,
|
||||
});
|
||||
} else if (payload.base64) {
|
||||
await writeBase64ToFile(filePath, payload.base64);
|
||||
}
|
||||
await writeCameraPayloadToFile({
|
||||
filePath,
|
||||
payload,
|
||||
expectedHost: resolvedNode.remoteIp,
|
||||
invalidPayloadMessage: "invalid camera.snap payload",
|
||||
});
|
||||
content.push({ type: "text", text: `MEDIA:${filePath}` });
|
||||
if (payload.base64) {
|
||||
content.push({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parseNodeList, parsePairingList } from "../../shared/node-list-parse.js";
|
||||
import type { NodeListNode } from "../../shared/node-list-types.js";
|
||||
import { resolveNodeIdFromCandidates } from "../../shared/node-match.js";
|
||||
import { resolveNodeFromNodeList, resolveNodeIdFromNodeList } from "../../shared/node-resolve.js";
|
||||
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
||||
|
||||
export type { NodeListNode };
|
||||
@@ -142,17 +142,10 @@ export function resolveNodeIdFromList(
|
||||
query?: string,
|
||||
allowDefault = false,
|
||||
): string {
|
||||
const q = String(query ?? "").trim();
|
||||
if (!q) {
|
||||
if (allowDefault) {
|
||||
const picked = pickDefaultNode(nodes);
|
||||
if (picked) {
|
||||
return picked.nodeId;
|
||||
}
|
||||
}
|
||||
throw new Error("node required");
|
||||
}
|
||||
return resolveNodeIdFromCandidates(nodes, q);
|
||||
return resolveNodeIdFromNodeList(nodes, query, {
|
||||
allowDefault,
|
||||
pickDefaultNode: pickDefaultNode,
|
||||
});
|
||||
}
|
||||
|
||||
export async function resolveNodeId(
|
||||
@@ -169,6 +162,8 @@ export async function resolveNode(
|
||||
allowDefault = false,
|
||||
): Promise<NodeListNode> {
|
||||
const nodes = await loadNodes(opts);
|
||||
const nodeId = resolveNodeIdFromList(nodes, query, allowDefault);
|
||||
return nodes.find((node) => node.nodeId === nodeId) ?? { nodeId };
|
||||
return resolveNodeFromNodeList(nodes, query, {
|
||||
allowDefault,
|
||||
pickDefaultNode: pickDefaultNode,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user