Passer au contenu principal
X

dsh-model-endpoint

xxsttb/dsh-model-endpoint

Publishes whichever model the profile has selected in chat or on the Models page as an OpenAI-compatible HTTP endpoint: /v1/models, /v1/chat/completions (streaming SSE and non-streaming), /health. A request may name any routable model; one that omits `model` gets the current chat selection, so switching model in chat switches the endpoint. Switched on, it can also lend the model the Harness tools switched on individually, run those calls through the Harness tool runtime and feed results back up to a round limit. The listener is the plugin's own, so 127.0.0.1, 0.0.0.0 and a NAT-forwarded port all behave the same with no change to the request path. Settings cover bind address, port, API key with a generator, fallback provider and model, reasoning effort, CORS origin and timeout.

Installer

dsh plugin --profile web add github:xxsttb/dsh-model-endpoint

README

dsh-model-endpoint

English | 中文

把 DeepSeek Harness profile 当前正在使用的模型发布成一个 OpenAI 兼容的 HTTP 端点。

任意 OpenAI 客户端 —— openai SDK、LangChain、Cline、一个 shell 脚本、另一个 Harness 实例 —— 指向本插件打印出的 base URL,它就会用你在聊天里、或在「模型」设置页里选中的那个 模型作答。不需要第二份凭据,客户端侧除了一个 base URL 之外也没有任何额外配置。

因为监听器是插件自己创建的(不是 Harness 的 Web 服务器),绑定地址由你决定:127.0.0.1 只留在本机,0.0.0.0 发布到局域网,同一个端口放在路由器或 NAT 转发之后即可从公网访问。 请求路径完全不取决于你用的是这三种拓扑里的哪一种。


它做什么

GET /v1/models列出本 profile 能路由的每个模型,既有 provider/model 形式,也有无歧义时的裸 id。
GET /v1/models/<id>单个模型对象,否则 404 model_not_found。
POST /v1/chat/completionsOpenAI 对话补全,支持流式(SSE)与非流式。
GET /health实时绑定状态、各拓扑下的可达性、鉴权模式、当前模型。
GET /与 /health 同一份文档。

另外还有几条挂在 Harness Web 服务器上(不是本端点)的路由:

GET /plugins/model-endpoint同一份状态文档,同源,供设置页使用。
GET /plugins/model-endpoint/models状态文档加上可路由模型目录。
GET /plugins/model-endpoint/efforts?provider=&model=该路由声明的推理强度,供设置页下拉框使用。
GET /plugins/model-endpoint/tools每个已注册工具及其开关状态,供设置页使用。

模型解析

端点按以下优先级作答:

  1. 请求里的 model 字段,前提是它指向一个可路由的模型。provider/model 与裸模型 id 都接受。
  2. profile 当前的选择 —— 与聊天选择器和「模型」设置页写入的是同一个值。在聊天里换 模型,端点就跟着换。
  3. 本插件设置页上的备用提供方与模型。
  4. 无论选中的是什么,即使 followSelection 关着,也作为最后手段使用。一个用你正看着 的模型作答的端点,好过一个回 503 的端点。

如果没有任何可路由的东西,响应是 503 model_unavailable,报文里会列出每一个失败候选及 其原因。

推理强度

只有当被服务的模型确实声明了该强度时,reasoning_effort 才会生效;否则按模型自己的默认 值。这就是未知强度会安静降级而不是让请求失败的原因。插件页面上的设置项为省略该字段的请求 提供默认值。

备用模型与推理强度都是下拉框,不是自由文本。它们取自「模型」设置页读取的同一份目录,所以 你能选的就是这个 profile 真正能路由到的,而强度列表属于你实际选中的那个模型。已经存下来 但不再被提供的值 —— 提供方下线了、它的模型不再声明某个强度了 —— 仍然可选并带标注,而不 会被悄悄改写。

流式

POST /v1/chat/completions 带 "stream": true 时以 SSE 作答。文本作为 delta.content 到达,推理作为 delta.reasoning_content(DeepSeek 客户端已经理解的扩展),工具调用则是 惯常的带索引 delta.tool_calls。模型思考期间会发送保活注释,使代理不会因超时切断连接。 用量报告在最后一个 chunk 里,随终结性的 finish_reason 一起发出。

工具调用

这里有两件不同的事都叫「工具调用」,它们可以叠加。

调用方自带的工具。 tools 会被转发给模型,工具调用在流式与非流式两条路径上都会回来, 入站的 role: "tool" 消息也照收。端点从不执行请求塞给它的工具 —— 那些属于调用方,调用 会在 finish_reason: "tool_calls" 下原样返回给调用方去跑。因此依赖 function calling 的 客户端可以正常工作。

借出的工具。 端点还可以把这个 Harness 已注册的工具借给模型,自己执行,再把结果回填 —— 一次请求内完成完整的模型/工具往返。那是对本机的真实能力,所以它默认关闭,开启后 也只发送被逐个打开的工具。见借出的工具。

自带 tools 的请求永远不会收到借出的工具。把它们混在一起,等于把调用方跑不了的调用交还 给它。


借出的工具

设置 → Model endpoint → 把这个 profile 的工具借给模型。

开启后,Harness 已注册的每个工具都会按名字列出并各带一个开关。只有打开的工具会被发送给 模型。当模型调用其中一个时,端点经 Harness 工具运行时执行它、附加结果、再问模型一次 —— 最多 Max tool rounds(默认 8)轮。达到上限的一轮会以 409 tool_round_limit 被切断, 而不是无限重试。

开关上方,全选一次写入打开所有已列出的工具,全不选则全部关闭,这样一长串工具不必 一行一行点。两者写入的是与单个开关相同的 toolSwitches 字典。

如实说明粒度。 开关是按工具,而不是按插件。Harness 注册表记录工具的名字、描述与 参数;它不记录是哪个插件注册的,而工具注册所在的 scope 属于调用它的 agent,不属于插件。 因此按插件的开关将不得不猜测本插件无从观测的归属。按工具比按插件更细,而且是失败关闭的, 所以提供的就是按工具。

有些工具会执行代码、读写文件或访问网络。请只为那些你愿意替一个未鉴权调用方亲自运行的工具 打开开关 —— 如果监听器在 127.0.0.1 之外可达,还要设一个 API 密钥。

curl http://127.0.0.1:8787/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"auto","messages":[{"role":"user","content":"列出工作区里的文件。"}]}'

是否调用借出的工具由模型决定;没有任何东西强制它。


安装

从一份检出,或从一个打包好的 tarball:

dsh plugin --profile desktop add /absolute/path/to/dsh-model-endpoint
# 或者
pnpm pack --pack-destination dist
dsh plugin --profile desktop add /absolute/path/to/dist/dsh-model-endpoint-1.0.0.tgz

插件会自行挂载、启动监听器,并在「设置」下添加一个 Model endpoint 页面。除此之外无需 任何东西 —— Harness 自身不暴露任何入站 HTTP API,所以本插件从头到尾拥有自己的监听器。

验证:

curl http://127.0.0.1:8787/health
curl http://127.0.0.1:8787/v1/models

卸载:

dsh plugin --profile desktop remove dsh-model-endpoint

两点值得知道的事

用 dsh plugin add 安装,不要手工改。 自己编辑 profile 清单会让正在运行的进程留下一张 过期的模块解析表:插件会作为一个死行加载 —— 出现在组合里、从未激活、任何地方都不报错 —— 直到进程重启。这不是本插件特有的问题。

绝不要先用 link: 安装、之后又换成 tarball。 pnpm 会移除旧的 node_modules 条目,而在 Windows 上它会跟随重解析点,从而清空链接指向的源码目录。用一种方式安装,就一直用那种方式。


设置

设置 → Model endpoint。

字段默认值含义
Run the endpoint开在不卸载的情况下关掉监听器。
Bind address127.0.0.10.0.0.0 接受每个接口上的连接。
Port8787客户端连接的端口。
API key空设置后,客户端需发送 Authorization: Bearer <key>。
Follow the selected model开服务聊天或「模型」页里选中的任何模型。开启期间备用模型下拉框会隐藏,因为它不是实际被服务的东西。
Fallback provider / model空不跟随当前选择、或什么都没选时使用。从本 profile 真正能到的模型下拉框里选。
Reasoning effort模型默认只在被服务的模型声明它时生效。下拉框列的正是所选备用模型声明的强度,加上该模型自己的默认值。
Extra system prompt空只为自身不带 system 消息的请求前置。
Default max output tokens8192请求省略 max_tokens 时使用。0 表示本端点不加任何上限 —— 请求不带 maxTokens 字段发出,由提供方按它自己模型的限制处理。
Request timeout (ms)600000一次补全(含流式)的上界。
Allowed browser origin*作为 Access-Control-Allow-Origin 发送。
Lend this profile's tools关把下面打开开关的 Harness 工具交给模型,并在此执行。
(逐个工具)关每个已注册工具一个开关。只有打开的工具会被发送。
Max tool rounds8一次请求在被切断前可进行的模型/工具往返次数。

API key 那一行有一个 Generate 按钮,会用 62 个字符的字母表加拒绝采样填上一个全新的 48 字符密钥(约 286 位,没有需要转义的 +///=,也没有易混的字符对)。它只是填入字段 —— 按 Save 才生效,并且请先把它复制出去,因为本字段是它唯一的存放处。

每个字段都是实时的:改一个,监听器就自行重新绑定。字段上方的状态块显示实际绑定的端口 和本机可达的地址,读自正在运行的监听器,而不是把表单里的值回显出来。

安全

只有在绑定 127.0.0.1 时才可以留空 API 密钥。当端点在无密钥的情况下绑定到本机之外时, 设置页会给出警告,因为那种配置下任何能连到这个端口的人都能花掉本 profile 的模型额度。 API 密钥以常量时间比较。

没有 TLS。如果你要把它暴露到公网,请在反向代理处终结 TLS,或把连接保持在隧道内 —— 无论 哪种方式,端点本身说的都是明文 HTTP。


连接一个客户端

任意 OpenAI 兼容客户端:

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key="unused")
client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "hello"}],
)

model: "auto"(或 "default",或省略该字段)表示「用这个 profile 当前选中的」。鉴权关闭 时,任意非空字符串都能当 API 密钥用。

用 curl:

curl http://127.0.0.1:8787/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"auto","messages":[{"role":"user","content":"hello"}],"stream":true}'

从另一台机器访问

  1. 把 Bind address 设为 0.0.0.0,并给端点设一个 API key。
  2. 从状态块里读 Local network URL —— 那是本机在局域网里的地址,端口已经填好。
  3. 要从公网访问,就在路由器上把该端口转发到本机,然后使用你的公网地址。端点不需要知道 转发这件事;到达的请求就是会被作答的请求。

如果 Host 日志说监听器无法绑定,那说明端口被占用,或者绑定地址在本机并不存在。设置页会把 这种情况呈现为「Unknown」状态,而不是默默假装在跑。


错误

错误使用 OpenAI 的信封:

{"error": {"message": "...", "type": "invalid_request_error", "code": "model_not_found"}}
状态代码原因
400invalid_request_error消息、角色、工具或请求体格式错误。
400unsupported_content非文本内容块。本端点服务的是纯文本模型。
401invalid_api_key缺少或错误的 bearer token。
404not_found没有这条路由。
404model_not_found请求的 model 不可路由。
405method_not_allowed对已知路由用了错误的方法。
409tool_round_limit模型在超过 Max tool rounds 之后仍在调用借出的工具。
413request_too_large请求体超过 8 MiB,或输入过大。
429rate_limit_exceeded上游提供方限流。
429insufficient_quota上游额度耗尽。
500upstream_error模型调用失败且没有更具体的代码。
503model_unavailable无法解析出任何可路由的目标。

上游失败保留 Harness 自己的分类,因此 AUTH、RATE_LIMIT、QUOTA、ACCOUNT_QUOTA 与 CONTEXT_WINDOW_EXCEEDED 会以上表中的代码到达,而不会被拍平成一个笼统的错误。


目录结构

cordis.patch.yml    profile 条目:id `model-endpoint` —— 同时也是它的设置命名空间
lib/index.js        Host 半侧:HTTP 监听器与 OpenAI 转译
lib/client.js       Client 半侧:设置页
locale/{en,zh}.json 插件列表用的显示元数据
icon.svg            插件图标

cordis.patch.yml 里的 profile 条目 id 是承重的:设置服务按条目 id 寻址插件,所以设置 命名空间与 id 必须一致。只改一个而不改另一个,设置页就会脱钩。


开发

语法检查两半:

ELECTRON_RUN_AS_NODE=1 "/path/to/DeepSeek Harness" --check lib/index.js
ELECTRON_RUN_AS_NODE=1 "/path/to/DeepSeek Harness" --check lib/client.js

打包:

pnpm pack --pack-destination dist

Host 半侧是 ESM,导入 @deepseek-ai/schemastery 与 @deepseek-ai/dsh-llm。两者都经 Harness 安装解析,所以本包把它们声明为 peer 依赖,node_modules/ 不进仓库。


免责声明

在你依赖它之前请先审阅。它是针对 Harness 0.2.0-rc.2 写的,没有任何东西保证它跟得上 之后的版本。

它是通过阅读那份安装自身的源码写成的,并对着一个正在运行的 profile 做过现场验证:监听器 能绑定、/v1/models 能枚举出 profile 的提供方、/v1/chat/completions 能流式返回真实 补全、设置页能通过设置服务读写。借出工具的往返循环是对着一个用假工具注册表驱动 Host 半侧 的测试台验证的,覆盖了能跑的工具、会失败的工具、只在后续 delta 上才出现名字的调用、畸形的 参数、撞到轮数上限的模型循环,以及一个绝不能被与借出工具混用的调用方自带工具。

它不是从任何现有插件抄来的。它仍可能含有错误,且不附带任何担保。在把它暴露到网络之前, 请先读一遍。


由 AI 生成

本仓库中的一切都由 AI 助手应仓库所有者的要求生成:

  • Host 半侧(lib/index.js),包括每个请求处理器、OpenAI 转译层与监听器生命周期;
  • Client 半侧(lib/client.js),包括设置页、其样式、胶囊开关、随机密钥生成器,以及中英文 文案;
  • 设置页的额外 system 提示词字段及其空默认值 —— 端点刻意不注入任何自己的东西,因此调用方 自己的 system 消息原样通过;
  • 本 README、插件清单、cordis.patch.yml 与 icon.svg;
  • 每个文件里的每一条代码注释。

许可证

MIT —— 见 LICENSE。

Plugins associés