waterfall:用 next() 做拦截、包装与短路
沿着一条真实的 Cordis waterfall 链路,理解监听器如何委托下游、包装结果,或在拥有决策权时直接短路。
记住一个规则:观察者必须调用 next();只有真正拥有决策权的监听器,才应该不调用它并直接返回。
waterfall 不是普通广播。每个监听器拿到参数和一个 continuation,可以把下游当作默认行为来委托,也可以在链条外层包装下游结果。
01 / 一条链像一组嵌套函数
import type { Context } from '@deepseek-ai/cordis'
declare module '@deepseek-ai/cordis' {
interface Events {
'demo/transform'(input: string, next: () => Promise<string>): Promise<string>
}
}
export async function apply(ctx: Context) {
ctx.on('demo/transform', async (input, next) => {
const downstream = await next()
return downstream.toUpperCase()
})
ctx.on('demo/transform', async (input, next) => {
if (input.includes('blocked')) return '** blocked **'
return next()
})
const input = 'hello'
const result = await ctx.waterfall('demo/transform', input, async () => input)
console.log(result)
}最内层函数是默认行为。监听器按注册顺序包在它外面,调用 next() 才会继续走向下游。
02 / 包装和短路是两种不同意图
- 委托:监听器不决定结果,调用
next()。 - 包装:监听器等待
next()返回,再转换结果,例如统一大写或追加诊断信息。 - 短路:监听器拥有决策权,不调用
next(),直接返回替代结果。
示例中,hello 会经过默认逻辑,再被外层转换成 HELLO;blocked words 在策略监听器处直接得到 ** blocked **,默认逻辑根本不会运行。
03 / 忘记 next() 会悄悄吞掉默认行为
ctx.on('agent/request', async (request, next) => {
console.log('observed request')
return next()
})如果这个只负责记录的监听器没有调用 next(),它的返回值就会结束整个 waterfall。下游模型请求、工具执行或默认策略可能因此完全不发生,而且表面上不像一次异常。
所以“是否调用 next”不是风格偏好,而是监听器的责任声明:不调用意味着否决或替代;调用意味着继续委托。
04 / DSH 中的两个决策点
DeepSeek Harness 使用 waterfall 连接可替换策略:
agent/request允许插件替换模型调用配置,或在请求进入 provider 前包装它。approval/request允许策略插件代替用户作答,也允许普通监听器继续交给下游。
具体服务仍可定义自己的异常边界:例如 approval/request 会把答复者抛出的异常规范化为 unavailable 结果;这不是所有 waterfall 事件的通用行为。
这些扩展点不需要把策略写进 agent loop。插件只挂到事件上,返回结果的责任由 waterfall 语义明确表达。
05 / 使用前先问三个问题
- 这个事件的默认行为是什么?把它作为最内层
next函数写出来。 - 监听器只是观察,还是有权替换决策?观察者必须委托。
- 返回值是否需要包装?如果需要,在
await next()之后转换,而不是绕过下游。
把这三个问题写清楚,waterfall 就是可读的中间件链,而不是一串难以追踪的回调。
下一步
更多文章
Cordis:五个核心概念,读懂一棵插件树
用 Plugin、Context、inject、Events 与 Effect 五个概念,建立 Cordis 运行时的最小心智模型。
生命周期与 effect:让 Cordis 注册可以回卷
跟随 Fiber 从 PENDING 到 DISPOSED,理解 effect、disposer 与 HMR 如何共同管理插件副作用。
动手:从 hello.ts 写出你的第一个 Cordis 插件
用一个函数插件和一份 cordis.yml,跑通 Loader、Context 与 Fiber 的最短路径。
邮件列表
加入我们的社区
订阅邮件列表,及时获取最新消息和更新