dsh-gateway-provider 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Luck9Star
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,234 @@
1
+ # dsh-gateway-provider
2
+
3
+ [![gitleaks](https://github.com/Luck9Star/dsh-gateway-provider/actions/workflows/gitleaks.yml/badge.svg)](https://github.com/Luck9Star/dsh-gateway-provider/actions/workflows/gitleaks.yml)
4
+
5
+ > 中文文档:[README.zh.md](README.zh.md)
6
+
7
+ Bring any LLM gateway — **newapi, LiteLLM, Higress, or any OpenAI-compatible
8
+ endpoint** — into DeepSeek Harness. The plugin discovers the gateway's model
9
+ list automatically, enriches every model with real parameters from
10
+ [models.dev](https://models.dev), and serves each model over its native wire
11
+ protocol (OpenAI / Anthropic / Gemini) through the pi-ai SDK.
12
+
13
+ ## Why this exists
14
+
15
+ DeepSeek Harness ships first-party adapters (`llm-deepseek`, `llm-pi-ai`) that
16
+ each speak for one provider. If your models live behind a gateway, the
17
+ alternative is a hand-maintained static model list with guessed context
18
+ windows and output caps.
19
+
20
+ This plugin mounts the gateway itself: **N gateways become N provider
21
+ routes, with zero static model lists.** Every model keeps its true
22
+ parameters, and adding a model on the gateway side is enough — nothing to
23
+ re-deploy.
24
+
25
+ ## What you get
26
+
27
+ - **Multiple gateways** — a default `newapi` route plus one `gateway:<id>`
28
+ route per extra gateway, each with its own catalog cache.
29
+ - **Automatic discovery** — `GET {base}/v1/models` first (including each
30
+ model's supported request formats), management API fallback second.
31
+ - **Real parameters** — models.dev data fills context window, output cap,
32
+ reasoning levels, and release date; config defaults are only a fallback.
33
+ - **Every wire format** — each model routes to its own protocol
34
+ (`openai-completions` / `openai-responses` / `anthropic-messages` /
35
+ `google-generative-ai`); no hand-written SSE or request serialization.
36
+ - **A settings page** — **Settings → Gateway Models**: add and edit gateways
37
+ from templates, test connections, hide / override / custom-add models —
38
+ no YAML editing required.
39
+ - **A clean picker** — newest-first release-date sorting, chat-only filtering,
40
+ and regex excludes for non-chat models.
41
+
42
+ ## Requirements
43
+
44
+ - A DeepSeek Harness installation with a profile (`dsh`)
45
+ - A reachable gateway: newapi / LiteLLM / Higress / any OpenAI-compatible endpoint
46
+ - An API key for that gateway
47
+
48
+ ## Quickstart
49
+
50
+ 1. Install the plugin into your profile — straight from GitHub, no clone needed:
51
+
52
+ ```bash
53
+ dsh plugin --profile web add git+https://github.com/Luck9Star/dsh-gateway-provider.git
54
+ ```
55
+
56
+ `dsh plugin add` forwards to `pnpm add` in the profile directory, so any
57
+ pnpm spec works (`github:Luck9Star/dsh-gateway-provider`, a local path, …).
58
+ The bundle patch (`cordis.patch.yml`) then mounts the `llm-newapi` loader
59
+ row automatically — no manual patch editing. The bare package name will
60
+ resolve once the package is published to npm.
61
+
62
+ 2. Store your gateway key in `$DSH_HOME/.credentials.yaml` (mode 0600,
63
+ hot-reloaded):
64
+
65
+ ```yaml
66
+ NEWAPI_API_KEY: sk-REPLACE_WITH_YOUR_KEY
67
+ ```
68
+
69
+ Or export `NEWAPI_API_KEY` in the launching environment.
70
+
71
+ 3. Restart the profile, then open **Settings → Gateway Models**.
72
+
73
+ **Success looks like:** the gateway appears with a "synced N models" badge,
74
+ and every chat-capable model is selectable with its real context window.
75
+ The base URL resolves from `llm-newapi.baseURL` settings →
76
+ `NEWAPI_BASE_URL` / `NEWAPI_API_URL` env → the public default
77
+ `https://api.newapi.ai`.
78
+
79
+ ## Add more gateways
80
+
81
+ Extra gateways live in the `gateways` array — each becomes its own
82
+ `gateway:<id>` provider route:
83
+
84
+ ```yaml
85
+ llm-newapi:
86
+ baseURL: https://your-newapi-instance.com
87
+ gateways:
88
+ - id: litellm-prod
89
+ label: LiteLLM Prod
90
+ baseURL: https://litellm.example.com
91
+ apiKeyEnv: LITELLM_API_KEY
92
+ flavor: litellm # form template: newapi / litellm / higress / openai-compatible / custom
93
+
94
+ # Fully-custom gateway: complete per-protocol endpoint URLs, no shared
95
+ # base. A protocol left empty stays disabled.
96
+ - id: edge
97
+ label: Edge GW
98
+ flavor: custom
99
+ openaiURL: https://edge.example.com/openai/v1/chat/completions
100
+ responsesURL: https://edge.example.com/openai/v1/responses
101
+ anthropicURL: https://edge.example.com/anthropic/v1/messages
102
+ apiKeyEnv: EDGE_API_KEY
103
+ ```
104
+
105
+ You can also add gateways from the settings page (with the same templates)
106
+ instead of editing YAML.
107
+
108
+ ## Control the model list per gateway
109
+
110
+ Hide models, fix wrong metadata, or add models the gateway does not list:
111
+
112
+ ```yaml
113
+ llm-newapi:
114
+ models:
115
+ - id: glm-5.2
116
+ disabled: true # hide from picker
117
+ - id: glm-5.2-highspeed
118
+ contextWindow: 1000000 # override discovered value
119
+ protocol: openai # force protocol (openai/anthropic/gemini/openai-response)
120
+ - id: my-internal-model # custom model the gateway does not list
121
+ name: My Internal Model
122
+ contextWindow: 200000
123
+ ```
124
+
125
+ The **Gateway Models** page exposes the same operations with a UI: search,
126
+ hidden/custom filters with counts, a per-model override editor (placeholders
127
+ show discovered values), connection test, and save/cancel semantics.
128
+
129
+ ## Configuration reference
130
+
131
+ Section `llm-newapi:` of `$DSH_HOME/settings.yaml`. The flat fields
132
+ (`baseURL` / `apiKeyEnv` / …) seed the default `newapi` route; gateways in
133
+ the `gateways` array support most of the same fields per gateway
134
+ (`label` / `apiKeyEnv` / `flavor` / `catalogMode` / `endpointPriority` / …).
135
+
136
+ | Field | Default | Description |
137
+ |-------|---------|-------------|
138
+ | `label` | `NewAPI` | Display name of the default gateway route |
139
+ | `apiKeyEnv` | `NEWAPI_API_KEY` | Credential reference (environment variable name) |
140
+ | `baseURL` | env `NEWAPI_BASE_URL` / `NEWAPI_API_URL` → `https://api.newapi.ai` | Gateway base URL (unused when protocol URLs are set) |
141
+ | `flavor` | `newapi` | Gateway template: `newapi` / `litellm` / `higress` / `openai-compatible` / `custom` |
142
+ | `openaiURL` | – | Full chat-completions endpoint URL (custom template; empty = protocol disabled) |
143
+ | `responsesURL` | – | Full Responses endpoint URL (custom template; empty = protocol disabled) |
144
+ | `anthropicURL` | – | Full Anthropic messages endpoint URL (custom template; empty = protocol disabled) |
145
+ | `modelsUrl` | `https://models.dev/models.json` | models.dev source (`file:` URLs work offline) |
146
+ | `useModelsDev` | `true` | Enrich gateway models with models.dev parameters |
147
+ | `extendedReasoningLevels` | `false` | Widen the unknown-model reasoning fallback to off~max (default off/low/medium/high) |
148
+ | `sortModelsByRelease` | `true` | Sort the picker newest-first by release date (unknown dates first) |
149
+ | `catalogMode` | `auto` | Model-list source: `auto` / `v1` / `management` |
150
+ | `catalogTtlMs` | `1800000` | Model-list cache freshness window |
151
+ | `includeChatOnly` | `true` | Only expose chat-capable models to the picker |
152
+ | `excludePatterns` | image/speech/embed/… | Regex patterns excluding models from the picker |
153
+ | `endpointPriority` | `["openai-response","anthropic","openai","gemini"]` | Wire-format preference order (first match wins per model) |
154
+ | `userId` | `1` | `New-Api-User` header for the management API |
155
+ | `maxTokens` | `32768` | Output cap fallback when models.dev lacks data |
156
+ | `defaultContextWindow` | `128000` | Context window fallback when models.dev lacks data |
157
+ | `streamIdleTimeoutMs` | `300000` | Stream idle watchdog |
158
+ | `retryPolicy` | standard | Same shape as `llm-deepseek` |
159
+
160
+ ## How it works
161
+
162
+ All wire-format concerns are delegated to
163
+ [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai)
164
+ — the same SDK the official `dsh-llm-pi-ai` adapter uses. Per request:
165
+
166
+ ```
167
+ harness GenerateOptions
168
+ → toPiContext() lib/pi-bridge.js harness messages → pi-ai Context
169
+ → models.streamSimple() pi-ai SDK dispatches by each model's `api`
170
+ → toStreamChunks() lib/pi-bridge.js pi-ai events → harness chunks
171
+ ```
172
+
173
+ Each discovered model is built with an `api` field mapped from the gateway's
174
+ advertised `supported_endpoint_types`, honoring `endpointPriority`; the
175
+ pi-ai provider receives an api *map*, so every model routes to its own
176
+ protocol implementation. `sdkBaseURL()` appends `/v1` for OpenAI-protocol
177
+ models and leaves Anthropic/Google bases untouched.
178
+
179
+ ```
180
+ dsh-gateway-provider/
181
+ ├── index.js # plugin entry: Config, provider registration, settings/credentials
182
+ ├── cordis.patch.yml # dsh.bundle patch (auto-mounts via `dsh plugin add`)
183
+ ├── lib/ # adapter, pi-provider, pi-bridge, catalog, modelsdev, thinking, client
184
+ ├── test/ # smoke (live gateway) + offline units + settings-UI render
185
+ └── scripts/link.sh # link profile node_modules for local checkouts
186
+ ```
187
+
188
+ ## Develop from a checkout
189
+
190
+ 1. `bash scripts/link.sh` — symlink this package's `node_modules` to the
191
+ profile's so bare `@deepseek-ai/*` imports resolve to the exact module
192
+ instances the harness process uses (single-copy `instanceof` safety).
193
+ 2. Register the checkout as a profile link dependency and install:
194
+
195
+ ```bash
196
+ cd "$DSH_HOME/profiles/web"
197
+ # package.json dependencies: "dsh-gateway-provider": "link:/absolute/path/to/dsh-gateway-provider"
198
+ # and add "dsh-gateway-provider" to the bundles list in the same file
199
+ pnpm install
200
+ ```
201
+
202
+ 3. Restart the profile.
203
+
204
+ > ⚠️ Do **not** also insert `id: llm-newapi` into the profile's own
205
+ > `cordis.patch.yml` — the bundle layer already mounts it, and a duplicate
206
+ > raises `duplicate loader entry id` at boot.
207
+
208
+ Client-bundle edits hot-apply after a browser reload; host-half edits need a
209
+ profile restart.
210
+
211
+ ### Tests
212
+
213
+ ```bash
214
+ node test/smoke.mjs # live gateway: catalog / openai × 2 / tools / anthropic / gemini / custom-urls
215
+ node test/smoke.mjs --only custom-urls
216
+ node test/protocol-urls.mjs # offline: URL derivation + gateway resolution
217
+ node test/client-render.mjs # offline: settings-UI render tree (zh + en)
218
+ ```
219
+
220
+ Smoke-test credentials resolve from: process environment → plugin `.env` →
221
+ `$NEWAPI_ENV_FILE` (plus a legacy author-local fallback path).
222
+
223
+ ## Security
224
+
225
+ - The API key is only resolved through `$DSH_HOME/.credentials.yaml` (0600)
226
+ or the environment — never written to logs, config, or chat.
227
+ Credential fields are edited masked on the web UI.
228
+ - No runtime `dependencies`; `peerDependencies` reuse the harness-installed
229
+ `@deepseek-ai/*` packages and `@earendil-works/pi-ai`. Commits are gated
230
+ by gitleaks locally (pre-commit) and in CI.
231
+
232
+ ## License
233
+
234
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,218 @@
1
+ # dsh-gateway-provider
2
+
3
+ [![gitleaks](https://github.com/Luck9Star/dsh-gateway-provider/actions/workflows/gitleaks.yml/badge.svg)](https://github.com/Luck9Star/dsh-gateway-provider/actions/workflows/gitleaks.yml)
4
+
5
+ > English: [README.md](README.md)
6
+
7
+ 把任意 LLM 网关 —— **newapi、LiteLLM、Higress 或任何 OpenAI 兼容端点** —— 接入
8
+ DeepSeek Harness。插件自动发现网关的模型列表,用 [models.dev](https://models.dev)
9
+ 的真实参数补齐每个模型,并通过 pi-ai SDK 以模型原生协议(OpenAI / Anthropic /
10
+ Gemini)分发请求。
11
+
12
+ ## 为什么需要它
13
+
14
+ DeepSeek Harness 自带的适配器(`llm-deepseek`、`llm-pi-ai`)各自只服务一个官方
15
+ provider。如果你的模型都在网关后面,就得手工维护一份静态模型清单,上下文窗口、
16
+ 输出上限全靠猜。
17
+
18
+ 本插件直接把网关挂进来:**N 个网关 = N 个 provider 路由,零静态清单。** 每个模型
19
+ 带着真实参数,网关侧加模型即可生效——无需重新部署。
20
+
21
+ ## 你能得到什么
22
+
23
+ - **多网关** — 默认 `newapi` 路由 + 每个额外网关一条 `gateway:<id>` 路由,
24
+ 各自独立 catalog 缓存。
25
+ - **自动发现** — 首选 `GET {base}/v1/models`(含每个模型支持的请求格式),
26
+ 管理 API 兜底。
27
+ - **真实参数** — models.dev 补齐上下文窗口、输出上限、推理等级、发布日期;
28
+ 配置默认值只是兜底。
29
+ - **全协议覆盖** — 每个模型路由到自己的协议(`openai-completions` /
30
+ `openai-responses` / `anthropic-messages` / `google-generative-ai`),
31
+ 无手写 SSE、无手写请求序列化。
32
+ - **图形设置页** — **设置 → 网关模型**:按模板添加/编辑网关、测试连接、
33
+ 隐藏/覆写/自定义添加模型——不用碰 YAML。
34
+ - **干净的选择器** — 按发布日期新到旧排序、只保留对话模型、正则剔除。
35
+
36
+ ## 环境要求
37
+
38
+ - 装有 profile 的 DeepSeek Harness(`dsh`)
39
+ - 一个可达网关:newapi / LiteLLM / Higress / 任意 OpenAI 兼容端点
40
+ - 该网关的 API Key
41
+
42
+ ## 快速开始
43
+
44
+ 1. 把插件装进 profile——直接从 GitHub 安装,无需 clone:
45
+
46
+ ```bash
47
+ dsh plugin --profile web add git+https://github.com/Luck9Star/dsh-gateway-provider.git
48
+ ```
49
+
50
+ `dsh plugin add` 会在 profile 目录里转发执行 `pnpm add`,因此任何
51
+ pnpm spec 都可用(`github:Luck9Star/dsh-gateway-provider`、本地路径等)。
52
+ bundle 补丁(`cordis.patch.yml`)随后自动挂载 `llm-newapi` 加载行——
53
+ 无需手工改 patch。裸包名要等发布到 npm 之后才能解析。
54
+
55
+ 2. 把网关 key 写入 `$DSH_HOME/.credentials.yaml`(权限 0600,热加载):
56
+
57
+ ```yaml
58
+ NEWAPI_API_KEY: sk-REPLACE_WITH_YOUR_KEY
59
+ ```
60
+
61
+ 或在启动环境中导出 `NEWAPI_API_KEY`。
62
+
63
+ 3. 重启 profile,打开 **设置 → 网关模型**。
64
+
65
+ **成功的样子:** 网关出现并带"已同步 N 个模型"徽标,全部对话模型可选、
66
+ 上下文窗口是真实值。基础地址解析顺序:`llm-newapi.baseURL` 设置 →
67
+ `NEWAPI_BASE_URL` / `NEWAPI_API_URL` 环境变量 → 公共默认
68
+ `https://api.newapi.ai`。
69
+
70
+ ## 添加更多网关
71
+
72
+ 额外网关写在 `gateways` 数组——每个成为独立的 `gateway:<id>` provider 路由:
73
+
74
+ ```yaml
75
+ llm-newapi:
76
+ baseURL: https://your-newapi-instance.com
77
+ gateways:
78
+ - id: litellm-prod
79
+ label: LiteLLM 生产
80
+ baseURL: https://litellm.example.com
81
+ apiKeyEnv: LITELLM_API_KEY
82
+ flavor: litellm # 表单模板:newapi / litellm / higress / openai-compatible / custom
83
+
84
+ # 完全自定义网关:各协议写完整端点地址,不共享基础地址;
85
+ # 留空的协议不启用。
86
+ - id: edge
87
+ label: 边缘网关
88
+ flavor: custom
89
+ openaiURL: https://edge.example.com/openai/v1/chat/completions
90
+ responsesURL: https://edge.example.com/openai/v1/responses
91
+ anthropicURL: https://edge.example.com/anthropic/v1/messages
92
+ apiKeyEnv: EDGE_API_KEY
93
+ ```
94
+
95
+ 也可以在设置页里用同样的模板添加网关,不必编辑 YAML。
96
+
97
+ ## 按网关控制模型清单
98
+
99
+ 隐藏模型、修正错误元数据、添加网关未列出的模型:
100
+
101
+ ```yaml
102
+ llm-newapi:
103
+ models:
104
+ - id: glm-5.2
105
+ disabled: true # 从选择器隐藏
106
+ - id: glm-5.2-highspeed
107
+ contextWindow: 1000000 # 覆盖发现值
108
+ protocol: openai # 强制协议(openai/anthropic/gemini/openai-response)
109
+ - id: my-internal-model # 自定义模型(网关未列出)
110
+ name: 我的内测模型
111
+ contextWindow: 200000
112
+ ```
113
+
114
+ **网关模型**设置页提供同样的操作:搜索、带计数的已隐藏/自定义筛选、逐模型覆写
115
+ 编辑器(placeholder 显示发现值)、连接测试、保存/取消语义。
116
+
117
+ ## 配置参考
118
+
119
+ `$DSH_HOME/settings.yaml` 的 `llm-newapi:` 节。扁平字段(`baseURL` /
120
+ `apiKeyEnv` 等)构建默认 `newapi` 路由;`gateways` 数组里的每个网关支持
121
+ 大部分同名字段(`label` / `apiKeyEnv` / `flavor` / `catalogMode` /
122
+ `endpointPriority` / …)。
123
+
124
+ | 字段 | 默认 | 说明 |
125
+ |------|------|------|
126
+ | `label` | `NewAPI` | 默认网关路由的显示名 |
127
+ | `apiKeyEnv` | `NEWAPI_API_KEY` | 凭据引用(环境变量名) |
128
+ | `baseURL` | env `NEWAPI_BASE_URL` / `NEWAPI_API_URL` → `https://api.newapi.ai` | 网关地址(配置协议 URL 后不再使用) |
129
+ | `flavor` | `newapi` | 网关模板:`newapi` / `litellm` / `higress` / `openai-compatible` / `custom` |
130
+ | `openaiURL` | – | OpenAI 兼容完整端点地址(自定义模板;留空 = 不启用) |
131
+ | `responsesURL` | – | Responses 完整端点地址(自定义模板;留空 = 不启用) |
132
+ | `anthropicURL` | – | Anthropic messages 完整端点地址(自定义模板;留空 = 不启用) |
133
+ | `modelsUrl` | `https://models.dev/models.json` | models.dev 数据源(`file:` URL 可离线) |
134
+ | `useModelsDev` | `true` | 是否用 models.dev 参数增强模型 |
135
+ | `extendedReasoningLevels` | `false` | 未知模型推理等级兜底放宽到 off~max(默认仅 off/low/medium/high) |
136
+ | `sortModelsByRelease` | `true` | 选择器按发布日期新到旧排列(未知日期排最前) |
137
+ | `catalogMode` | `auto` | 模型列表来源:`auto` / `v1` / `management` |
138
+ | `catalogTtlMs` | `1800000` | 模型列表缓存时长 |
139
+ | `includeChatOnly` | `true` | 仅把对话模型放进选择器 |
140
+ | `excludePatterns` | image/speech/embed/… | 选择器剔除的正则列表 |
141
+ | `endpointPriority` | `["openai-response","anthropic","openai","gemini"]` | wire 格式优先级(每模型取首个匹配) |
142
+ | `userId` | `1` | 管理 API 的 `New-Api-User` 头 |
143
+ | `maxTokens` | `32768` | 无 models.dev 数据时的输出上限兜底 |
144
+ | `defaultContextWindow` | `128000` | 无 models.dev 数据时的上下文兜底 |
145
+ | `streamIdleTimeoutMs` | `300000` | 流空闲看门狗 |
146
+ | `retryPolicy` | 标准重试 | 与 `llm-deepseek` 同构 |
147
+
148
+ ## 工作原理
149
+
150
+ 所有 wire 格式细节都委托给
151
+ [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai)
152
+ —— 官方 `dsh-llm-pi-ai` 适配器用的同一 SDK。每次请求:
153
+
154
+ ```
155
+ harness GenerateOptions
156
+ → toPiContext() lib/pi-bridge.js harness 消息 → pi-ai Context
157
+ → models.streamSimple() pi-ai SDK 按每个模型的 `api` 分发协议
158
+ → toStreamChunks() lib/pi-bridge.js pi-ai 事件 → harness chunk
159
+ ```
160
+
161
+ 每个发现的模型带一个 `api` 字段,由网关广告的 `supported_endpoint_types`
162
+ 映射而来,遵循 `endpointPriority`;pi-ai provider 收到的是 api *映射表*,
163
+ 所以每个模型路由到自己的协议实现。`sdkBaseURL()` 为 OpenAI 协议模型补
164
+ `/v1`,Anthropic/Google 的基础地址保持不变。
165
+
166
+ ```
167
+ dsh-gateway-provider/
168
+ ├── index.js # 插件入口:Config 校验、provider 注册、设置/凭据接线
169
+ ├── cordis.patch.yml # dsh.bundle 补丁(经 `dsh plugin add` 自动挂载)
170
+ ├── lib/ # adapter、pi-provider、pi-bridge、catalog、modelsdev、thinking、client
171
+ ├── test/ # 冒烟(直连网关)+ 离线单元 + 设置页渲染
172
+ └── scripts/link.sh # 本地检出时链接 profile 的 node_modules
173
+ ```
174
+
175
+ ## 从本地检出开发
176
+
177
+ 1. `bash scripts/link.sh` — 把本包 `node_modules` 软链到 profile 的,
178
+ 保证 `@deepseek-ai/*` 裸导入解析到 harness 进程使用的同一份模块实例
179
+ (单实例 `instanceof` 安全)。
180
+ 2. 把检出注册为 profile 的 link 依赖并安装:
181
+
182
+ ```bash
183
+ cd "$DSH_HOME/profiles/web"
184
+ # package.json dependencies 追加:"dsh-gateway-provider": "link:/本包绝对路径/dsh-gateway-provider"
185
+ # 再把 "dsh-gateway-provider" 加进同一文件的 bundles 列表
186
+ pnpm install
187
+ ```
188
+
189
+ 3. 重启 profile。
190
+
191
+ > ⚠️ **不要**再往 profile 自己的 `cordis.patch.yml` 里插入 `id: llm-newapi`
192
+ > ——bundle 层已挂载,重复会导致启动时报 `duplicate loader entry id`。
193
+
194
+ 客户端 bundle 改动刷新浏览器即生效;宿主侧改动需要重启 profile。
195
+
196
+ ### 测试
197
+
198
+ ```bash
199
+ node test/smoke.mjs # 直连网关:catalog / openai × 2 / 工具调用 / anthropic / gemini / custom-urls
200
+ node test/smoke.mjs --only custom-urls
201
+ node test/protocol-urls.mjs # 离线:URL 派生 + 网关解析单元测试
202
+ node test/client-render.mjs # 离线:设置页渲染树(中/英)
203
+ ```
204
+
205
+ 冒烟测试凭据解析顺序:进程环境变量 → 插件目录 `.env` →
206
+ `$NEWAPI_ENV_FILE`(另有一个作者本地遗留兜底路径)。
207
+
208
+ ## 安全说明
209
+
210
+ - API Key 只经 `$DSH_HOME/.credentials.yaml`(0600)或环境变量解析,
211
+ 绝不写入日志、配置或对话;凭据字段在 Web UI 中以掩码编辑。
212
+ - 无运行时 `dependencies`;`peerDependencies` 复用 harness 已装的
213
+ `@deepseek-ai/*` 与 `@earendil-works/pi-ai`。提交在本地(pre-commit)
214
+ 与 CI 双层接受 gitleaks 扫描把关。
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,10 @@
1
+ # dsh-gateway-provider bundle patch: inserts the plugin row into the profile
2
+ # roster. Applied as a profile bundle layer (the `dsh.bundle.patch` manifest
3
+ # field) over dsh-base; activate with
4
+ # dsh plugin --profile <name> add dsh-gateway-provider
5
+ #
6
+ # The row is a bare plugin by package name: the host half (exports ".") runs
7
+ # in the DSH process and registers the `newapi` provider route on ctx.llm.
8
+ - insert:
9
+ - id: llm-newapi
10
+ name: 'dsh-gateway-provider'