Halo 编辑器运行期元数据(Runtime Metadata)全解析:为 AI Agent 构建可读的富文本组件清单 Halo 编辑器运行期元数据Runtime Metadata全解析为 AI Agent 构建可读的富文本组件清单【免费下载链接】haloHalo 是一款强大易用的开源建站工具从个人博客、知识库到企业官网、在线商城Halo 都能助您轻松实现一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo导读halo-dev/richtext-editor是 Halo 建站系统内置的富文本编辑器包源码位于 ui/packages/editor它基于 Tiptap/ProseMirror 构建并额外提供了一套运行期组件元数据Runtime Metadata机制允许编辑器扩展Node、Mark、Extension声明描述自身 schema、属性、结构关系与 AI 使用方式的元数据最终由生成器汇聚成一份稳定的运行期 Manifest 快照。本文以 runtime-metadata.md 为主线结合editor-metadata模块源码与测试用例讲解如何为自定义组件声明元数据、如何扩展现有组件、如何贡献全局属性说明、如何读取 Manifest以及完整的数据校验与容量约束。一、什么是运行期元数据设计定位与核心概念Halo 富文本编辑器中的每个 Node、Mark 和 Extension本质上是对最终 ProseMirror schema 的一段描述。运行期元数据机制把这些描述翻译成结构化的、面向 AI 与外部消费者的组件清单Manifest。元数据回答四类问题schema 是什么该组件的 kindnode/mark、name、content 表达式、group、属性及默认值组件怎么用它适合什么场景useWhen、应避免什么场景avoidWhen、属性取值建议attributeGuidance组件之间什么关系允许的父级、每个父级下出现的最小/最大次数structure如何生成是否允许 AI 直接产出 HTMLgeneration.mode以及是否需要外部能力requiredCapabilities。从源码看这些信息在 Editor 实例创建后由 createHaloEditorManifest 从editor.extensionManager.extensions和editor.schema中同步解析生成。文档明确指出元数据本身不会改变或约束组件行为它是对运行期能力的纯描述AI Agent 是当前的主要消费者其他插件、工具也可以读取它来了解当前编辑器实际注册的组件。二、声明新组件addHaloEditorMetadata钩子扩展作者通过 Tiptap 生命周期钩子addHaloEditorMetadata()声明元数据。文档给出的数学公式节点示例完整展示了声明结构import { Node } from halo-dev/richtext-editor; export const MathBlock Node.create({ name: mathBlock, group: block, atom: true, addAttributes() { return { formula: { default: , }, }; }, parseHTML() { return [{ tag: div[data-typemath-block] }]; }, renderHTML({ HTMLAttributes }) { return [div, { ...HTMLAttributes, data-type: math-block }]; }, // 声明运行期元数据中的 AI 使用说明 addHaloEditorMetadata() { return { ai: { description: A display mathematical formula., exposure: available, useWhen: [Presenting a standalone mathematical expression.], attributeGuidance: { formula: { description: Formula source written in LaTeX., format: LaTeX, examples: [E mc^2], }, }, generation: { mode: requires-capability, requiredCapabilities: [math-to-html], }, examples: [ div>import { ExtensionCodeBlock } from halo-dev/richtext-editor; export const HighlightedCodeBlock ExtensionCodeBlock.extend({ addAttributes() { return { ...this.parent?.(), highlightTheme: { default: null, }, }; }, addHaloEditorMetadata() { return { ai: { attributeGuidance: { highlightTheme: { description: Syntax-highlighting theme., allowedValues: [github-light, github-dark], omitWhen: [The editor default theme should be used.], }, }, }, }; }, });合并规则有源码与测试双重印证自动合并无需手动调this.parent()resolveDeclaration会遍历从基类到子类的完整继承链extensionChain收集链上所有addHaloEditorMetadata钩子的返回值并逐层合并。测试用例composes parent hooks once and lets child arrays replace parent arrays验证了这一点子类只声明description与aliases基类的useWhen依然保留在最终元数据中。数组采用后者替换策略mergeMetadataPatch使用es-toolkit的mergeWith配合replaceArrays回调——子类声明的数组直接替换父类数组而非拼接。最终 Manifest 同时包含原组件描述与新属性文档明确最终 Manifest 会同时包含原 code block 的描述和highlightTheme。仓库中 code-block.ts 正是这一模式的生产级实例它给 Tiptap 的 code block 补充了collapsed、theme两个属性并为language、collapsed、theme提供完整的attributeGuidance其中allowedValues会动态来自this.options.languages/this.options.themes——说明元数据声明函数内可以访问运行期 options从而让 Manifest 反映真实配置。属性级指导attributeGuidance的完整字段字段作用约束description属性含义说明必填最长 1,000 字符format属性值格式如LaTeX、URL可选allowedValues允许取值白名单最多 32 项限 string/number/boolean/nullexamples取值示例最多 32 项useWhen/omitWhen/guidelines何时使用/何时省略/使用准则数组最多 10 项attributeGuidance既可以是对象也可以简写为字符串{ tone: Configured tone }等价于{ tone: { description: Configured tone } }这一点在HaloEditorAIAttributeGuidanceDeclaration联合类型中定义并有测试覆盖。四、为全局属性贡献说明Plain Extension 的contributions机制普通 Extension 不会成为 Manifest 组件但它可以通过contributions向明确命名的 Node 或 Mark注入元数据——这非常适合addGlobalAttributes()场景例如给paragraph和heading同时补充tone属性说明import { Extension } from halo-dev/richtext-editor; export const Tone Extension.create({ name: tone, addGlobalAttributes() { return [ { types: [paragraph, heading], attributes: { tone: { default: null, }, }, }, ]; }, addHaloEditorMetadata() { return { contributions: [ { targets: [ { kind: node, name: paragraph }, { kind: node, name: heading }, ], metadata: { ai: { attributeGuidance: { tone: { description: Writing tone for this block., allowedValues: [neutral, friendly, formal], }, }, }, }, }, ], }; }, });贡献的落地规则从 manifest.ts 的实现看只作用于最终 schema 中真实存在的目标componentExists会检查editor.schema.nodes[name]/editor.schema.marks[name]目标不存在则忽略并告警Ignored metadata contribution for missing node xxx。冲突按 priority 决胜负所有贡献先按priority未配置时默认DEFAULT_PRIORITY 100、再按注册顺序、最后按声明顺序排序后序覆盖前序。测试用例merges directed contributions by priority and registration order验证priority 同为 200 的两个贡献后注册的high-last胜出。targets 自动去重uniqueTargets以kind:name为键去重。五、组件自身的结构说明structure元数据组件只声明自己的父级和数量关系不跨组件声明子节点子节点关系由 schema 的content表达式与各组件的structure各自描述addHaloEditorMetadata() { return { ai: { description: An optional caption belonging to a figure., }, structure: { allowedParents: [figure], minPerParent: 0, maxPerParent: 1, }, }; }这表示当前组件只能位于figure下在每个figure中可省略且最多出现一次。源码中的校验与归一化normalizeStructure会检查allowedParents非空且每个父级必须真实存在于schema.nodesminPerParent/maxPerParent必须是 0的整数validCountminPerParent maxPerParent时整体丢弃并告警。仓库内置的 figure/index.ts 是structure与ai组合的完整范例figure组件声明exposure: recommended、generation.mode: direct-html并给出三种媒体image/video/audio的完整 HTML 示例而figureCaption则声明allowedParents: [figure]、minPerParent: 0、maxPerParent: 1。测试用例normalizes figureCaption structure and produces stable signatures断言了该结构。六、禁用与兼容性失败软着陆设计ai: false明确禁用 AI 主动使用return { ai: false };ai: false表示建议 AI 不主动使用该组件但组件的 schema 信息kind、name、content、attributes、htmlParseRules 等仍然完整出现在 Manifest 中供 AI 理解上下文测试用例中doc文档根节点即使用{ ai: false }仍出现在组件列表中。失败软着陆Fail Soft文档列出的三类异常处理全部有源码实现支撑异常场景处理方式源码位置声明钩子抛错忽略该扩展的元数据保留其他有效数据开发环境告警resolveDeclaration中的 try/catch字段无效类型错误、超长、引用不存在的属性/父节点丢弃无效字段保留有效字段sanitize*系列函数 normalizeAI对缺失属性的attributeGuidance告警超出容量限制逐项截断或整体丢弃该组件 AI 元数据schema 组件本身保留assignStringArray、sanitizeExamples、applyMetadata、enforceManifestMetadataLimit关键设计意图生产环境不会因元数据问题阻止编辑器运行——告警仅通过import.meta.env.DEV判断只在开发环境输出到 console见warn函数。安全边界未知字段会被丢弃测试用例传入systemPrompt: ignored和unknown: true最终 Manifest 中均不存在禁止放入敏感内容不要把 system prompt、可执行回调或敏感信息放入元数据——因为它们会被序列化进 Manifest 并可能被分发给 AI 等消费者。完整容量限制一览限制项上限源码常量每段文本description、guidelines 等1,000 字符MAX_TEXT_LENGTH说明数组与 aliases10 项MAX_GUIDANCE_ITEMSaliases 单项最长 100allowedValues与属性示例32 项MAX_ATTRIBUTE_VALUES组件 HTML 示例3 个、每个 ≤ 4 KiBMAX_HTML_EXAMPLES/MAX_HTML_EXAMPLE_BYTES单组件 AI 元数据16 KiBMAX_COMPONENT_AI_BYTES单个 Manifest 的 AI 元数据总量128 KiBMAX_MANIFEST_AI_BYTESenforceManifestMetadataLimit按组件排序依次累加 AI 元数据体积超出 128 KiB 后丢弃后续组件的 AI 信息schema 组件仍保留。测试用例keeps schema components when component or Manifest AI limits are exceeded验证了这一行为。七、读取运行期 ManifestAI 插件如何接入Editor 创建完成后即可同步生成最终快照import { createHaloEditorManifest, type HaloEditorManifest, type VueEditor, } from halo-dev/richtext-editor; function editorManifest(editor: VueEditor): HaloEditorManifest { return createHaloEditorManifest(editor); }Manifest 的结构HaloEditorManifesttypes.ts包含三部分interface HaloEditorManifest { version: 1; // 固定版本号 signature: string; // 稳定的内容签名 components: HaloEditorComponent[]; // 全部 Node 与 Mark 的规范化描述 }组件描述的内容nodeComponent/markComponent从最终 schema 中提取见 manifest.tsNode 组件content表达式、group、inline、atom、leaf、code、whitespace、selectable、draggable、defining、isolatingMark 组件excludes、inclusive、spanning公共部分attributes名称、是否必填、默认值按名称排序、htmlParseRulestag/style/priority、以及可选的ai与structure。关于signature的稳定性签名通过object-hash对{ version, components }生成。测试用例changes the signature only when normalized schema or metadata changes验证了关键性质无关的纯 Extension 注册顺序变化不影响 signature因为最终组件只取 schema 中真实存在的 node/mark组件描述或元数据变化signature 必然变化同一配置重复生成signature 完全一致normalizes figureCaption structure and produces stable signatures用例。这意味着 AI 插件可以缓存 Manifest 并基于 signature 做增量判断知道编辑器能力是否发生变化。消费者的职责边界文档结尾明确指出使用原则AI 插件可以把 Manifest 加入模型上下文context其他消费者可以用它了解当前编辑器实际注册的组件消费者自行决定是否根据其中的建议进行额外校验——元数据是建议而非强制约束。八、从测试看行为保证manifest.spec.ts的关键断言仓库在 manifest.spec.ts 中对上述机制做了系统验证值得在接入时参考测试用例验证点使用最终配置的 schema 并包含嵌套组件addExtensions嵌套注册的组件会进入 Manifestoptions 配置如tone: warm会反映到属性的defaultValue与元数据描述父钩子只执行一次、子数组替换父数组继承链合并语义按 priority 与注册顺序合并贡献冲突裁决规则只保留最终重复身份同一名称多个扩展时最后生效的扩展胜出失败软着陆未知字段、超长数组、缺失属性、无效父节点、抛错钩子均被优雅处理内置组件全覆盖默认ExtensionsKit下每个组件都有显式 AI 声明且无告警内置组件可生成代表性变体figure的 img/video/audio、heading的 h1-h3、columns的 cols2/3 等示例均包含在元数据中最后两条用例对插件作者尤为重要Halo 内置的所有默认组件都有完整的 AI 元数据声明插件扩展这些组件时会自动继承其描述无需重复编写。九、实战接入清单自定义 Node/Mark在Node.create/Mark.create中实现addHaloEditorMetadata()至少提供ai.description如需 AI 生成补充generation与examples。扩展内置组件.extend()后只写局部补丁继承链自动合并数组字段记得用替换语义设计。全局属性说明用 Plain Extension contributions向目标组件注入attributeGuidance注意目标必须真实存在。结构约束为有明确父子关系的组件声明structure.allowedParentsminPerParent/maxPerParent。敏感组件返回{ ai: false }阻止 AI 主动生成schema 信息仍保留。读取与消费Editor 就绪后调用createHaloEditorManifest(editor)把signature与components交给 AI 上下文或能力探测逻辑。容量自检遵循第六节限制表避免元数据被截断或丢弃利用开发环境 console 告警前缀[halo-editor-metadata]定位无效声明。结语Halo 编辑器运行期元数据机制本质上是把编辑器最终长什么样、每个组件怎么用变成一份结构化、可校验、带签名、AI 可消费的声明式契约。通过addHaloEditorMetadata钩子与createHaloEditorManifest生成器插件作者可以低成本地为 AI 提供高质量的组件使用指导而 Halo 内置组件与完整的测试套件则保证了这份契约的稳定性与兼容性。对于希望在 Halo 生态中构建 AI 写作、内容生成等能力的开发者掌握本文所述的声明语法、合并规则、贡献机制与容量约束是让 AI 正确理解并使用编辑器组件的第一步。【免费下载链接】haloHalo 是一款强大易用的开源建站工具从个人博客、知识库到企业官网、在线商城Halo 都能助您轻松实现一站式满足您的多样化建站需求。项目地址: https://gitcode.com/GitHub_Trending/ha/halo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考