UNPKG

permamind

Version:

An MCP server that provides an immortal memory layer for AI agents and clients

137 lines (136 loc) 5.52 kB
import { z } from "zod"; export class ToolValidationError extends Error { field; value; expectedType; constructor(field, value, expectedType, message) { super(message || `Invalid ${field}: expected ${expectedType}, got ${typeof value}`); this.field = field; this.value = value; this.expectedType = expectedType; this.name = "ToolValidationError"; } } export class ToolValidator { static validateArray(value, fieldName, itemValidator) { if (!Array.isArray(value)) { throw new ToolValidationError(fieldName, value, "array"); } if (itemValidator) { return value.map((item, index) => { try { return itemValidator(item, index); } catch (error) { if (error instanceof ToolValidationError) { throw new ToolValidationError(`${fieldName}[${index}].${error.field}`, error.value, error.expectedType, error.message); } throw error; } }); } return value; } static validateBoolean(value, fieldName) { if (typeof value !== "boolean") { throw new ToolValidationError(fieldName, value, "boolean"); } return value; } static validateEnum(value, fieldName, allowedValues) { if (!allowedValues.includes(value)) { throw new ToolValidationError(fieldName, value, `one of: ${allowedValues.join(", ")}`, `${fieldName} must be one of: ${allowedValues.join(", ")}`); } return value; } static validateNumber(value, fieldName, options) { if (typeof value !== "number" || isNaN(value)) { throw new ToolValidationError(fieldName, value, "number"); } if (options?.integer && !Number.isInteger(value)) { throw new ToolValidationError(fieldName, value, "integer"); } if (options?.min !== undefined && value < options.min) { throw new ToolValidationError(fieldName, value, `number >= ${options.min}`, `${fieldName} must be at least ${options.min}`); } if (options?.max !== undefined && value > options.max) { throw new ToolValidationError(fieldName, value, `number <= ${options.max}`, `${fieldName} must be at most ${options.max}`); } return value; } static validateObject(value, fieldName, schema) { if (typeof value !== "object" || value === null) { throw new ToolValidationError(fieldName, value, "object"); } try { return schema.parse(value); } catch (error) { if (error instanceof z.ZodError) { const firstError = error.errors[0]; throw new ToolValidationError(`${fieldName}.${firstError.path.join(".")}`, "received" in firstError ? firstError.received : undefined, "expected" in firstError ? String(firstError.expected) : "valid value", firstError.message); } throw error; } } static validateOptional(value, fieldName, validator) { if (value === undefined || value === null) { return undefined; } return validator(value, fieldName); } static validateRequired(value, fieldName) { if (value === undefined || value === null) { throw new ToolValidationError(fieldName, value, "required value"); } return value; } static validateString(value, fieldName, options) { if (typeof value !== "string") { throw new ToolValidationError(fieldName, value, "string"); } if (options?.minLength && value.length < options.minLength) { throw new ToolValidationError(fieldName, value, `string with min length ${options.minLength}`, `${fieldName} must be at least ${options.minLength} characters`); } if (options?.maxLength && value.length > options.maxLength) { throw new ToolValidationError(fieldName, value, `string with max length ${options.maxLength}`, `${fieldName} must be at most ${options.maxLength} characters`); } if (options?.pattern && !options.pattern.test(value)) { throw new ToolValidationError(fieldName, value, `string matching pattern ${options.pattern}`, `${fieldName} must match the required pattern`); } return value; } } // Common validation schemas export const CommonSchemas = { address: z .string() .regex(/^[a-zA-Z0-9_-]{43}$/, "Invalid Arweave address format"), importance: z.number().min(0).max(1), limit: z.number().int().min(1).max(1000).optional().default(100), memoryType: z.enum([ "conversation", "context", "knowledge", "procedure", "reasoning", "enhancement", "performance", "workflow", ]), nonNegativeInteger: z.number().int().min(0), offset: z.number().int().min(0).optional().default(0), positiveInteger: z.number().int().positive(), processId: z .string() .regex(/^[a-zA-Z0-9_-]{43}$/, "Invalid process ID format"), quantity: z.string().regex(/^\d+$/, "Quantity must be a numeric string"), sortOrder: z.enum(["asc", "desc"]).optional().default("desc"), tagArray: z.array(z.object({ name: z.string(), value: z.string(), })), };