🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

模型中转站 qilin-zhu/dsh-model-relay

DeepSeek Harnessv0.1.1
qilin-zhu44 天前有提交已提供中文文档扫描:含敏感能力

把已配好的模型转成 OpenAI 接口供外部调用

33.1/100
综合分
0
GitHub 星标
30
npm 周下载
—/5
用户评分

数据截至 2026/10/6 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-model-relay
npm 源 · dsh-model-relay 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · model
装得上吗
静态安装检查通过
未做真实安装
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 44 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-model-relay @ 0.1.1
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/20 14:14:33

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 10 处,展示前 10 条)
  • 发起网络请求
    lib/index.js:353
    response = await fetch(url, signal === undefined ? {} : { signal })
  • 读取本地文件
    lib/index.js:1111
    table = readFileSync('/proc/net/route', 'utf8')
  • 发起网络请求
    lib/client.js:233
    response = await fetch(new URL(SETTINGS_ROUTE, document.baseURI), {
  • 读取本地文件
    lib/keys.js:122
    const info = await stat(this.file)
  • 读取本地文件
    lib/keys.js:141
    raw = await readFile(this.file, 'utf8')
  • 读取本地文件
    lib/keys.js:147
    raw = await readFile(legacy, 'utf8')
  • 写入本地文件
    lib/keys.js:187
    await mkdir(dirname(this.file), { recursive: true })
  • 写入本地文件
    lib/keys.js:189
    await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, { mode: 0o600 })
  • 写入本地文件
    lib/keys.js:190
    await rename(temporary, this.file)
  • 写入本地文件
    lib/keys.js:192
    await chmod(this.file, 0o600)
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-attachment@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-home-paths@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-llm
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 中转已配置模型
  • 提供OpenAI接口
  • 供外部项目调用

把deepseek harness里已经配好的模型中转出去,让其他项目可以使用。

README

由 DeepSeek 最新模型翻译生成
模型中转站 (dsh-model-relay) 给 DeepSeek Harness 挂一个 OpenAI 兼容的 /v1 接口,把 harness 里已注册的大模型(比如 @tnnevol/dsh-codebuddy 里的 CodeBuddy 模型)反代出去,其他项目用标准 OpenAI 客户端就能调用。 在 设置 → 模型中转站 里可以看到所有接口地址、创建和管理 API 密钥、开关鉴权、查看可用模型。 为什么不是「再登一次」 这个插件不碰任何凭据。它自己不读、不存、不刷新 token,而是把请求交给 DSH 的 llm 服务转发——也就是直接复用已注册的 provider 适配器。 这不是风格偏好,是正确性要求:CodeBuddy 的 refresh token 会轮换,而且 @tnnevol/dsh-codebuddy 内部用单飞(single-flight) 保证并发刷新只发生一次。如果本插件自己再建一个 session,两次刷新会互相作废,结果是 Web 界面里的登录被踢掉。走 ctx.llm 就不可能出现这种情况。 由此还顺带复用了上游插件的账号故障转移、额度感知、图片序列化和模型目录。 安装 发布到 npm 后,最简单的方式是在 DSH 里打开 设置 → 插件市场 搜索「模型中转站」一键安装。
展开完整文档(README 剩余 1 节)
模型中转站 (dsh-model-relay)

给 DeepSeek Harness 挂一个 OpenAI 兼容的 /v1 接口,把 harness 里已注册的大模型(比如 @tnnevol/dsh-codebuddy 里的 CodeBuddy 模型)反代出去,其他项目用标准 OpenAI 客户端就能调用。

在 设置 → 模型中转站 里可以看到所有接口地址、创建和管理 API 密钥、开关鉴权、查看可用模型。

为什么不是「再登一次」

这个插件不碰任何凭据。它自己不读、不存、不刷新 token,而是把请求交给 DSH 的 llm 服务转发——也就是直接复用已注册的 provider 适配器。

这不是风格偏好,是正确性要求:CodeBuddy 的 refresh token 会轮换,而且 @tnnevol/dsh-codebuddy 内部用单飞(single-flight) 保证并发刷新只发生一次。如果本插件自己再建一个 session,两次刷新会互相作废,结果是 Web 界面里的登录被踢掉。走 ctx.llm 就不可能出现这种情况。

由此还顺带复用了上游插件的账号故障转移、额度感知、图片序列化和模型目录。

安装

发布到 npm 后,最简单的方式是在 DSH 里打开 设置 → 插件市场 搜索「模型中转站」一键安装。

命令行安装:

从 npm 安装(发布后)
dsh plugin --profile web add dsh-model-relay

本地开发(file: 链接)
dsh plugin --profile web add /绝对路径/dsh-model-relay

装完重启 Web profile,服务地址是:

http://127.0.0.1:3080/v1

本插件尚未发布到 npm 与插件目录。发布步骤见 PUBLISHING.md。

用法

任意 OpenAI 客户端都可以,base_url 指向上面的地址。

鉴权是可选的:还没创建密钥、也没在配置里写固定密钥时,接口对所有人开放(随便填一个 sk-xxx 或留空都行)。创建第一个密钥后自动开始校验。

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:3080/v1", api_key="sk-dshgw-...")

resp = client.chat.completions.create(
model="codebuddy_deepseek-v4.1-flash",
messages=[{"role": "user", "content": "你好"}],
)
print(resp.choices[0].message.content)

curl http://127.0.0.1:3080/v1/models

curl http://127.0.0.1:3080/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"codebuddy_deepseek-v4.1-flash","messages":[{"role":"user","content":"你好"}]}'

支持的接口:

| 方法 | 路径 | 说明 |
| GET | /v1/models | 列出所有已注册 provider 的模型 |
| POST | /v1/chat/completions | 对话,支持 stream: true |

请求体与 OpenAI Chat Completions 一致:model、messages、stream、tools、tool_choice、temperature、max_tokens、stop、reasoning_effort。支持 role: "tool" 的工具结果回传、assistant 的 tool_calls 回放、以及 image_url 图片输入(data URL 或远程 URL)。

流式响应是标准 SSE,以 data: [DONE] 结束。

模型名怎么写

推荐用 供应商_模型,例如 codebuddy_deepseek-v4-flash。

不同供应商的模型 id 会重名。实测这套组合里 deepseek-v4-flash 和 deepseek-v4-pro 同时存在于 deepseek-official 和 codebuddy。所以每个模型都按 <供应商>_<模型id> 命名,/v1/models 返回的 id 就是这个形式。

用下划线而不是斜杠,是为了让模型名对客户端保持单个不透明 token:有些 OpenAI 兼容工具会把 / 当作路径分隔符,而且斜杠会和下面这种旧写法混淆。

三种写法都接受,优先级从高到低:

| 写法 | 例子 | 说明 |
| 供应商_模型 | codebuddy_deepseek-v4-flash | 推荐,唯一无歧义 |
| 供应商/模型 | codebuddy/deepseek-v4-flash | 旧写法,仍兼容 |
| 裸模型名 | glm-5.2 | 先查 defaultProvider,再找目录里的唯一匹配,都没有时若只注册了一个供应商就用它 |

裸名在多个供应商上都存在时返回 400,并在错误信息里给出全部可选的全名,例如:

model "deepseek-v4-flash" is served by several providers;
use a namespaced id such as codebuddy_deepseek-v4-flash, deepseek-official_deepseek-v4-flash

响应里的 model 字段回显你请求时用的全名,方便对照日志。

模型目录是建议性的:适配器接受未列出的 id。目录会缓存 30 秒。

设置页

装好后打开 设置 → 模型中转站,页面上有:

- 接口地址:Base URL、两个端点、一段可直接复制的 Python 示例
- API 密钥:创建(带备注名)、删除、以及开启/关闭鉴权
- 可用模型:当前暴露的模型清单

密钥是怎么处理的

- 服务端只保存 SHA-256 哈希,明文只在创建弹窗里显示一次,关掉就再也拿不回来——丢了就删掉重建。
- 列表里显示的是 sk-dshgw-abcd…wxyz 这种可辨认的掩码,不能用来调用。
- 密钥文件默认在 ~/.dsh/model-relay-keys.json,权限 0600;权限被放宽的文件会被当作不存在,不会被信任。
- 写入是原子替换 + 串行化队列,连续创建多个密钥不会互相覆盖。

一个刻意的安全取舍

删掉最后一个密钥,接口仍然保持锁定,必须显式点「关闭鉴权」才会重新开放。

这不是偷懒:如果删除密钥会自动放开接口,那么「清理一个不再使用的密钥」这个日常动作就会静默地把接口暴露给所有人。权限只应由明确的操作放开,而不是由清理动作的副作用放开。页面上有对应的提示文案。

反向代理(飞牛统一网关等)

调用模型请用端口地址,不要走反向代理。

页面上显示的 Base URL 是 http://<host>:<port>/v1,其中 port 是 DSH 实际监听的端口(从 ctx.webServer.port 读取,不是页面地址)。这样做的原因:

- 反向代理会重写路径。飞牛网关把应用挂在 /app/fn-deepseek-harness 下,转发前剥掉这个前缀,并按自己的白名单给页面资源加前缀。SSE 流式响应和这个网关的路径改写叠加起来很容易出问题。
- 直接打端口就绕开了所有这些不确定性。

页面上会提示当前是「只监听本机」还是「监听所有网卡」,据此判断其他机器能否调用。

局域网访问

想让同一网络的其他设备(手机、笔记本、另一台服务器)也能调用,开一个独立监听端口:

- id: dsh-model-relay
inject:
- llm
- webServer
config:
lanPort: 3081

然后其他设备用:

http://<这台机器的局域网IP>:3081/v1

设置页的「局域网访问」卡片会直接列出可用的 IP 地址,每个都能一键复制。

只列出真正的局域网地址。 装了 Docker/libvirt 的机器通常会有十几个 172.x/br-* 虚拟网桥地址,局域网设备根本连不上。这些会被过滤掉(docker、br-、veth、virbr、vmnet、wg、tailscale 等前缀),默认路由所在网卡的地址排在最前。被隐藏的数量会在卡片下方注明,方便排查——万一你的可用地址恰好落在某个网桥上,能看出是过滤导致的,而不是「没有地址」。

为什么是独立端口,而不是把 Web UI 也开到局域网

lanPort 起的是一个只提供模型接口的 node:http 服务:

- 它不会代理 DSH 界面。在这上面访问 / 或 /api/... 都是 404。
- 它的访问面只有 GET /v1/models 和 POST /v1/chat/completions 两条。
- 如果改成把 DSH Web 服务器本身绑到 0.0.0.0,那么会话 Cookie 登录、设置接口、以及所有其他 DSH 路由都会一起暴露到网络上——那不是这个功能想要的东西。

必须配密钥

开启局域网监听前请先创建一个 API 密钥。 没有密钥时,这个端口对同网络的任何设备开放。启动时如果发现没配密钥,日志会打一条警告,设置页上也会显示红色提示。

密钥校验在两条监听上是同一套:本机调用和局域网调用都认同一份密钥。

配置项

| 字段 | 默认 | 说明 |
| lanPort | false | false 关闭;端口号开启独立监听;0 让系统分配一个空闲端口 |
| lanHost | 0.0.0.0 | 监听地址。只想给某一个网卡用时填该网卡的 IP |

lanPort 是整数(0–65535)或 false,其他值会在加载时直接报错,而不是等到运行时才发现。

防火墙

如果局域网连不上,先确认主机防火墙放行了这个端口。以飞牛/群晖这类 NAS 为例,通常需要在系统防火墙里额外放行 TCP 3081。

设置页自身是怎么在网关下工作的

设置页需要在网关下也能用,所以它的请求走了一个专门的约定:

- 端点挂在 /api/model-relay,而不是插件私有的通道名。/api 是每个部署(包括飞牛网关的白名单)都一定会转发的唯一前缀;插件私有通道名不在白名单里,请求根本到不了 DSH——这正是早先设置页报 transport failure ... HTTP 404 的原因。
- 客户端用相对路径 api/model-relay 并对着 document.baseURI 解析。网关会把 设成带前缀的地址,相对路径因此自动带上前缀;写成根绝对路径 /api/... 会以 origin 为基准解析,前缀就丢了。
- 该端点复用 Connection 的信任校验与浏览器鉴权,所以只有已登录的 DSH 页面能管理密钥,/v1 自身不提供任何密钥管理入口。

配置(可选)

在 profiles/web/cordis.patch.yml 里按 id 覆盖:

- id: dsh-model-relay
inject:
- llm
- webServer
config:
挂载点,默认 /v1 path: /v1 固定密钥:配了就校验,没配就放行。支持 Authorization: Bearer 和 x-api-key。
与设置页创建的密钥同时生效;配置里存在固定密钥时,设置页无法关闭鉴权。
apiKeys:
- sk-your-key
只暴露这些 provider,默认全部 providers:
- codebuddy
裸模型名的优先 provider defaultProvider: codebuddy 跨域响应头,默认 true cors: true 密钥存储路径,默认 <DSH_HOME>/model-relay-keys.json keysFile: /path/to/keys.json 局域网独立监听端口(详见上文"局域网访问");false 或不写即关闭 lanPort: 3081 监听地址,默认 0.0.0.0 lanHost: 0.0.0.0 本插件的 bundle patch 会复写 connection 那一行的 inject(loader patch 是整值替换,不合并)。它必须保持为所有需要 connection RPC 的插件所要求依赖的并集——目前是 webRuntime 和 webServer。以后再有插件加自己的 RPC 通道,也要一起维护这个并集。

边界

- 默认只监听本机。DSH Web 默认绑 127.0.0.1;局域网访问要用 lanPort 开独立端口,并且一定要先配密钥。
- 密钥管理只走设置页。管理端点挂在 DSH 已鉴权的 connection carrier 上(/api/model-relay),/v1 自身不提供任何密钥管理接口。
- 没有 /v1/embeddings、/v1/images 等接口。这个网关只做对话和模型列表;DSH 的 llm 服务没有 embedding 能力,所以这里不会假装有。
- 图片输入依赖附件服务。ctx.attachments 没挂载时,带图请求会被明确拒绝(400),而不是静默丢图。
- 请求体上限 32 MiB,超出返回 413。

开发

node --check lib/index.js && node --check lib/keys.js && node --check lib/client.js
node test/gateway.test.mjs # 协议翻译 + 路由 + 设置端点
node test/keys.test.mjs # 密钥存储:哈希、权限、并发、锁定语义

测试用假的 llm 服务和真实的 node:http 服务器驱动路由,验证 OpenAI 协议翻译的往返形状;密钥测试跑在真实临时目录上。都不需要登录账号,也不发外部请求。

改了代码后确认一下安装副本。 pnpm 对 file: 依赖用的是硬链接(不是拷贝,也不是软链):源文件和 node_modules 里的副本是同一个 inode。

这带来一个反直觉的坑:

- 原地写入(echo >> file、sed -i 之外的直接写)会同步到两边——因为本来就是同一份内容。
- 先写临时文件再 rename(write/edit 这类工具、多数编辑器保存时都是这么做的)会悄悄断开硬链接。源文件更新了,副本还是旧内容,而且没有任何报错。

所以改完源码后如果行为没变,别怀疑代码——先对一下内容:

diff -r lib/ /绝对路径/profiles/web/node_modules/dsh-model-relay/lib/

不一致就重新执行 dsh plugin --profile web add file:/绝对路径/dsh-model-relay。注意 pnpm 可能报 "Already up to date" 而不重新链接,这种情况先 remove 再 add。

想让改动彻底自动生效,可以把依赖换成 link:。

License

Apache-2.0
预览分享卡片长什么样 ↓qilin-zhu/dsh-model-relay
分享 · DeepSeek Harness Hub
qilin-zhu/dsh-model-relay

把已配好的模型转成 OpenAI 接口供外部调用

回到顶部