no-mistakes opencode适配器:SSE流、结构化输出与重试门
【免费下载链接】no-mistakesgit push no-mistakes项目地址: https://gitcode.com/GitHub_Trending/no/no-mistakes
no-mistakes是一款git push no-mistakes即触发 AI 校验流水线的本地 git 门控工具,而它的opencode 适配器负责与 OpenCode 智能体协作:启动常驻 HTTP 服务、通过SSE 流实时接收响应、以json_schema请求结构化输出,并用一套谨慎的重试门决定何时重试、何时停下来等人做决定。本文带你快速看懂这套适配器的设计思路。
一、背景:no-mistakes 需要连接多种智能体
no-mistakes 的核心玩法是:把分支推给门控远程no-mistakes(而不是直接推给origin),它会在一次性的 worktree 中运行"审查 → 测试 → 文档 → Lint → 推送 → PR → CI"流水线,全部绿灯后才放行。
流水线里真正干活的,是多种可替换的编码智能体:claude、codex、opencode、grok、pi、copilot等。其中 OpenCode 比较特别——它不像别的 CLI 那样"跑完就退出",而是以常驻 HTTP 服务 + 事件流的方式工作。适配器源码位于 internal/agent/opencode.go,官方文档在 agents 指南中有专门说明。
二、适配器工作原理:三步走
1. 启动并复用常驻 HTTP 服务器
首次调用时,适配器执行opencode serve,绑定到本机127.0.0.1的随机空闲端口(见 opencode_http.go),并轮询/global/health确认就绪。之后的每次调用都会复用同一个服务器,避免反复冷启动。
一个贴心的细节:如果复用中的服务器拒绝连接,适配器会把它丢弃、换新服务器再试(recoverTransientRetry),而不是让整个任务失败。
💡 模型与推理强度不放在命令行参数里,而是随每次会话消息发送。因为
opencode serve遇到未知参数会直接退出,命令行"钉模型"会拖垮整个服务器。
2. 创建会话并发出消息
每次任务都会新建一个会话(目录 + 全量放行权限),然后 POST 到/session/{id}/message发出提示词,最后用defer确保会话被中止和删除,不留垃圾。
3. 并行消费 SSE 事件流
发消息的同时,适配器订阅/global/event的SSE 流(opencode_stream.go),边发边收,直到收到session.idle为止。主要事件类型如下:
| SSE 事件 | 适配器的处理 |
|---|---|
message.part.delta | 增量拼接文本,实时回传给 TUI 展示 |
message.part.updated | 更新完整文本块,识别工具调用,累计 token 用量 |
message.updated | 区分用户/助手消息,记录缓存读写 token |
session.idle | 本回合结束,停止消费 |
session.error | 识别"思考模式冲突",触发回退(见下文) |
解析时严格按sessionID过滤,只处理自己会话的事件;用户自己消息产生的文本块会被过滤掉,避免把提示词当成回答。通用 SSE 解析器(sse.go)支持多行 data、\r\n分隔,单行缓冲最大 256MB,足够应对大事件。
三、结构化输出:json_schema 与优雅回退
流水线的每个步骤(审查结论、CI 修复判断等)都需要机器可读的 JSON 结果。适配器在消息体里声明:
"format": { "type": "json_schema", "schema": {...}, "retryCount": 2 }OpenCode 会把它实现为一次"必须调用"的 StructuredOutput 工具,响应中的structured字段就是最终结果,直接交给流水线使用。
但有个坑:部分开启"思考模式(thinking)"的模型会拒绝"强制工具调用 + 思考"的组合,直接报错。适配器用正则识别这类冲突文本(thinkingToolChoiceConflictPatterns),一旦命中就触发回退门:
- 新开一个干净会话,去掉原生
format,把 schema 直接写进提示词("只回复符合该 schema 的 JSON"); - 拿到纯文本回答后,再用同一个 schema 做 JSON 解析与校验(
finalizeTextResult)。
这个回退不是无条件的——如果失败的回合已经调用过工具(比如已经改过文件、提过 commit),重放提示词会造成二次副作用,适配器会拒绝回退并如实报错,把决定权交给人。
四、重试门:什么时候重试,什么时候停手
这是 opencode 适配器最讲究的部分(opencode_failure.go + retry.go)。
重试循环:瞬时故障最多重试若干次,采用指数退避(1s → 4s → 16s…,带 ±25% 抖动),且尊重取消信号。
判定"能否重试"的三道锁:
- 以 opencode 自己的
isRetryable为准。它报"不可重试"(如 400 请求非法),就不再走通用的关键字猜测——不能因为错误文本里恰好出现了 "rate limit" 字样就盲目重发。 - 工具证据三态门。重试是在全新会话里重放整个提示词,没有记忆。如果失败回合已经跑过工具(
toolInvoked证据),或无法证明它没跑过工具("没看到"不等于"没发生"),重试被扣留并明确标注not retried: the failed turn already ran tools。 - 流中断的取证流程。SSE 流中途断开时,适配器会中止会话、限期等待消息响应落地,用"响应中列出的 parts 里有没有工具调用"来回答"这回合动过手没有",而不是凭猜测放行重试。
🛡️ 设计哲学一句话:沉默不是证据——无法验证"没跑过工具"的回合,一律失败关闭(fail-closed),宁可停下问人,也不静默重放可能产生副作用的操作。
五、想深入阅读?看这几个文件
- 适配器主流程与结构化输出回退:internal/agent/opencode.go
- SSE 事件解析与 token 用量累计:internal/agent/opencode_stream.go
- HTTP 客户端、会话管理与消息体构造:internal/agent/opencode_http.go
- 失败分类、工具证据与重试门:internal/agent/opencode_failure.go
- 通用重试循环与退避策略:internal/agent/retry.go
- 项目总览:README.md
六、小结
no-mistakes 的 opencode 适配器用三块拼图接住了 OpenCode 这种"服务器 + 事件流"形态的智能体:
- SSE 流:并行收发,实时展示回答进度,精确累计 token 与缓存用量;
- 结构化输出:优先
json_schema原生格式,遇思考模式冲突自动降级为"提示词内嵌 schema + 事后校验"; - 重试门:以 opencode 自身的可重试性裁决为准,叠加"工具副作用证据"检查,该重试时指数退避重试,不该重放时干净利落地停手。
这正是 no-mistakes 的理念缩影——自动化负责跑流水线,人负责在关键处拍板。
【免费下载链接】no-mistakesgit push no-mistakes项目地址: https://gitcode.com/GitHub_Trending/no/no-mistakes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考