第 3 卷
解耦的手法不止一种(拆函数、拆包、手写装配都行),dsh 的选择是:借 Cordis 现成的 Service/inject 机制,把可替换能力规范化成三个角色,让框架自动搞定切换、暂停、重
启。这套结构叫 seam。注意:seam 指的是这三个角色的整体,不是其中某一个角色。

复制代码
| 1 | // src/ch05/seam-demo.ts(节选,完整代码见该文件) |
|---|---|
| 2 | import{Context,Service}from'@deepseek-ai/cordis' |
| 3 | import{ defineTool }from'@deepseek-ai/dsh-tools' |
4 5// ── 角色一:Definition(契约)──
6abstract class EnvProbe extends Service { 7constructor(ctx: Context) {
8super(ctx, 'envProbe')
9} 10abstract probe(): Promise<{ platform: string; node: string }>
11} 12
13// ── 角色二:Provider(实现,可替换)── 14class LocalEnvProbe extends EnvProbe {
| 15 | asyncprobe() {return{platform: process.platform,node: process.version} } |
|---|---|
| 16 | } |
| 17 | classFakeEnvProbeextendsEnvProbe{ |
| 18 | asyncprobe() {return{platform:'<fake:platform>',node:'<fake:node>'} } |
| 19 | } |
26ctx.tools.register(defineTool({ 27name: 'env_probe',
| 28 | // ... output与parameters写法同上一节,此处略 |
|---|---|
| 29 | asyncexecute() { |
| 30 | returnctx.envProbe.probe() //只依赖契约,不关心具体实现 |
| 31 | }, |
| 32 | })) |
| 33 | }, |
| 34 }</fake:node></fake:platform> |
为什么非要切三个角色?
关键在于中间那层契约:Consumer 的 inject 写的是契约名(envProbe),不是某个具体类,它压根不知道、也不关心背后是 local 还是 fake。于是 Provider 可以放心换成任意实
现,Consumer 一行都不用改。换的过程中,框架先把失去依赖的 Consumer 自动暂停,等新的 Provider 就位再自动重启,绝不会出现“调用到一个不存在的东⻄”的中间态。
跑 npm run start:ch05:seam 看这个魔术(execute 返回 { isError, content, value },下面只展示 value 的变化):
复制代码 1--- 提供方 = local ---
| 2 | { value: { platform:'darwin', node:'v24.4.1'} } |
|---|---|
| 3 | ---卸载Provider后,剩余工具--- |
| 4 | [] ←只卸了Provider,工具一行没动,它自己消失了 |
| 5 | ---换上fake后,剩余工具--- |
| 6 | ["env_probe"] ←工具自动回来 |
| 7 | { value: { platform:'<fake:platform>', node:'<fake:node>'} }</fake:node></fake |
这套三角色不是玩具,dsh 的 shell seam 真实存在,写进声明式配置(cordis.yml)就是三行,换实现只改 Provider 那一行:
回到 EnvProbe 那几行:三角色其实完全落在基础篇讲过的 Cordis Service 机制上—— EnvProbe 通过 super(ctx, 'envProbe') 注册一个名为 envProbe 的服务;
LocalEnvProbe / FakeEnvProbe 继承它,实例化时就把各自实现挂到这个名字下;
Consumer 用 inject: ['envProbe'] 只按名字取用,压根不认识具体类。也正因如此, Definition 必须是 Service 子类(抽象类),不能是 interface,因为 interface 没有
super,注册不了服务。
最后需要注意:不是所有场景都适合用 seam,我们可以参考下面的表格来判断:

本讲小结
今天这一讲,我们跨进了 dsh,回答了“dsh 里 Agent 动手做事的能力从哪来”这个问题。
我们用真实 dsh 写了一个 greet 工具并跑通全链路,看清了三件事:
- 模型只看得见 name/description/parameters 三样;
- 返回值要拆成“给程序的值 + 给模型的呈现”;