Files
ComfyUI-OpenClaw/web/openclaw_parameter_lab_policy.js

199 lines
6.1 KiB
JavaScript

export const PARAMETER_LAB_POLICY = Object.freeze({
version: "1.0",
maxRequestBytes: 5 * 1024 * 1024,
maxWorkflowUtf8Bytes: 4 * 1024 * 1024,
maxSweepDimensions: 8,
maxValuesPerDimension: 50,
maxNodeIdUtf8Bytes: 128,
maxWidgetNameUtf8Bytes: 256,
maxScalarStringUtf8Bytes: 16 * 1024,
maxPlanUtf8Bytes: 8 * 1024 * 1024,
maxSweepCombinations: 50,
maxCompareItems: 8,
});
const encoder = new TextEncoder();
const valid = () => ({ ok: true, reason: "" });
const invalid = (reason) => ({ ok: false, reason });
function utf8Size(value) {
return encoder.encode(value).byteLength;
}
function hasControlCharacters(value) {
return Array.from(value).some((character) => {
const code = character.charCodeAt(0);
return code < 32 || code === 127;
});
}
function validateNodeId(nodeId) {
if (nodeId === null || nodeId === undefined || nodeId === "") {
return invalid("node_id_required");
}
if (
typeof nodeId !== "string" &&
!(typeof nodeId === "number" && Number.isInteger(nodeId))
) {
return invalid("invalid_node_id");
}
const normalized = String(nodeId);
if (!normalized.trim()) {
return invalid("node_id_required");
}
// IMPORTANT: replay keys use the first "." as the node/widget separator.
if (normalized.includes(".") || hasControlCharacters(normalized)) {
return invalid("invalid_node_id");
}
if (utf8Size(normalized) > PARAMETER_LAB_POLICY.maxNodeIdUtf8Bytes) {
return invalid("node_id_too_large");
}
return valid();
}
function validateWidgetName(widgetName) {
if (typeof widgetName !== "string" || !widgetName.trim()) {
return invalid("widget_name_required");
}
if (hasControlCharacters(widgetName)) {
return invalid("invalid_widget_name");
}
if (utf8Size(widgetName) > PARAMETER_LAB_POLICY.maxWidgetNameUtf8Bytes) {
return invalid("widget_name_too_large");
}
return valid();
}
export function isParameterLabScalar(value) {
return (
typeof value === "string" ||
typeof value === "boolean" ||
(typeof value === "number" && Number.isFinite(value))
);
}
function validateScalar(value) {
if (!isParameterLabScalar(value)) {
return invalid("invalid_scalar_value");
}
if (
typeof value === "string" &&
utf8Size(value) > PARAMETER_LAB_POLICY.maxScalarStringUtf8Bytes
) {
return invalid("scalar_string_too_large");
}
return valid();
}
export function validateParameterLabScalar(value) {
return validateScalar(value);
}
export function filterParameterLabCandidates(candidates) {
if (!Array.isArray(candidates)) {
return [];
}
const filtered = [];
const presentations = new Set();
for (const candidate of candidates) {
if (!validateScalar(candidate).ok) {
continue;
}
const presentation = String(candidate);
if (presentations.has(presentation)) {
continue;
}
presentations.add(presentation);
filtered.push(candidate);
}
return filtered;
}
export function validateParameterLabDimensions(dimensions) {
if (!Array.isArray(dimensions) || dimensions.length === 0) {
return invalid("dimensions_required");
}
if (dimensions.length > PARAMETER_LAB_POLICY.maxSweepDimensions) {
return invalid("too_many_dimensions");
}
const dimensionKeys = new Set();
let combinations = 1;
for (const dimension of dimensions) {
if (!dimension || typeof dimension !== "object" || Array.isArray(dimension)) {
return invalid("invalid_dimension");
}
const nodeResult = validateNodeId(dimension.node_id);
if (!nodeResult.ok) {
return nodeResult;
}
const widgetResult = validateWidgetName(dimension.widget_name);
if (!widgetResult.ok) {
return widgetResult;
}
const dimensionKey = `${String(dimension.node_id)}\u0000${dimension.widget_name}`;
if (dimensionKeys.has(dimensionKey)) {
return invalid("duplicate_dimension");
}
dimensionKeys.add(dimensionKey);
if (!Array.isArray(dimension.values) || dimension.values.length === 0) {
return invalid("values_required");
}
if (dimension.values.length > PARAMETER_LAB_POLICY.maxValuesPerDimension) {
return invalid("too_many_values");
}
const presentations = new Set();
for (const value of dimension.values) {
const scalarResult = validateScalar(value);
if (!scalarResult.ok) {
return scalarResult;
}
const presentation = String(value);
if (presentations.has(presentation)) {
return invalid("duplicate_ambiguous_value");
}
presentations.add(presentation);
}
const strategy = dimension.strategy || "grid";
if (strategy !== "grid" && strategy !== "compare") {
return invalid("invalid_strategy");
}
combinations *= dimension.values.length;
if (combinations > PARAMETER_LAB_POLICY.maxSweepCombinations) {
return invalid("sweep_too_large");
}
}
return valid();
}
export function validateParameterLabWorkflow(workflowJson) {
if (typeof workflowJson !== "string" || !workflowJson.trim()) {
return invalid("workflow_required");
}
if (utf8Size(workflowJson) > PARAMETER_LAB_POLICY.maxWorkflowUtf8Bytes) {
return invalid("workflow_too_large");
}
return valid();
}
export function validateParameterLabRequestBody(payload) {
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
return invalid("invalid_payload");
}
let serialized;
try {
serialized = JSON.stringify(payload);
} catch {
return invalid("invalid_payload");
}
if (typeof serialized !== "string") {
return invalid("invalid_payload");
}
if (utf8Size(serialized) > PARAMETER_LAB_POLICY.maxRequestBytes) {
return invalid("payload_too_large");
}
return valid();
}