UNPKG

@hashgraphonline/standards-agent-kit

Version:

A modular SDK for building on-chain autonomous agents using Hashgraph Online Standards, including HCS-10 for agent discovery and communication. https://hol.org

543 lines (476 loc) 18.1 kB
import { ZodType } from 'zod'; import { RenderConfigSchema, EnhancedRenderConfig, ZodSchemaWithRender, FormFieldType, FieldMetadata, SelectOption } from './types'; interface ExtendedZodSchema { description?: string; merge?: (other: ZodType<unknown>) => ZodType<unknown>; extend?: (extensions: Record<string, ZodType<unknown>>) => ZodType<unknown>; pick?: (fields: Record<string, boolean>) => ZodType<unknown>; omit?: (fields: Record<string, boolean>) => ZodType<unknown>; withRender?: (config: EnhancedRenderConfig | RenderConfigSchema) => ZodSchemaWithRender<unknown>; withProgressive?: (priority: 'essential' | 'common' | 'advanced' | 'expert', group?: string) => ZodSchemaWithRender<unknown>; withBlock?: (blockId: string) => ZodSchemaWithRender<unknown>; } /** * Extends a Zod schema with render configuration capabilities */ export function extendZodSchema<TSchema>(schema: ZodType<TSchema>): ZodSchemaWithRender<TSchema> { const extendedSchema = schema as ZodSchemaWithRender<TSchema>; extendedSchema.withRender = function(config: EnhancedRenderConfig | RenderConfigSchema): ZodSchemaWithRender<TSchema> { const newSchema = Object.create(this); const currentConfig = this._renderConfig || {} as EnhancedRenderConfig; const mergedConfig: EnhancedRenderConfig = { ...currentConfig, ...config, ui: { ...(currentConfig.ui || {}), ...(config.ui || {}) } }; if (currentConfig.progressive || (config as EnhancedRenderConfig).progressive) { mergedConfig.progressive = { ...(currentConfig.progressive || {} as NonNullable<EnhancedRenderConfig['progressive']>), ...((config as EnhancedRenderConfig).progressive || {} as NonNullable<EnhancedRenderConfig['progressive']>) }; } if (currentConfig.block || (config as EnhancedRenderConfig).block) { mergedConfig.block = { ...(currentConfig.block || {} as NonNullable<EnhancedRenderConfig['block']>), ...((config as EnhancedRenderConfig).block || {} as NonNullable<EnhancedRenderConfig['block']>) }; } (newSchema as ZodSchemaWithRender<TSchema>)._renderConfig = mergedConfig; (newSchema as ZodSchemaWithRender<TSchema>).withRender = extendedSchema.withRender; (newSchema as ZodSchemaWithRender<TSchema>).withProgressive = extendedSchema.withProgressive; (newSchema as ZodSchemaWithRender<TSchema>).withBlock = extendedSchema.withBlock; return newSchema as ZodSchemaWithRender<TSchema>; }; extendedSchema.withProgressive = function( priority: 'essential' | 'common' | 'advanced' | 'expert', group?: string ): ZodSchemaWithRender<TSchema> { const currentConfig = this._renderConfig || { fieldType: inferFieldTypeFromSchema(schema) }; return this.withRender({ ...currentConfig, progressive: { priority, group } }); }; extendedSchema.withBlock = function(blockId: string): ZodSchemaWithRender<TSchema> { const currentConfig = this._renderConfig || { fieldType: inferFieldTypeFromSchema(schema) }; return this.withRender({ ...currentConfig, block: { id: blockId, type: 'field', reusable: true } }); }; return extendedSchema; } /** * Checks if a schema has render configuration */ export function hasRenderConfig(schema: unknown): schema is ZodSchemaWithRender { return Boolean(schema && typeof (schema as Record<string, unknown>).withRender === 'function'); } /** * Extracts render configuration from a schema */ export function getRenderConfig(schema: ZodType<unknown>): EnhancedRenderConfig | undefined { if (hasRenderConfig(schema)) { return schema._renderConfig; } return undefined; } /** * Infers form field type from Zod schema type with enhanced field detection */ export function inferFieldTypeFromSchema(schema: ZodType<unknown>): FormFieldType { const typeName = (schema._def as Record<string, unknown>)?.typeName; switch (typeName) { case 'ZodString': { const stringChecks = ((schema._def as Record<string, unknown>)?.checks as Array<Record<string, unknown>>) || []; for (const check of stringChecks) { if (check.kind === 'email') {return 'text';} if (check.kind === 'url') {return 'text';} } return 'text'; } case 'ZodNumber': case 'ZodBigInt': return 'number'; case 'ZodBoolean': return 'checkbox'; case 'ZodEnum': case 'ZodNativeEnum': return 'select'; case 'ZodArray': return 'array'; case 'ZodUnion': case 'ZodDiscriminatedUnion': return 'select'; case 'ZodDate': return 'text'; case 'ZodObject': return 'object'; case 'ZodOptional': case 'ZodDefault': { const innerType = (schema._def as Record<string, unknown>)?.innerType as ZodType<unknown>; if (innerType) { return inferFieldTypeFromSchema(innerType); } return 'text'; } default: return 'text'; } } /** * Extracts options from enum, union, or literal schemas */ export function extractOptionsFromSchema(schema: ZodType<unknown>): SelectOption[] | undefined { const typeName = (schema._def as Record<string, unknown>)?.typeName; if (typeName === 'ZodEnum') { const values = (schema._def as Record<string, unknown>)?.values; if (Array.isArray(values)) { return values.map((value: string) => ({ value, label: value.charAt(0).toUpperCase() + value.slice(1).replace(/[_-]/g, ' '), })); } } if (typeName === 'ZodNativeEnum') { const enumObject = (schema._def as Record<string, unknown>)?.values as Record<string, unknown>; if (enumObject) { return Object.entries(enumObject).map(([key, value]) => ({ value, label: key.replace(/[_-]/g, ' '), })); } } if (typeName === 'ZodUnion') { const options: SelectOption[] = []; const unionOptions = (schema._def as Record<string, unknown>)?.options as Array<ZodType<unknown>>; if (Array.isArray(unionOptions)) { for (const option of unionOptions) { if ((option._def as Record<string, unknown>)?.typeName === 'ZodLiteral') { const value = (option._def as Record<string, unknown>)?.value; if (value !== undefined) { options.push({ value, label: typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : String(value), }); } } } } return options.length > 0 ? options : undefined; } return undefined; } /** * Checks if a schema is optional */ export function isOptionalSchema(schema: ZodType<unknown>): boolean { const typeName = (schema._def as Record<string, unknown>)?.typeName; return typeName === 'ZodOptional' || typeName === 'ZodDefault'; } /** * Gets the inner schema from optional/default wrappers */ export function getInnerSchema(schema: ZodType<unknown>): ZodType<unknown> { const typeName = (schema._def as Record<string, unknown>)?.typeName; if (typeName === 'ZodOptional' || typeName === 'ZodDefault') { const innerType = (schema._def as Record<string, unknown>)?.innerType as ZodType<unknown>; return innerType || schema; } return schema; } /** * Extracts validation constraints from a schema with enhanced support */ export function extractValidationConstraints(schema: ZodType<unknown>): Record<string, unknown> { const innerSchema = getInnerSchema(schema); const typeName = (innerSchema._def as Record<string, unknown>)?.typeName; const constraints: Record<string, unknown> = {}; if (typeName === 'ZodString') { const def = innerSchema._def as Record<string, unknown>; const checks = def.checks as Array<Record<string, unknown>>; if (checks && Array.isArray(checks)) { for (const check of checks) { switch (check.kind) { case 'min': constraints.minLength = check.value; break; case 'max': constraints.maxLength = check.value; break; case 'email': constraints.type = 'email'; break; case 'url': constraints.type = 'url'; break; case 'regex': constraints.pattern = check.regex as string; break; } } } } if (typeName === 'ZodNumber') { const def = innerSchema._def as Record<string, unknown>; const checks = def.checks as Array<Record<string, unknown>>; if (checks && Array.isArray(checks)) { for (const check of checks) { switch (check.kind) { case 'min': constraints.min = check.value; break; case 'max': constraints.max = check.value; break; case 'int': constraints.step = 1; break; case 'multipleOf': constraints.step = check.value; break; } } } } if (typeName === 'ZodArray') { const def = innerSchema._def as Record<string, unknown>; const minLength = def.minLength as Record<string, unknown> | undefined; const maxLength = def.maxLength as Record<string, unknown> | undefined; if (minLength !== undefined) { constraints.minItems = minLength.value; } if (maxLength !== undefined) { constraints.maxItems = maxLength.value; } } return constraints; } /** * Gets default value from schema with comprehensive type support */ export function getDefaultValue(schema: ZodType<unknown>): unknown { const typeName = (schema._def as Record<string, unknown>)?.typeName; if (typeName === 'ZodDefault') { const defaultValue = (schema._def as Record<string, unknown>)?.defaultValue; if (typeof defaultValue === 'function') { return (defaultValue as () => unknown)(); } return defaultValue; } const innerSchema = getInnerSchema(schema); const innerTypeName = (innerSchema._def as Record<string, unknown>)?.typeName; switch (innerTypeName) { case 'ZodString': return ''; case 'ZodNumber': case 'ZodBigInt': return 0; case 'ZodBoolean': return false; case 'ZodArray': return []; case 'ZodObject': { const shape = (innerSchema._def as Record<string, unknown>)?.shape as Record<string, ZodType<unknown>>; if (shape) { const defaultObj: Record<string, unknown> = {}; for (const [key, value] of Object.entries(shape)) { defaultObj[key] = getDefaultValue(value); } return defaultObj; } return {}; } case 'ZodDate': return new Date(); case 'ZodEnum': { const values = (innerSchema._def as Record<string, unknown>)?.values as unknown[]; return Array.isArray(values) && values.length > 0 ? values[0] : undefined; } default: return undefined; } } /** * Extracts field metadata from schema including render configuration */ export function extractFieldMetadata(schema: ZodType<unknown>): FieldMetadata { const innerSchema = getInnerSchema(schema); const fieldType = inferFieldTypeFromSchema(schema); const required = !isOptionalSchema(schema); const optional = isOptionalSchema(schema); const defaultValue = getDefaultValue(schema); const options = extractOptionsFromSchema(innerSchema); const constraints = extractValidationConstraints(schema); const description = (schema as ZodType<unknown> & ExtendedZodSchema)?.description; return { type: fieldType, required, optional, default: defaultValue, options, constraints, description, validation: { minLength: constraints.minLength as number, maxLength: constraints.maxLength as number, min: constraints.min as number, max: constraints.max as number, pattern: constraints.pattern ? new RegExp(constraints.pattern as string) : undefined, } }; } /** * Helper function to create common render configurations */ export const renderConfigs = { text: (label: string, placeholder?: string, priority: 'essential' | 'common' | 'advanced' | 'expert' = 'common'): EnhancedRenderConfig => ({ fieldType: 'text', ui: { label, placeholder, priority }, progressive: { priority } }), number: (label: string, min?: number, max?: number, priority: 'essential' | 'common' | 'advanced' | 'expert' = 'common'): EnhancedRenderConfig => ({ fieldType: 'number', ui: { label, priority }, constraints: { min, max }, progressive: { priority } }), select: (label: string, options: SelectOption[], priority: 'essential' | 'common' | 'advanced' | 'expert' = 'common'): EnhancedRenderConfig => ({ fieldType: 'select', ui: { label, priority }, options, progressive: { priority } }), textarea: (label: string, rows = 3, priority: 'essential' | 'common' | 'advanced' | 'expert' = 'common'): EnhancedRenderConfig => ({ fieldType: 'textarea', ui: { label, priority }, props: { rows }, progressive: { priority } }), currency: (label: string, symbol = 'HBAR', priority: 'essential' | 'common' | 'advanced' | 'expert' = 'common'): EnhancedRenderConfig => ({ fieldType: 'currency', ui: { label, priority }, props: { symbol }, progressive: { priority } }), array: (label: string, itemLabel?: string, priority: 'essential' | 'common' | 'advanced' | 'expert' = 'advanced'): EnhancedRenderConfig => ({ fieldType: 'array', ui: { label, priority }, props: { itemLabel }, progressive: { priority } }), essential: { text: (label: string, placeholder?: string) => renderConfigs.text(label, placeholder, 'essential'), number: (label: string, min?: number, max?: number) => renderConfigs.number(label, min, max, 'essential'), select: (label: string, options: SelectOption[]) => renderConfigs.select(label, options, 'essential'), textarea: (label: string, rows?: number) => renderConfigs.textarea(label, rows, 'essential'), }, advanced: { text: (label: string, placeholder?: string) => renderConfigs.text(label, placeholder, 'advanced'), number: (label: string, min?: number, max?: number) => renderConfigs.number(label, min, max, 'advanced'), select: (label: string, options: SelectOption[]) => renderConfigs.select(label, options, 'advanced'), textarea: (label: string, rows?: number) => renderConfigs.textarea(label, rows, 'advanced'), array: (label: string, itemLabel?: string) => renderConfigs.array(label, itemLabel, 'advanced'), }, expert: { text: (label: string, placeholder?: string) => renderConfigs.text(label, placeholder, 'expert'), number: (label: string, min?: number, max?: number) => renderConfigs.number(label, min, max, 'expert'), select: (label: string, options: SelectOption[]) => renderConfigs.select(label, options, 'expert'), textarea: (label: string, rows?: number) => renderConfigs.textarea(label, rows, 'expert'), } }; /** * Extends Zod prototype to add withRender functionality globally */ export function installZodRenderExtensions() { if (!(ZodType.prototype as typeof ZodType.prototype & ExtendedZodSchema).withRender) { (ZodType.prototype as typeof ZodType.prototype & ExtendedZodSchema).withRender = function(config: EnhancedRenderConfig | RenderConfigSchema) { return extendZodSchema(this).withRender(config); }; (ZodType.prototype as typeof ZodType.prototype & ExtendedZodSchema).withProgressive = function( priority: 'essential' | 'common' | 'advanced' | 'expert', group?: string ) { return extendZodSchema(this).withProgressive(priority, group); }; (ZodType.prototype as typeof ZodType.prototype & ExtendedZodSchema).withBlock = function(blockId: string) { return extendZodSchema(this).withBlock(blockId); }; } } /** * Creates a progressive form schema with grouped fields */ export function createProgressiveSchema<TSchema>( baseSchema: ZodType<TSchema>, groups: Record<string, { priority: 'essential' | 'common' | 'advanced' | 'expert'; fields: string[] }> ): ZodSchemaWithRender<TSchema> { const extendedSchema = extendZodSchema(baseSchema); const typeName = (baseSchema._def as Record<string, unknown>)?.typeName; if (typeName === 'ZodObject') { const shape = (baseSchema._def as Record<string, unknown>)?.shape as Record<string, ZodType<unknown>>; if (shape) { const enhancedShape: Record<string, ZodType<unknown>> = {}; for (const [fieldName, fieldSchema] of Object.entries(shape)) { let fieldGroup: string | undefined; let fieldPriority: 'essential' | 'common' | 'advanced' | 'expert' = 'common'; for (const [groupName, groupConfig] of Object.entries(groups)) { if (groupConfig.fields.includes(fieldName)) { fieldGroup = groupName; fieldPriority = groupConfig.priority; break; } } enhancedShape[fieldName] = extendZodSchema(fieldSchema).withProgressive(fieldPriority, fieldGroup); } return extendedSchema; } } return extendedSchema; } /** * Utility to convert legacy render configs to enhanced configs */ export function enhanceRenderConfig(config: RenderConfigSchema): EnhancedRenderConfig { return { ...config, progressive: { priority: config.ui?.priority || 'common', group: config.ui?.group } }; } /** * Helper to extract progressive disclosure information from schema */ export function extractProgressiveInfo(schema: ZodType<unknown>): { priority: 'essential' | 'common' | 'advanced' | 'expert'; group?: string; } { const renderConfig = getRenderConfig(schema); if (renderConfig?.progressive) { return { priority: renderConfig.progressive.priority, group: renderConfig.progressive.group }; } if (renderConfig?.ui?.priority) { return { priority: renderConfig.ui.priority, group: renderConfig.ui.group }; } return { priority: 'common' }; }