mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix: normalize canvas numeric params
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
optionalFiniteNumberSchema,
|
||||
optionalNonNegativeIntegerSchema,
|
||||
optionalPositiveIntegerSchema,
|
||||
stringEnum,
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
import { Type } from "typebox";
|
||||
|
||||
export const CANVAS_ACTIONS = [
|
||||
@@ -12,30 +18,23 @@ export const CANVAS_ACTIONS = [
|
||||
|
||||
export const CANVAS_SNAPSHOT_FORMATS = ["png", "jpg", "jpeg"] as const;
|
||||
|
||||
function stringEnum<T extends readonly string[]>(values: T) {
|
||||
return Type.Unsafe<T[number]>({
|
||||
type: "string",
|
||||
enum: [...values],
|
||||
});
|
||||
}
|
||||
|
||||
export const CanvasToolSchema = Type.Object({
|
||||
action: stringEnum(CANVAS_ACTIONS),
|
||||
gatewayUrl: Type.Optional(Type.String()),
|
||||
gatewayToken: Type.Optional(Type.String()),
|
||||
timeoutMs: Type.Optional(Type.Number()),
|
||||
timeoutMs: optionalPositiveIntegerSchema(),
|
||||
node: Type.Optional(Type.String()),
|
||||
target: Type.Optional(Type.String()),
|
||||
x: Type.Optional(Type.Number()),
|
||||
y: Type.Optional(Type.Number()),
|
||||
width: Type.Optional(Type.Number()),
|
||||
height: Type.Optional(Type.Number()),
|
||||
x: optionalFiniteNumberSchema(),
|
||||
y: optionalFiniteNumberSchema(),
|
||||
width: optionalFiniteNumberSchema(),
|
||||
height: optionalFiniteNumberSchema(),
|
||||
url: Type.Optional(Type.String()),
|
||||
javaScript: Type.Optional(Type.String()),
|
||||
outputFormat: Type.Optional(stringEnum(CANVAS_SNAPSHOT_FORMATS)),
|
||||
maxWidth: Type.Optional(Type.Number()),
|
||||
quality: Type.Optional(Type.Number()),
|
||||
delayMs: Type.Optional(Type.Number()),
|
||||
maxWidth: optionalPositiveIntegerSchema(),
|
||||
quality: optionalFiniteNumberSchema({ minimum: 0, maximum: 1 }),
|
||||
delayMs: optionalNonNegativeIntegerSchema(),
|
||||
jsonl: Type.Optional(Type.String()),
|
||||
jsonlPath: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
@@ -97,6 +97,72 @@ describe("Canvas tool", () => {
|
||||
expect(imageResultParams?.imageSanitization).toEqual({ maxDimensionPx: 1600 });
|
||||
});
|
||||
|
||||
it("normalizes numeric string params before invoking node canvas commands", async () => {
|
||||
mocks.callGatewayTool.mockResolvedValue({
|
||||
payload: {
|
||||
format: "png",
|
||||
base64: Buffer.from("not-a-real-png").toString("base64"),
|
||||
},
|
||||
});
|
||||
const tool = createCanvasTool();
|
||||
|
||||
await tool.execute("tool-call-1", {
|
||||
action: "present",
|
||||
timeoutMs: "1500",
|
||||
x: "10.5",
|
||||
y: "-2",
|
||||
width: "640",
|
||||
height: "480",
|
||||
});
|
||||
|
||||
expect(mocks.callGatewayTool).toHaveBeenLastCalledWith(
|
||||
"node.invoke",
|
||||
{ timeoutMs: 1500 },
|
||||
expect.objectContaining({
|
||||
command: "canvas.present",
|
||||
params: {
|
||||
placement: {
|
||||
x: 10.5,
|
||||
y: -2,
|
||||
width: 640,
|
||||
height: 480,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await tool.execute("tool-call-2", {
|
||||
action: "snapshot",
|
||||
maxWidth: "800",
|
||||
quality: "0.75",
|
||||
});
|
||||
|
||||
expect(mocks.callGatewayTool).toHaveBeenLastCalledWith(
|
||||
"node.invoke",
|
||||
{},
|
||||
expect.objectContaining({
|
||||
command: "canvas.snapshot",
|
||||
params: {
|
||||
format: "png",
|
||||
maxWidth: 800,
|
||||
quality: 0.75,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects malformed numeric canvas params before invoking node commands", async () => {
|
||||
const tool = createCanvasTool();
|
||||
|
||||
await expect(
|
||||
tool.execute("tool-call-1", {
|
||||
action: "snapshot",
|
||||
maxWidth: "800px",
|
||||
}),
|
||||
).rejects.toThrow("maxWidth must be a positive integer");
|
||||
expect(mocks.callGatewayTool).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects node-controlled snapshot formats before creating image results", async () => {
|
||||
mocks.callGatewayTool.mockResolvedValue({
|
||||
payload: {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
jsonResult,
|
||||
readStringParam,
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
import { readFiniteNumberParam, readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers";
|
||||
import type { AnyAgentTool, OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
import { normalizeCanvasSnapshotFileExtension, parseCanvasSnapshotPayload } from "./cli-helpers.js";
|
||||
@@ -29,7 +30,7 @@ function readGatewayCallOptions(params: Record<string, unknown>) {
|
||||
return {
|
||||
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
|
||||
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
|
||||
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : undefined,
|
||||
timeoutMs: readPositiveIntegerParam(params, "timeoutMs"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,10 +115,10 @@ export function createCanvasTool(options?: CanvasToolOptions): AnyAgentTool {
|
||||
switch (action) {
|
||||
case "present": {
|
||||
const placement = {
|
||||
x: typeof params.x === "number" ? params.x : undefined,
|
||||
y: typeof params.y === "number" ? params.y : undefined,
|
||||
width: typeof params.width === "number" ? params.width : undefined,
|
||||
height: typeof params.height === "number" ? params.height : undefined,
|
||||
x: readFiniteNumberParam(params, "x"),
|
||||
y: readFiniteNumberParam(params, "y"),
|
||||
width: readFiniteNumberParam(params, "width"),
|
||||
height: readFiniteNumberParam(params, "height"),
|
||||
};
|
||||
const invokeParams: Record<string, unknown> = {};
|
||||
const presentTarget =
|
||||
@@ -169,14 +170,11 @@ export function createCanvasTool(options?: CanvasToolOptions): AnyAgentTool {
|
||||
? params.outputFormat.trim().toLowerCase()
|
||||
: "png";
|
||||
const format = formatRaw === "jpg" || formatRaw === "jpeg" ? "jpeg" : "png";
|
||||
const maxWidth =
|
||||
typeof params.maxWidth === "number" && Number.isFinite(params.maxWidth)
|
||||
? params.maxWidth
|
||||
: undefined;
|
||||
const quality =
|
||||
typeof params.quality === "number" && Number.isFinite(params.quality)
|
||||
? params.quality
|
||||
: undefined;
|
||||
const maxWidth = readPositiveIntegerParam(params, "maxWidth");
|
||||
const quality = readFiniteNumberParam(params, "quality", {
|
||||
min: 0,
|
||||
max: 1,
|
||||
});
|
||||
const raw = (await invoke("canvas.snapshot", {
|
||||
format,
|
||||
maxWidth,
|
||||
|
||||
Reference in New Issue
Block a user