第 3 卷
图纸与实物:两棵树对照。现在树长高了,值得把两张图完整对照起来:

isolate 与 intercept
现在来到这一讲最具魔法的部分。我们来设想一个真实需求:两个租户共用一棵树,但各自需要一个私有的 shell,租户 A 用 mock,租户 B 用 b-provider,互不干扰;租户 B 还要求把
shell 的 model 强制覆盖成 intercept-model。
要让每个租户“自成世界”、各配一个私有 shell,得靠 isolate;要让租户 B“篡改”shell 的配置,得靠 intercept。两者都写在配置里。这是这一讲 cordis.yml 的最终完整版:
复制代码 1# 最终版:完整配置树
2- id: logger 3name: './plugins/logger.ts'
10config:
11provider: deepseek 12model: deepseek-chat
13 14- id: tenant-a# 租户 A 组
| 15 | name:'@deepseek-ai/cordis-plugin-group' |
|---|---|
| 16 | group:true |
| 17 | isolate: |
| 18 | shell: tenant-a #给shell服务加前缀tenant-a ——本组自成世界 |
| 19 | config: |
| 20 | -id: a-shell #组内私有的shell |
| 21 | name:'./plugins/shell.ts' |
| 22 | config: |
| 23 | provider: mock |
| 24 | model: mock-chat |
| 25 | -id: a-chat #消费shell的插件 |
| 26 | name:'./plugins/chat.ts' |
27 28- id: tenant-b# 租户 B 组
| 29 | name:'@deepseek-ai/cordis-plugin-group' |
|---|---|
| 30 | group:true |
| 31 | isolate: |
| 32 | shell: tenant-b #前缀与租户A不同——两个世界互不相通 |
| 33 | intercept: |
| 34 | shell: |
| 35 | model: intercept-model #只改本组"看到"的shell配置,b-shell实例本身不变 |
| 36 | config: |
| 37 | -id: b-shell |
| 38 | name:'./plugins/shell.ts' |
| 39 | config: |
| 40 | provider: b-provider |
| 41 | model: b-model |
| 42 | -id: b-chat |
| 43 | name:'./plugins/chat.ts' |
44 45- id: spare
46name: './plugins/chat.ts' 47disabled: true# 保留在配置里,但不挂载
跑一遍 npx tsx src/ch04/main.ts,输出:
| 4 | [shell]实例就绪:provider=mock, model=mock-chat |
|---|---|
| 5 | [shell]实例就绪:provider=b-provider, model=b-model |
| 6 | [chat]收到:[b-provider/b-model]回复:你好(我看到shell配置:{"model":"intercept-m |
| 7 | [chat]收到:[mock/mock-chat]回复:你好 |
两个 chat 的先后顺序因异步竞争每次可能不同,不影响结论。
同一份 shell.ts 代码,跑出了三个实例;两个 chat 各自注入自己组里的 shell;而且只有租户 B 的 chat 看到了被覆盖的 model。整棵插件树长这样:
复制代码 1root(new Context())
| 2 | ├── logger | → logger.ts(level=info) |
|---|---|---|
| 3 | ├── shell | → shell.ts(provider=deepseek) |
| 4 | ├── tenant-a(组)→isolate:shell: tenant-a | |
| 5 | │ ├── a-shell | → shell.ts(provider=mock) |
| 6 | │ └── a-chat | → chat.ts(注入shell) |
| 7 | ├── tenant-b(组)→isolate:shell: tenant-b +intercept: shell.model | |
| 8 | │ ├── b-shell | → shell.ts(provider=b-provider) |
| 9 | │ └── b-chat | → chat.ts(注入shell) |
| 10 | └── spare | → chat.ts(disabled,保留但不挂载) |
isolate:同名服务各住一间(底层换 symbol 键)。为什么需要它?服务名在 Cordis 里全局唯一,同名只能注册一次,直接往组里放第二个 shell 会当场报错(service "shell"
has been registered)。
解法就一句:给服务名换一个 symbol 键。配置写作 isolate: { shell: tenant-a },这个字符串叫 label,框架会把它换成不同的 symbol。标同一 label 的共享同一份实例,
于是 a-chat 拿到组内 a-shell(mock),b-chat 拿到组内 b-shell(bprovider),谁也看不到根级那个 deepseek。反过来也一样:比如根级的 spare,一旦
启用,没有 isolate 加持,看到的自然就是根级这个 deepseek,隔离是双向的。图纸还是那