Capability Seam:服务定义、提供方与消费方
插件挂好了、日志也记上了,主循环要跑命令、读文件、调模型时,仍会碰上硬绑定:工具里直接 spawn('bash', ...),产品要求改远程沙箱或 Windows PowerShell 时,所有调用点一起改。dsh 用 Capability Seam(能力 seam)把「能力长什么样」和「谁来实现、谁来用」拆开:服务定义、服务提供方、消费方三者组成一条可替换接缝。
seam 是什么
seam 本意是接缝。在 dsh 里指一项可替换能力,由 Service Definition(服务定义)、Service Provider(服务提供方)、Consumer(消费方)共同构成。一个包可以兼任多个角色,但只写接口或只写实现都不算完整 seam;加一项能力时三者要一并设计。
类比电源插座:插座形状是服务定义,火电或水电是提供方,台灯是消费方。台灯只关心能供电,不关心电从哪来。换提供方等于换发电来源,插座形状和台灯都不用改。
以 ctx.shell / ShellExecutor 为例
服务定义在 packages/shell/shell/,暴露抽象类 ShellExecutor:resolve 把请求变成完全规格,run 跑前台命令,start 启后台进程。定义里没有本机或沙箱细节。
1 | abstract class ShellExecutor extends Service { |
提供方可以是 dsh-bash-local(本机 bash -c)、dsh-bash-sandbox(本机但沙箱受限)、dsh-pwsh-local(PowerShell)等,都实现同一 ctx.shell。消费方通常是面向模型的工具,例如 bash 工具只认接口。模型侧看到的工具名与 schema 可以保持稳定,换的是执行落点。
1 | const result = await ctx.shell.run( |
Profile 里把执行器那一行从 @deepseek-ai/dsh-bash-local 改成 @deepseek-ai/dsh-bash-sandbox,工具 schema 与主循环代码都不用动。一次组合只挂一个提供方,换哪一个由配置决定。
resolve() 是唯一填默认值和上限的地方;run / start 只接受完全解析的 ShellExecSpec,实现里不藏 fallback。默认值和上限集中在一处,排查「超时从哪来的」不用翻多个提供方。SHELL_SETTINGS_NAMESPACE 也定义在服务定义包:命名空间属于能力本身,一个组合里只有一个 ctx.shell 提供方,共享命名空间不会冲突,跨平台设置文档在两边都能解析。
调用时内部怎么走
消费方调用 ctx.shell.resolve / run 时,抽象服务委派给已挂载的提供方。以 sandbox 为例,提供方可能先经 ctx.sandbox.confine(argv) 收紧参数,再真正执行,最后返回统一的 ShellRunResult(退出码与输出)。消费方从头到尾只看到接口,提供方身份对它透明。
执行世界一起搬:fs 与 subprocess
文件系统 ctx.fs 与子进程 ctx.subprocess 若都指向同一远程沙箱,Bash、PTY、LSP 会跟着走:它们底层都经这两个 seam 访问文件与进程,不必为每个工具单独 fork 一份远程适配。换提供方等于换整片执行世界的落点。这比「每个工具各自判断本机还是远程」省一整层分支,也少一处配置漂移。
能力形态可以不同:directoryPicker
ctx.directoryPicker 暴露可辨识的能力对象,不强行统一成一组固定方法。kind: 'native' 弹操作系统对话框,适合本机用户;kind: 'browse' 提供列目录、建目录等原语,给远程客户端做应用内逐层浏览(远程客户端够不着宿主机 OS 对话框)。消费方读 capability().kind 再分支:
1 | const cap = ctx.directoryPicker.capability() |
联合类型可合并扩展,新的交互形态可以声明合并进来。遇到未知 kind,约定是隐藏选择入口,避免把不支持的交互硬推给用户。这里 seam 表达的是能力形态本身可差异:不同后端交互方式不同,接口就暴露可辨识能力;强行把所有后端塞进同一组方法签名,反而会把远程场景写歪。
合起来:调用方认服务定义,平台细节关在提供方里,换一行配置就能换本机 / 沙箱 / PowerShell,或换目录选择的交互形态。插件树(Profile / Bundle)决定挂谁,Session Log 记录发生了什么,Capability Seam 决定这件事找谁做。读代码时先找服务定义包里的抽象类或能力对象,再顺着 Profile 看挂了哪个提供方,最后才看工具或 UI 消费方怎么调用。
Capability Seam:服务定义、提供方与消费方