openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
291 lines (290 loc) • 11.6 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js";
import { t as PluginLruCache } from "./plugin-cache-primitives-BaxqicKH.js";
import { t as sanitizeTerminalText } from "./safe-text-BkQYdJi1.js";
import { n as findJsonSchemaShapeError, r as normalizeJsonSchemaForTypeBox, t as applyJsonSchemaDefaults } from "./json-schema-defaults-BxlICyTl.js";
import { Compile } from "typebox/compile";
import { Format } from "typebox/format";
//#region src/config/allowed-values.ts
const MAX_ALLOWED_VALUES_HINT = 12;
const MAX_ALLOWED_VALUE_CHARS = 160;
function truncateHintText(text, limit) {
if (text.length <= limit) return text;
return `${text.slice(0, limit)}... (+${text.length - limit} chars)`;
}
function safeStringify(value) {
try {
const serialized = JSON.stringify(value);
if (serialized !== void 0) return serialized;
} catch {}
return String(value);
}
function toAllowedValueLabel(value) {
if (typeof value === "string") return JSON.stringify(truncateHintText(value, MAX_ALLOWED_VALUE_CHARS));
return truncateHintText(safeStringify(value), MAX_ALLOWED_VALUE_CHARS);
}
function toAllowedValueValue(value) {
if (typeof value === "string") return value;
return safeStringify(value);
}
function toAllowedValueDedupKey(value) {
if (value === null) return "null:null";
const kind = typeof value;
if (kind === "string") return `string:${value}`;
return `${kind}:${safeStringify(value)}`;
}
/** Summarizes enum/allowed-value candidates for compact validation error hints. */
function summarizeAllowedValues(values) {
if (values.length === 0) return null;
const deduped = [];
const seenValues = /* @__PURE__ */ new Set();
for (const item of values) {
const dedupeKey = toAllowedValueDedupKey(item);
if (seenValues.has(dedupeKey)) continue;
seenValues.add(dedupeKey);
deduped.push({
value: toAllowedValueValue(item),
label: toAllowedValueLabel(item)
});
}
const shown = deduped.slice(0, MAX_ALLOWED_VALUES_HINT);
const hiddenCount = deduped.length - shown.length;
const formattedCore = shown.map((entry) => entry.label).join(", ");
const formatted = hiddenCount > 0 ? `${formattedCore}, ... (+${hiddenCount} more)` : formattedCore;
return {
values: shown.map((entry) => entry.value),
hiddenCount,
formatted
};
}
function messageAlreadyIncludesAllowedValues(message) {
const lower = normalizeLowercaseStringOrEmpty(message);
return lower.includes("(allowed:") || lower.includes("expected one of");
}
/** Appends an allowed-values hint unless the validation message already includes one. */
function appendAllowedValuesHint(message, summary) {
if (messageAlreadyIncludesAllowedValues(message)) return message;
return `${message} (allowed: ${summary.formatted})`;
}
//#endregion
//#region src/plugins/schema-validator.ts
const schemaCache = new PluginLruCache(512);
const annotationOnlyFormats = [
"date-time",
"date",
"duration",
"email",
"hostname",
"idn-email",
"idn-hostname",
"ipv4",
"ipv6",
"iri-reference",
"iri",
"json-pointer-uri-fragment",
"json-pointer",
"regex",
"relative-json-pointer",
"time",
"uri-reference",
"uri-template",
"url",
"uuid"
];
function fingerprintSchema(schema) {
return JSON.stringify(schema);
}
function schemaHasDefaults(schema) {
if (!schema || typeof schema !== "object") return false;
if (Array.isArray(schema)) return schema.some((item) => schemaHasDefaults(item));
const record = schema;
if (Object.hasOwn(record, "default")) return true;
return Object.values(record).some((value) => schemaHasDefaults(value));
}
function cloneValidationValue(value) {
if (value === void 0 || value === null) return value;
return structuredClone(value);
}
function compileSchema(schema) {
return Compile(normalizeJsonSchemaForTypeBox(schema));
}
function relaxConditionalRequiredKeywords(schema, insideConditionalBranch = false) {
if (Array.isArray(schema)) return schema.map((entry) => relaxConditionalRequiredKeywords(entry, insideConditionalBranch));
if (!schema || typeof schema !== "object") return schema;
return Object.fromEntries(Object.entries(schema).filter(([key]) => !(insideConditionalBranch && key === "required")).map(([key, value]) => [key, typeof value === "boolean" || value && typeof value === "object" ? relaxConditionalRequiredKeywords(value, insideConditionalBranch || key === "then" || key === "else") : value]));
}
function withPluginFormatSemantics(callback) {
const previousFormats = Format.Entries();
Format.Set("uri", (value) => URL.canParse(value));
for (const format of annotationOnlyFormats) Format.Set(format, () => true);
try {
return callback();
} finally {
Format.Clear();
for (const [format, check] of previousFormats) Format.Set(format, check);
}
}
function checkSchema(validate, value) {
return withPluginFormatSemantics(() => {
if (validate.Check(value)) return null;
return [...validate.Errors(value)];
});
}
function applyDefaultsWithPluginFormatSemantics(schema, value) {
return withPluginFormatSemantics(() => applyJsonSchemaDefaults(schema, value));
}
function isDefaultActivatedConditionalFailure(params) {
if (checkSchema(compileSchema(relaxConditionalRequiredKeywords(params.schema)), params.defaultedValue)) return false;
return checkSchema(compileSchema(params.schema), params.originalValue) === null;
}
function normalizeErrorPath(instancePath) {
const path = instancePath?.replace(/^\//, "").replace(/\//g, ".");
return path && path.length > 0 ? path : "<root>";
}
function appendPathSegment(path, segment) {
const trimmed = segment.trim();
if (!trimmed) return path;
if (path === "<root>") return trimmed;
return `${path}.${trimmed}`;
}
function firstStringParam(value) {
if (typeof value === "string" && value.trim()) return value;
if (Array.isArray(value)) return value.find((entry) => typeof entry === "string" && entry.trim().length > 0) ?? null;
return null;
}
function resolveMissingProperty(error) {
if (error.keyword !== "required" && error.keyword !== "dependentRequired" && error.keyword !== "dependencies") return null;
return firstStringParam(error.params?.missingProperty) ?? firstStringParam(error.params?.requiredProperties) ?? firstStringParam(error.params?.dependencies);
}
function resolveValidationErrorPath(error) {
const basePath = normalizeErrorPath(error.instancePath);
const missingProperty = resolveMissingProperty(error);
if (!missingProperty) return basePath;
return appendPathSegment(basePath, missingProperty);
}
function extractAllowedValues(error) {
if (error.keyword === "enum") {
const allowedValues = error.params?.allowedValues;
return Array.isArray(allowedValues) ? allowedValues : null;
}
if (error.keyword === "const") {
const params = error.params;
if (!params || !Object.hasOwn(params, "allowedValue")) return null;
return [params.allowedValue];
}
return null;
}
function getAllowedValuesSummary(error) {
const allowedValues = extractAllowedValues(error);
if (!allowedValues) return null;
return summarizeAllowedValues(allowedValues);
}
function resolveAdditionalProperty(error) {
if (error.keyword !== "additionalProperties") return;
return firstStringParam(error.params?.additionalProperty) ?? void 0;
}
function resolveAdditionalProperties(error) {
if (error.keyword !== "additionalProperties") return [];
const additionalProperties = error.params?.additionalProperties;
if (Array.isArray(additionalProperties)) return additionalProperties.filter((entry) => typeof entry === "string");
const additionalProperty = error.params?.additionalProperty;
return typeof additionalProperty === "string" ? [additionalProperty] : [];
}
function formatRequiredMessage(error) {
const missingProperty = resolveMissingProperty(error);
if (!missingProperty) return null;
return `must have required property '${missingProperty}'`;
}
function formatAdditionalPropertiesMessage(error) {
const additionalProperties = resolveAdditionalProperties(error);
if (additionalProperties.length === 0) return null;
return `must not have additional properties: ${additionalProperties.map((entry) => `"${entry}"`).join(", ")}`;
}
function formatValidationErrorMessage(error) {
return formatRequiredMessage(error) ?? formatAdditionalPropertiesMessage(error) ?? error.message ?? "invalid";
}
function formatValidationErrors(errors) {
if (!errors || errors.length === 0) return [{
path: "<root>",
message: "invalid config",
text: "<root>: invalid config"
}];
return errors.map((error) => {
const path = resolveValidationErrorPath(error);
const baseMessage = formatValidationErrorMessage(error);
const allowedValuesSummary = getAllowedValuesSummary(error);
const additionalProperty = resolveAdditionalProperty(error);
const message = allowedValuesSummary ? appendAllowedValuesHint(baseMessage, allowedValuesSummary) : baseMessage;
return {
path,
message,
text: `${sanitizeTerminalText(path)}: ${sanitizeTerminalText(message)}`,
...additionalProperty ? { additionalProperty } : {},
...allowedValuesSummary ? {
allowedValues: allowedValuesSummary.values,
allowedValuesHiddenCount: allowedValuesSummary.hiddenCount
} : {}
};
});
}
/**
* Validate a plugin-owned value against a JSON Schema, optionally hydrating schema defaults.
* The cache key is caller-owned so repeated plugin/schema validations can reuse compiled TypeBox validators.
*/
function validateJsonSchemaValue(params) {
const schemaError = findJsonSchemaShapeError(params.schema);
if (schemaError) throw new Error(sanitizeTerminalText(`invalid schema: ${schemaError}`));
if (!(params.cache !== false)) {
const validate = compileSchema(params.schema);
const value = params.applyDefaults && schemaHasDefaults(params.schema) ? applyDefaultsWithPluginFormatSemantics(params.schema, cloneValidationValue(params.value)) : params.value;
const errors = checkSchema(validate, value);
if (!errors) return {
ok: true,
value
};
if (params.applyDefaults && value !== params.value && isDefaultActivatedConditionalFailure({
schema: params.schema,
originalValue: params.value,
defaultedValue: value
})) return {
ok: true,
value
};
return {
ok: false,
errors: formatValidationErrors(errors)
};
}
const cacheKey = params.applyDefaults ? `${params.cacheKey}::defaults` : params.cacheKey;
let cached = schemaCache.get(cacheKey);
const schemaFingerprint = !cached || cached.schema !== params.schema ? fingerprintSchema(params.schema) : void 0;
if (!cached || cached.schema !== params.schema && cached.schemaFingerprint !== schemaFingerprint) {
const validate = compileSchema(params.schema);
cached = {
hasDefaults: params.applyDefaults ? schemaHasDefaults(params.schema) : false,
validate,
schema: params.schema,
schemaFingerprint: schemaFingerprint ?? fingerprintSchema(params.schema)
};
schemaCache.set(cacheKey, cached);
} else if (cached.schema !== params.schema) cached.schema = params.schema;
const value = params.applyDefaults && cached.hasDefaults ? applyDefaultsWithPluginFormatSemantics(params.schema, cloneValidationValue(params.value)) : params.value;
const errors = checkSchema(cached.validate, value);
if (!errors) return {
ok: true,
value
};
if (params.applyDefaults && value !== params.value && isDefaultActivatedConditionalFailure({
schema: params.schema,
originalValue: params.value,
defaultedValue: value
})) return {
ok: true,
value
};
return {
ok: false,
errors: formatValidationErrors(errors)
};
}
//#endregion
export { appendAllowedValuesHint as n, summarizeAllowedValues as r, validateJsonSchemaValue as t };