2026/10/21 2:16:30

[DeepSeek Harness深度拆解-03]创建一个新的Profile提供自定义插件

[DeepSeek Harness深度拆解-03]创建一个新的Profile提供自定义插件 DeepSeek Harness深度拆解-02:Profile究竟是什么对DSH的Profile进行详细介绍我们知道表面上看Profile是为DSH程序提供了针对不同运行形态和交互方式但本质上它就是提供一组插件的配置而已。为了对Profile具有更加深刻的认识我们决定创建一个全新的Profile并利用它来提供我们自定义的插件。1. 在源码上定义插件DeepSeek Harness深度拆解-01DSH是如何跑起来的演示了如何以源码的形式启动DSH现在我们就在源码基础上创建一系列在自定义Profile中使用的插件。我们在相对于源代码根目录的./packages目录下创建一个名为demo的子目录并在其中定义了如下所示的目录结构./packages/demo/ ├── base/ │ ├── src/ │ │ └──index.ts | ├── cordis.patch.yml | ├── package.json | └── tsconfig.json ├── foo/ │ ├── src/ │ │ └──index.ts | ├── package.json | └── tsconfig.json ├── bar/ │ ├── src/ │ │ └──index.ts | ├── package.json | └── tsconfig.json ├── baz/ │ ├── src/ │ │ └──index.ts | ├── package.json | └── tsconfig.json ├── qux/ │ ├── src/ │ │ └──index.ts | ├── package.json | └── tsconfig.json └── utils/ ├── src/ │ └──index.ts ├── package.json └── tsconfig.json其中foo、bar、baz和qux用来提供四个对应的插件它们会调用定义在utils的一些公共函数。这四个插件构建的插件树最终利用base提供的补丁文件cordis.patch.yml进行构建。1.1 输出插件的路径和来源我们在utils/src/index.ts中定义了如下这个工具函数printPlunginPath来输出当前插件在插件树中的路径。如代码所示作为函数参数的Context为插件绑定的Context所谓的插件路径本质上就是Context的路径。我们从Context中提取Fiber对象进而提取出代表插件运行时的Plugin.Runtime对象并使用其名称作为节点名称。参数source是手工指定的表示插件来源的字符串。import{Context}fromdeepseek-ai/cordisexportfunctionprintPlunginPath(ctx:Context,source:string){constplugins:string[][];letfiberctx.fiberwhile(true){if(fiberfiber.parent.fiber||!fiber.runtime?.name){break;}plugins.unshift(fiber.runtime?.name??)fiberfiber.parent.fiber;}console.log(${plugins.join()}[${source}]);}如下所示的分别是package.json和tsconfig.json定义。package.json中将包名设置为jaydenai/utils,并定义了用来编译的build脚本。tsconfig.json直接在现有的基础配置tsconfig.base.json上扩展。{name:jaydenai/utils,private:true,type:module,main:lib/index.js,types:lib/index.d.ts,exports:{.:{types:./lib/index.d.ts,default:./lib/index.js}},dependencies:{deepseek-ai/cordis:workspace:*},scripts:{build:tsc -b tsconfig.json --force}}{extends:../../../tsconfig.base.json,compilerOptions:{rootDir:src,outDir:lib},include:[src],references:[{path:../../../vendor/cordis}]}1.2 定义四个插件四个插件foo、bar、baz和qux具有完全相同的定义所以我们只给出插件foo相关定义和配置。如下所示的是定义在foo/src/index.ts针对插件foo的定义。我们导出了表示插件名称的常量和插件函数apply。我们利用插件的配置对应插件函数apply的config参数来提供插件来源并直接调用上面定义的printPlunginPath函数将插件的路径和来源输出来。import{Context}fromdeepseek-ai/cordisimport{printPlunginPath}fromjaydenai/utilsexportconstnamefooexportfunctionapply(ctx:Context,config:{source:string}){printPlunginPath(ctx,config.source);}如下所示的分别是package.json和tsconfig.json定义。package.json中将包名设置为jaydenai/foo其余插件对应的包也采用这种模式命名,并定义了用来编译的build脚本。{name:jaydenai/foo,private:true,type:module,main:lib/index.js,types:lib/index.d.ts,exports:{.:{types:./lib/index.d.ts,default:./lib/index.js}},dependencies:{jaydenai/utils:workspace:*},scripts:{build:tsc -b tsconfig.json --force}}{extends:../../../tsconfig.base.json,compilerOptions:{rootDir:src,outDir:lib},include:[src],references:[{path:../../demo/utils},]}1.3 插件树的构建DSH将基础插件整合在deepseek-ai/dsh-base这个包中我们也采用类似的做法将上述四个插件整合在一个名为jaydenai/base包中对应./packages/demo/base目录。其base/src/index.ts代码只需要导出一个空对象即可核心在如下的cordis.patch.yml文件它将四个插件整合成具有层次结构的插件树。-insert:-id:plugin1name:jaydenai/fooconfig:source:package.json-id:plugin2name:jaydenai/barconfig:source:package.json-id:group1name:cordis:groupgroup:trueconfig:-id:plugin3name:jaydenai/bazconfig:source:package.json-id:group2name:cordis:groupgroup:trueconfig:-id:plugin4name:jaydenai/quxconfig:source:package.json这个cordis.patch.yml文件构建的插件树由四个插件节点plugin1、plugin2、plugin3和plugin4和两个插件组节点group1和group2组成。每个插件利用通过配置config将来源设置为package.json表明来源于Profile对应的package.json文件。值得一提的是作为组的名称被统一设置为cordis:group这个预定义的名称。整个插件树的结构如下root ├── plugin1(foo) ├── plugin2(bar) └── group1 ├── plugin3(baz) └──group2 └── plugin4(qux)照例给出package.json和tsconfig.json的定义{name:jaydenai/base,private:true,type:module,main:lib/index.js,types:lib/index.d.ts,exports:{.:{types:./lib/index.d.ts,default:./lib/index.js}},dsh:{bundle:{patch:./cordis.patch.yml}},dependencies:{jaydenai/foo:workspace:*,jaydenai/bar:workspace:*,jaydenai/baz:workspace:*,jaydenai/qux:workspace:*},scripts:{build:tsc -b tsconfig.json --force}}{extends:../../../tsconfig.base.json,compilerOptions:{rootDir:src,outDir:lib},include:[src],references:[{path:../../../vendor/cordis},{path:../foo},{path:../bar},{path:../baz},{path:../qux}]}1.4 为入口程序添加针对jaydenai/base的依赖我们知道DSH的如何程序指向apps/cli/src/bin.ts这个TypeScript文件为了让我们创建的Profile能够找到提供基础插件的jaydenai/base包我们只需要为在项目apps/cli对应的packakge.json以如下的方式添加针对这个包的依赖。{name:deepseek-ai/dsh,...dependencies:{jaydenai/base:workspace:^,...}}2. 创建自定义的Profile我们将创建的Profile命名为test所以我们需要在$DSH_HOME/profiles目录下创建一个对应的目录对于Windows来说这个目录的地址为%userprofile%.dsh/profiles/test。按照在这个目录下添加如下四个必要的配置文件package.json配置用来提供插件的包每个包会利用dsh.bundle.patch字段提供补丁文件cordis.patch.yml在上述包的基础上进一步提供额外的插件补丁cordis.yml为通过包和补丁构建的插件树提供一个统一的根设置为一个空的数组即可pnpm-workspace.yamlpnpm 包管理器的多项目Monorepo工作区配置文件可以从其他Profile目录拷贝进来。如下所示的是package.json的完整定义。我们在dsh.profile.bundles中添加了两个包一个是提供为DSH提供基础插件的deepseek-ai/dsh-base另一个就是上面构建的jaydenai/base。patchReload字段被设置为startup意味着不提供热重载的支持。{name:dsh-profile-test,private:true,dependencies:{},dsh:{profile:{bundles:[deepseek-ai/dsh-base,jaydenai/base],patchReload:startup}}}我们在cordis.patch.yml这个补丁文件中按照如下的方式定义了另一套包含三个插件foo、bar和baz的插件树并通过插件配置将来源设置成cordis.patch.yml。-insert:-id:plugin5name:jaydenai/fooconfig:source:cordis.patch.yml-id:group1insert:-id:plugin6name:jaydenai/barconfig:source:cordis.patch.yml-id:group2insert:-id:plugin7name:jaydenai/bazconfig:source:cordis.patch.yml注入看三个插件被插入的位置foo被至于最顶层bar被添加到由Profile Bundles构建的标识为group1的组中baz被添加到由Profile Bundles构建的标识为group2的组中。3. 测试自定义的Profile接下来我们以如下的方式执行npx tsx apps/cli/src/bin.ts --profile test以test作为Profile名称执行启动程序会得到如下的输出LoaderIncludefoo[package.json] LoaderIncludefoo[cordis.patch.yml] LoaderIncludebar[package.json] LoaderIncludeGroupbar[cordis.patch.yml] LoaderIncludeGroupbaz[package.json] LoaderIncludeGroupGroupbaz[cordis.patch.yml] LoaderIncludeGroupGroupqux[package.json]上面的输出来源于注册在Profile的七个插件其中四个来源于package.json其余三个来源于cordis.patch.yml整个插件树正式它们合并后的产物它的整个结构服务我们如下的预期root ├── plugin1(foo) ├── plugin2(bar) ├── plugin5(foo) └── group1 ├── plugin3(baz) ├── plugin6(baz) └──group2 ├── plugin4(qux) └── plugin7(baz)到目前位置我们可以看到Profile提供了插件的两种来源它们分别由注册到package.json中的补丁文件和自身的补丁文件cordis.patch.yml提供。DSF最终构建的插件还有其它的来源这是DeepSeek Harness深度拆解-04:完整的插件配置树如何构建重点讲述的内容。