深色模式
Demo 8 · 组装自己的 Profile
目标:把 Mock 适配器的实验从"每次敲
--patch"沉淀成"一个名字":dsh --profile demo8 "任务"。理解 profile 目录结构、用户层 cordis.patch.yml 与分层覆盖语义。对应原理篇 08。
运行
sh
cd demos/08-profile
DSH_HOME="$PWD/.dsh-home" npx dsh --profile demo8 "你好,自定义 profile"输出:
text
(MockAdapter,无需网络与 API Key)收到你的消息:"你好,自定义 profile"。当前 provider=mock,model=mock-1。没有 --patch,没有多余 flag。你的产品形态有了自己的名字。
profile 目录长什么样
text
.dsh-home/profiles/demo8/
├── package.json # ① dsh.profile 清单:有序 bundle 列表
└── cordis.patch.yml # ② 用户覆盖层package.json:
json
{
"name": "demo8",
"private": true,
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"]
}
}
}cordis.patch.yml:
yaml
# demo8 profile 的用户层
- id: agent-default-model
config:
provider: mock
model: mock-1
- insert:
- id: mock-adapter
name: ../../../plugins/mock-adapter.ts与 Demo 5 的 overlay 内容几乎一样,区别只在位置:它住在 profile 目录里,是分层组合中稳定的一层(bundle 层之上、home 层与 --patch 之下),而不是每次命令行携带的临时覆盖。
分层验证:dump 组合树
sh
DSH_HOME="$PWD/.dsh-home" npx dsh --profile demo8 --dump-config | tail -8yaml
# == /…/.dsh-home/profiles/demo8/cordis.patch.yml
- id: agent-default-model
config:
provider: mock
model: mock-1
- id: mock-adapter
name: ../../../plugins/mock-adapter.ts--dump-config 的注释行告诉你每一段配置来自哪一层——组合结果可溯源。你可以再用 --patch 叠加一个 overlay 观察"后写覆盖":
sh
# 临时把 model 改成 mock-2:overlay 层高于 profile 层
DSH_HOME="$PWD/.dsh-home" npx dsh --profile demo8 --dump-config --patch /dev/stdin <<'EOF' | tail -6
- id: agent-default-model
config: { provider: mock, model: mock-2 }
EOF覆盖语义:整行替换
这是本章最重要的坑。agent-default-model 行的原始 config 是:
yaml
config:
provider: deepseek-official
model: deepseek-v4-flash如果你的覆盖层只写 config: { model: mock-1 },provider 字段就丢了——整行替换,没有深合并。所以 demo 的 patch 里 provider 和 model 都复述了。这就是原理篇 8.3说的已知限制:"A user patch replaces the whole matched config."
完整分层回顾
你的 mock 改造在第 3 层。如果想让它对所有 profile 生效,移到第 4 层($DSH_HOME/cordis.patch.yml);想临时实验,用第 5 层(--patch)。同一份 patch 内容,放在不同层,影响范围不同。
亲手做实验
实验 1:home 层
把 mock 改造复制到 .dsh-home/cordis.patch.yml,然后跑 --profile headless "你好"——headless 也被 mock 接管了。体会 home 层的"机器级"影响。做完记得删掉。
实验 2:整行替换的坑
把 profile 的 patch 改成只写 config: { model: mock-1 },--dump-config 看 provider 变成了什么。
实验 3:web 版 profile
把 bundles 列表里的 @deepseek-ai/dsh-headless 换成 @deepseek-ai/dsh-web-app,npx dsh --profile demo8-web 启动 Web UI。在浏览器里体验同一条 mock 链路(启动后到设置页把模型切到 mock)。这是"产品形态 = 配置"最直接的体验。
常见错误
| 现象 | 原因 |
|---|---|
no profile named xxx | 目录不存在或 package.json 缺 dsh.profile.bundles |
| 覆盖后行为异常 | 整行替换丢了字段(复述保留字段) |
failed to import loader entry | 插件路径层级算错(baseUrl 是 profile 目录) |
阶段小结
到这里你完成了全部 8 个 Demo:Cordis 原语 → LLM 接缝 → 真实链路 → 工具 → 拦截 → 产品化组合。下一部分教学版项目把这条认知路线反过来走一遍:从零亲手实现一个 mini-harness——那时你写的每一行代码都能在 dsh 里找到对应物。