夜雨聆风学习资料网

ARTICLE · 1154447

2. 插件打包和安装的必要性

2. 插件打包和安装的必要性
在官方wiki上可以看到前面这样几个讲解,为什么开发完插件后,还需要打包和安装?简单点说:是因为dsh的插件是npm包,不是随便放个文件就能跑的

要让某个profile真正用上它,必须经过”编译打包->装进profile的node_modules -> 在patch里声明加载这三步“。 

一、核心原因

1、插件的加载方式决定了必须安装


dsh加载插件靠的是cordis.patch.yml里的配置,比如 

{ insert: { plugin: '@deepseek-ai/dsh-my-custom-tool' } }

这里写的是包名(@deepseek-ai/dsh-my-custom-tool),不是文件路径。Node.js 要能 import 到这个包,它必须存在于 node_modules 里——而进入 node_modules 的唯一方式就是安装。 

2、每个profile是独立的小项目


每个 profile 目录下都有自己的 package.json 和 node_modules,比如 

$DSH_HOME/profiles/web/    ├── package.json        ← 这个 profile 装了哪些插件    ├── cordis.patch.yml    └── node_modules/       ← 插件实际存在的地方

你给 web profile 装了一个插件,headless profile不会自动有。这种隔离设计要求每个 profile 单独安装自己需要的插件。 

3、插件有自己的依赖树


一个插件可能依赖第三方库(比如 axios、zod)。这些依赖需要 pnpm/npm 来解析、下载、放进正确的位置。手动拷贝文件搞不定依赖解析。


4、开发态vs运行态的区别

阶段
插件在哪
要不要安装
开发时
在 packages/xxx/ 下,通过 pnpm workspace 自动链接
不用,pnpm install 一次全搞定
运行时/分发
要装进某个 profile 的 node_modules
必须安装,否则 import 不到

在 dsh 源码仓库里开发插件,pnpm install 就把所有 workspace 包链接好了,所以感觉"不用安装"。但如果你把插件给别人用,或者装到一个独立的 profile 里,就必须走打包安装流程。 

二、打包和安装分别指什么 

1、打包build 

把ts源码编译成可运行的js,命令是


pnpm build # 产出 lib/ 目录(编译后的 JS + 类型声明) 

为什么要打包?因为


  • 运行时(尤其是 Python SDK 打包的 exe)不会跑 TypeScript 源码
  • 发布到 npm 的必须是编译后的产物
  • 减少体积、提升启动速度

2、安装install


把打包好的插件装进目标 profile:


# 方式 1:用 dsh 命令(推荐)    dsh plugin --profile web add file:/path/to/your-plugin     # 方式 2:手动    # 1. 编辑 profile 的 package.json,加上依赖    # 2. 在 profile 目录下 pnpm install 

安装完成后,还要在 cordis.patch.yml 里 insert 这个插件,它才会被加载。 

三、流程图

你写的插件源码(packages/my-plugin/src/)          │           │  pnpm build(打包)            ▼    编译产物(packages/my-plugin/lib/)           │           │  dsh plugin add(安装)           ▼   profile 的 node_modules/@deepseek-ai/dsh-my-plugin/           │           │  cordis.patch.yml 里 insert        ▼  运行时被加载,插件生效

四、最简单的插件

上面这个目录就是自定义开发的插件,可以用命令:pnpm dsh web --patch ./scratch-plugin/cordis.yml执行,会在cli客户端看到下面这行日志:[hello-plugin] plugin loaded!

......

[hello-plugin] plugin loaded!

dsh web: http://127.0.0.1:3080/?token=rl0JJgdY7pUDGanIiItFHr2NpSoXU62m-Q-XvdCWgFY

dsh web: opening the default browser; pass --no-open to disable

......

1、命令解释

部分
含义

pnpm

用 pnpm 执行仓库里定义的脚本/命令(这里走仓库的 dsh 启动器,从源码运行)

dsh

deepseek-harness 的启动器命令(`apps/cli/src/bin.ts`)

web

子命令,等价于 --profile web:启动 web 这个 profile(Web UI 服务)

--patch ./scratch-plugin/cordis.yml

--patch ./scratch-plugin/cordis.yml

2、启动时配置怎么叠加的?

web profile 的配置树 = cordis.yml(空根)\

  + dsh-base bundle 层 \

  + dsh-web-app bundle 层 \

  + profile 自己的 cordis.patch.yml(用户层) \

  + ./scratch-plugin/cordis.yml   ← --patch 加的这一层(插入 hello 插件)

  • --patch` 是单次生效的:只对本次启动有效,不会写进 profile 的 `cordis.patch.yml`。想永久生效要改 profile 的用户层或用 `dsh plugin add`

  • 效果:Web UI 正常启动的同时,额外加载你的 hello-plugin,终端打印 `[hello-plugin] plugin loaded!`

五、其他重要内容

自动清理、声明依赖(插件需要使用其他服务,如tools、llm,需要声明inject)、插件还有其他形态

六、自定义tool

import type { Context } from '@deepseek-ai/cordis'

import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'

export const inject = ['tools']

export function apply(ctx: Context) {

  ctx.tools.register(defineTool({

    name: 'greet',

    description: 'Greet someone by name.',

    parameters: {

      name: { type: 'string', required: true, description: 'The name to greet' },

    },

    output: {

      schema: { type: 'string' },

      render: (_args, value) => [{ type: 'text', text: value }],

    },

    async execute(args) {

      return `Hello, ${args.name}!`

    },

  }))

}

同样的命令启动后,就可以在正常对话时,调用工具了


相关学习资料