Kilo 开源代码仓库中的 Effect Schema 迁移指南:以 Schema 为领域模型的单一事实来源

发布时间:2026/9/13 17:16:35
Kilo 开源代码仓库中的 Effect Schema 迁移指南:以 Schema 为领域模型的单一事实来源 Kilo 开源代码仓库中的 Effect Schema 迁移指南以 Schema 为领域模型的单一事实来源【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode导读本文基于 Kilo 开源仓库中 packages/opencode/specs/effect/schema.md 这份规范文档系统讲解在packages/opencode中如何以 Effect Schema 作为领域模型、DTO、ID、输入输出与类型化错误的单一事实来源source of truth。读完本文你将掌握Schema.Class、Schema.Struct、Schema.TaggedErrorClass与 branded ID 的首选形态理解为何要避免重新引入通用的 Schema-to-Zod 桥接层并能够按照规范的迁移顺序与 PR 检查清单把遗留的混合 schema 逐步迁移到统一的 Effect Schema 形态。一、为什么把 Effect Schema 作为单一事实来源在现代 Effect 生态中Schema 不仅是运行时校验器更是类型系统的权威来源同一个 Schema 声明既可以推导出静态 TypeScript 类型也可以生成 JSON Schema 文档、用于 AI 对象生成、承载边界校验与序列化。Kilo 仓库的规范文档开宗明义地规定Use Effect Schema as the source of truth for domain models, DTOs, IDs, inputs, outputs, and typed errors.即领域模型、DTO、ID、输入、输出与类型化错误一律以 Effect Schema 为唯一权威定义。与之配套的还有两份关联规范packages/opencode/specs/effect/guide.mdEffect 编码风格总纲与 packages/opencode/specs/effect/migration.md迁移模式速查三者共同构成packages/opencode面向 Effect 重构的规范体系。需要注意这份文档是指导性规范而非存量清单This is guidance, not an inventory。文档明确要求不要用它来跟踪哪些 schema 模块已迁移完成动手迁移前应先用git grep核实代码当前状态。二、首选形态Preferred Shapes规范给出了四种核心形态的选择标准按对象身份与作用域划分。2.1 对外导出、具有明确领域身份的数据对象Schema.Class对于需要导出、具备清晰领域身份的数据对象使用Schema.Class并以模块名.类型名的命名约定标注标识export class Info extends Schema.ClassInfo(Foo.Info)({ id: FooID, name: Schema.String, enabled: Schema.Boolean, }) {}仓库中的真实实现可印证这一形态。以 packages/opencode/src/account/schema.ts 为例账户领域的Info、Org、Login均采用Schema.Classexport class Info extends Schema.ClassInfo(Account)({ id: AccountID, email: Schema.String, url: Schema.String, active_org_id: Schema.NullOr(OrgID), }) {}Schema.Class的字段直接引用领域内的 branded ID如AccountID并可以组合其他 Schema 构造器如Schema.NullOr表达可空字段这正是单一事实来源在字段层面的体现——Info不再自行重述id的格式而是引用共享的 ID schema。2.2 局部形状与简单嵌套对象Schema.Struct对于仅在函数/模块内部使用的局部形状local shapes与简单嵌套对象使用Schema.Struct无需导出const Payload Schema.Struct({ id: FooID, value: Schema.String, })2.3 预期领域错误Schema.TaggedErrorClass对于可预期的领域错误使用Schema.TaggedErrorClass。tag 命名同样遵循模块名/域名.错误名风格export class NotFoundError extends Schema.TaggedErrorClassNotFoundError()(FooNotFoundError, { id: FooID, }) {}仓库中 packages/opencode/src/account/schema.ts 定义了三个错误类并聚合成领域级错误联合export class AccountRepoError extends Schema.TaggedErrorClassAccountRepoError()(AccountRepoError, { message: Schema.String, cause: Schema.optional(Schema.Defect()), }) {} export class AccountServiceError extends Schema.TaggedErrorClassAccountServiceError()(AccountServiceError, { message: Schema.String, cause: Schema.optional(Schema.Defect()), }) {} export class AccountTransportError extends Schema.TaggedErrorClassAccountTransportError()(AccountTransportError, { method: Schema.String, url: Schema.String, description: Schema.optional(Schema.String), cause: Schema.optional(Schema.Defect()), }) { static fromHttpClientError(error: HttpClientError.TransportError): AccountTransportError { return new AccountTransportError({ method: error.request.method, url: error.request.url, description: error.description, cause: error.cause, }) } // 自定义 message 访问器为网络错误生成可读提示 } export type AccountError AccountRepoError | AccountServiceError | AccountTransportError这段实现还展示了几个值得借鉴的细节未知的底层原因字段使用Schema.optional(Schema.Defect())承载符合 guide 中用Schema.Defect表示未知 cause 字段的约定见 packages/opencode/specs/effect/guide.md错误类可以附带静态工厂方法如fromHttpClientError把外部错误翻译为领域错误的逻辑内聚在 schema 模块中领域模块对外统一导出AccountError联合类型服务接口的错误通道引用它例如Effect.EffectInfo, AccountError。类似的还有 packages/opencode/src/acp/error.ts 中一整套 ACP 错误SessionNotFoundError、InvalidConfigOptionError、AuthRequiredError等以及 packages/opencode/src/auth/index.ts 的AuthError。2.4 单值领域标识符branded ID对于单一值的领域标识符ID、token、code 等使用branded schema-backed ID——即用Schema.brand打上领域标记的字符串/数字 schemaexport const AccountID Schema.String.pipe(Schema.brand(AccountID)) export type AccountID Schema.Schema.Typetypeof AccountIDpackages/opencode/src/account/schema.ts 中AccountID、OrgID、AccessToken、RefreshToken、DeviceCode、UserCode全部采用这一模式packages/opencode/src/session/schema.ts 则展示了 branded ID 与前置校验、静态方法组合的进阶用法export const MessageID Schema.String.check(Schema.isStartsWith(msg)).pipe( Schema.brand(MessageID), statics((s) ({ ascending: (id?: string) s.make(Identifier.ascending(message, id)), })), )MessageID先通过Schema.String.check(Schema.isStartsWith(msg))校验前缀再打上MessageID品牌并附加ascending静态构造器。brand 的价值在于同一底层string在不同领域如SessionID、MessageID、PartID之间互不混淆编译器会在赋值时强制区分从而消灭字符串别名灾难。三、边界规则Boundary Rule由 Effect Schema 拥有类型规范中最关键的一条架构约束是Effect Schema should own the type. Boundaries should consume Effect Schema directly or use narrow boundary-specific helpers. Avoid reintroducing a generic Effect Schema - Zod bridge.即Effect Schema 拥有类型定义权各边界boundary应当直接消费 Effect Schema或使用窄化的、边界专用的辅助函数严禁重新引入通用的 Effect Schema → Zod 桥接层。文档同时列出了当前有意保留的边界intentional boundaries作为例外清单边界保留的形态原因公开插件工具仍通过tool.schema z暴露 Zod插件生态的既有契约工具参数使用工具专用的 JSON Schema helper工具参数有自身约束体系公开配置与 TUI schema通过 schema 脚本生成需要产出稳定的公共 JSON SchemaAI SDK 对象生成使用 Standard Schema / JSON Schema helper对接 AI 生成管线规范补充如果某处必须暂时保留 Zod必须留下一段简短注释说明该边界的成因或兼容性理由leave a short note explaining the boundary or compatibility reason让后来者知道这是有意为之而非遗漏。仓库中的 schema 脚本 packages/opencode/script/schema.ts 正是公开配置与 TUI schema 走 schema 脚本这一边界的实现它通过Schema.toJsonSchemaDocument将 Effect Schema 转换为 JSON Schema 文档再进行归一化处理折叠anyOf/单元素allOf、为无上限的integer补上Number.MAX_SAFE_INTEGER、恢复model/small_model字段的$ref引用最终产出带$schema头、支持注释与尾逗号的 JSON Schema——典型用法形如const document Schema.toJsonSchemaDocument(schema) const normalized normalize({ $schema: https://json-schema.org/draft/2020-12/schema, ...document.schema, $defs: document.definitions, })这意味着同一份 Effect Schema 既是运行时校验器与 TypeScript 类型源又是公开 JSON Schema 文档的生成输入单一事实来源由此落到实处。四、细化约束Refinements复用命名细化而非重复拼写当需要表达数值范围等约束时规范要求复用命名细化named refinements而不是在每个使用点重复拼写约束条件const PositiveInt Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0)) const NonNegativeInt Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))仓库源码中的实际用法验证了这一约定。例如 packages/opencode/src/kilocode/server/httpapi/groups/kilocode.tsrevision: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),packages/opencode/src/kilocode/board/store.ts 则更进一步把命名细化提升为局部常量以便复用const Integer Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))规范还给出了两条偏好原则命名收益原则当领域命名的叶子 schema 能改善调用方可读性或错误消息时优先使用领域命名如AccountID显然优于裸Schema.String克制原则不要为了新奇感而随意添加 brandAvoid adding brands purely for novelty——brand 是身份语义工具不是装饰品。五、迁移顺序Migration Order对于仍存在混合 schema部分 Zod、部分 Effect Schema的领域规范给出了五步迁移顺序按依赖方向从底层叶子向边界推进共享叶子模型与 branded ID——先迁移被广泛引用的基础类型ID、token、枚举值等它们是整个领域的地基导出的Info、Input、Output与事件 payload 类型——即对外传输的数据形状预期的领域错误——把 Zod 的z.object({...})错误模型迁移为Schema.TaggedErrorClass接入错误通道服务内部的局部模型——只在单个服务内使用的Schema.Struct形状HTTP / 工具 / AI 边界的校验器——最外层、最容易受兼容性约束牵制的部分放在最后。迁移过程中有一条硬性约束Keep public wire shapes stable unless the PR is explicitly a breaking API change.除非 PR 本身就是一次显式的破坏性 API 变更否则必须保持公开线上数据形状wire shapes稳定。这与 packages/opencode/specs/effect/guide.md 中保留遗留公开线上形状如{ name, data }直到刻意做破坏性变更的约定一致——迁移是内部形态的统一不是对外契约的改版。配套的 packages/opencode/specs/effect/migration.md 还为迁移中的测试划定了方向触达的测试应逐步迁向testEffect(...)、it.effect/it.live/it.instance、显式 layer 变体与确定性等待避免在 layer 构建后修改process.env或全局 flag。六、PR 检查清单Checklist For A PR每个涉及 schema 迁移的 PR 在合入前需要逐项核对以下清单每个被迁移类型有且仅有一个schema 事实来源one schema source of truth for each migrated type残留的 Zod 是有意的边界选择且按规范留有注释说明成因公开的 JSON / OpenAPI 输出未变化或为有意更新派生 helper窄化且边界专用narrow and boundary-specific没有滑向通用桥接测试断言行为而不是重复 schema 实现细节tests assert behavior, not duplicated schema implementation details。其中最后一条值得展开测试的价值在于验证 schema 在真实调用链中的行为解码成功、校验失败路径、错误 tag 匹配等而不是把 schema 定义原样抄进断言里——后者只会让测试与实现耦合schema 一改测试就失去意义。七、从规范到落地Kilo 仓库中的迁移实证packages/opencode已经落地了大量符合上述规范的 schema 代码可作为迁移的活样板branded ID Class TaggedErrorClass 三位一体packages/opencode/src/account/schema.ts 完整覆盖了 ID 品牌、Info/Org/Login数据类、三类领域错误及其联合类型还包含PollResult Schema.Union([...])的多态结果建模带校验与静态方法的 branded IDpackages/opencode/src/session/schema.ts 的MessageID/PartID展示了checkbrandstatics的组合写法协议层的品牌 IDpackages/opencode/src/kilocode/agent-manager/protocol.ts 与 packages/opencode/src/kilocode/notebook/protocol.ts 中的RequestID通过Schema.brand(...).annotate(...)携带额外注解JSON Schema 输出管线packages/opencode/script/schema.ts 是公开配置与 TUI schema 经 schema 脚本生成边界的实现本体。若要动手迁移规范的验证命令需要在packages/opencode目录内执行仓库根目录对测试有守卫禁止在根目录运行测试bun run typecheck bun run test -- path/to/test.ts结语packages/opencode/specs/effect/schema.md本质上是一份工程纪律文档它通过明确的首选形态Class / Struct / TaggedErrorClass / branded ID、严格的边界规则Effect Schema 拥有类型不重建通用桥接、可复用的命名细化、由内向外的迁移顺序与可执行的 PR 清单把单一事实来源从口号变成可评审、可验证的落地标准。对希望深入 Effect Schema 实践的读者建议将本文与 guide.md服务形态与错误通道规范、migration.md迁移模式速查以及 account/schema.ts完整范例对照阅读即可获得从规范到实现的完整闭环。【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考