dsh-gateway-provider 1.0.3 → 1.0.5
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/README.md +132 -215
- package/docs/README.zh.md +84 -197
- package/lib/adapter.js +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -4,246 +4,163 @@
|
|
|
4
4
|
|
|
5
5
|
> 中文文档:[docs/README.zh.md](docs/README.zh.md)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
endpoint
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
Use **all the models behind your LLM gateway** — newapi, LiteLLM, Higress,
|
|
8
|
+
or any OpenAI-compatible endpoint — directly in DeepSeek Harness.
|
|
9
|
+
|
|
10
|
+
Install the plugin, paste your API key, and every gateway model shows up in
|
|
11
|
+
dsh's model picker with its **real** parameters (context window, output cap,
|
|
12
|
+
reasoning support) fetched from [models.dev](https://models.dev). Requests
|
|
13
|
+
go out over each model's own native protocol — OpenAI, Anthropic, or Gemini
|
|
14
|
+
— so tool calls and streaming behave the way that model's maker intended.
|
|
12
15
|
|
|
13
16
|
## Why this exists
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
|
18
|
+
dsh ships one adapter per official provider. If your models live behind a
|
|
19
|
+
gateway, the manual alternative is a hand-maintained static model list with
|
|
20
|
+
guessed context windows and output caps. This plugin mounts the gateway
|
|
21
|
+
itself instead:
|
|
22
|
+
|
|
23
|
+
- **Nothing to maintain by hand** — the model list is read from the gateway
|
|
24
|
+
(`GET /v1/models`, with a management-API fallback for newapi); add a model
|
|
25
|
+
on the gateway side and it appears in dsh, no re-deploy.
|
|
26
|
+
- **Real numbers, not guesses** — models.dev data fills context window,
|
|
27
|
+
output cap, reasoning levels, release date; config defaults only fill gaps.
|
|
28
|
+
- **Every wire format, one plugin** — each model routes over its own
|
|
29
|
+
protocol (OpenAI chat completions / OpenAI responses / Anthropic messages
|
|
30
|
+
/ Gemini), handled by the same
|
|
31
|
+
[pi-ai](https://www.npmjs.com/package/@earendil-works/pi-ai) SDK the
|
|
32
|
+
official dsh adapter uses.
|
|
33
|
+
- **Multiple gateways at once** — a default `newapi` route plus one
|
|
34
|
+
`gateway:<id>` route per extra gateway, each with its own cache and key.
|
|
35
|
+
- **A settings page instead of YAML** — **Settings → Gateway Models**: add
|
|
36
|
+
gateways from templates (NewAPI / LiteLLM / Higress / OpenAI-compatible /
|
|
37
|
+
fully custom), test connections, sync models, hide or override any model,
|
|
38
|
+
add custom models.
|
|
41
39
|
|
|
42
40
|
## Requirements
|
|
43
41
|
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
## Quickstart
|
|
49
|
-
|
|
50
|
-
1. Install the plugin into your profile (published on npm):
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
dsh plugin --profile web add dsh-gateway-provider # latest
|
|
54
|
-
dsh plugin --profile web add dsh-gateway-provider@<version> # pinned
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
`dsh plugin add` forwards to `pnpm add` in the profile directory.
|
|
58
|
-
The bundle patch (`cordis.patch.yml`) then mounts the `llm-newapi`
|
|
59
|
-
loader row automatically — no manual patch editing.
|
|
60
|
-
|
|
61
|
-
2. Store your gateway key in `$DSH_HOME/.credentials.yaml` (mode 0600,
|
|
62
|
-
hot-reloaded):
|
|
63
|
-
|
|
64
|
-
```yaml
|
|
65
|
-
NEWAPI_API_KEY: sk-REPLACE_WITH_YOUR_KEY
|
|
66
|
-
```
|
|
42
|
+
- DeepSeek Harness (dsh) with a `web` profile (the settings page is a web-UI
|
|
43
|
+
extension; the provider itself works in any profile).
|
|
44
|
+
- A gateway API key (e.g. a newapi token).
|
|
67
45
|
|
|
68
|
-
|
|
46
|
+
## Install
|
|
69
47
|
|
|
70
|
-
|
|
48
|
+
```sh
|
|
49
|
+
# 1. Install the plugin (dsh plugin add runs pnpm add under the hood)
|
|
50
|
+
dsh plugin --profile web add dsh-gateway-provider
|
|
71
51
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
52
|
+
# 2. Store your key — pick ONE of:
|
|
53
|
+
# a) the dsh credentials file (recommended; created with mode 0600, hot-reloaded)
|
|
54
|
+
echo "NEWAPI_API_KEY: sk-REPLACE_WITH_YOUR_KEY" >> ~/.dsh/.credentials.yaml
|
|
55
|
+
# b) or export it in the shell you launch dsh from:
|
|
56
|
+
# export NEWAPI_API_KEY=sk-REPLACE_WITH_YOUR_KEY
|
|
77
57
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`gateway:<id>` provider route:
|
|
82
|
-
|
|
83
|
-
```yaml
|
|
84
|
-
llm-newapi:
|
|
85
|
-
baseURL: https://your-newapi-instance.com
|
|
86
|
-
gateways:
|
|
87
|
-
- id: litellm-prod
|
|
88
|
-
label: LiteLLM Prod
|
|
89
|
-
baseURL: https://litellm.example.com
|
|
90
|
-
apiKeyEnv: LITELLM_API_KEY
|
|
91
|
-
flavor: litellm # form template: newapi / litellm / higress / openai-compatible / custom
|
|
92
|
-
|
|
93
|
-
# Fully-custom gateway: complete per-protocol endpoint URLs, no shared
|
|
94
|
-
# base. A protocol left empty stays disabled.
|
|
95
|
-
- id: edge
|
|
96
|
-
label: Edge GW
|
|
97
|
-
flavor: custom
|
|
98
|
-
openaiURL: https://edge.example.com/openai/v1/chat/completions
|
|
99
|
-
responsesURL: https://edge.example.com/openai/v1/responses
|
|
100
|
-
anthropicURL: https://edge.example.com/anthropic/v1/messages
|
|
101
|
-
apiKeyEnv: EDGE_API_KEY
|
|
58
|
+
# 3. Restart and open the settings page
|
|
59
|
+
dsh --profile web
|
|
60
|
+
# → Settings → Gateway Models
|
|
102
61
|
```
|
|
103
62
|
|
|
104
|
-
|
|
105
|
-
|
|
63
|
+
**Expected result:** the model picker gains a "NewAPI" route listing your
|
|
64
|
+
gateway's chat models, newest first. Click **Test** on the gateway card —
|
|
65
|
+
it should answer `✓ Connected — N models`. Not using the public newapi
|
|
66
|
+
cloud? Set **Base URL** on the card (or `baseURL` in config) to your own
|
|
67
|
+
gateway address first.
|
|
106
68
|
|
|
107
|
-
##
|
|
69
|
+
## Daily use
|
|
108
70
|
|
|
109
|
-
|
|
71
|
+
Everything lives in **Settings → Gateway Models**:
|
|
110
72
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
The **Gateway Models** page exposes the same operations with a UI: search,
|
|
125
|
-
hidden/custom filters with counts, a per-model override editor (placeholders
|
|
126
|
-
show discovered values), connection test, and save/cancel semantics.
|
|
127
|
-
|
|
128
|
-
> ⚠️ **Hand-editing YAML?** `settings.yaml` is parsed with YAML 1.2 core
|
|
129
|
-
> semantics: an unquoted `true` / `false` (any casing) becomes a boolean and
|
|
130
|
-
> an unquoted digit run becomes a number. Every plain string field of the
|
|
131
|
-
> section (`id`, `name`, `label`, `baseURL`, `apiKeyEnv`, `modelsUrl`,
|
|
132
|
-
> `userId`, `reasoningLevels` / `excludePatterns` / `endpointPriority`
|
|
133
|
-
> entries, …) tolerates the coerced form, but the cleanest habit is quoting
|
|
134
|
-
> intent-significant tokens — write `reasoningLevels: ["off", "low",
|
|
135
|
-
> "high"]`, not `reasoningLevels: [off, low, high]` — and never `false`
|
|
136
|
-
> where a level name is meant (it survives as the junk level `"false"` and
|
|
137
|
-
> is silently dropped from the picker). A structurally invalid section
|
|
138
|
-
> (objects where strings belong, etc.) is refused at registration: the
|
|
139
|
-
> namespace stays unregistered and every settings write from the web UI
|
|
140
|
-
> then answers `settings-rejected: settings namespace "llm-newapi" is not
|
|
141
|
-
> registered`.
|
|
73
|
+
- **Add more gateways** — "Add Gateway", pick a template (LiteLLM, Higress,
|
|
74
|
+
OpenAI-compatible, or fully custom with per-protocol URLs), point it at
|
|
75
|
+
the base URL, name its key env var, Test, Sync. Each gateway becomes its
|
|
76
|
+
own route in the picker.
|
|
77
|
+
- **Tame the model list** — non-chat models (image / speech / embedding /
|
|
78
|
+
rerank …) are excluded by default regexes; hide or rename any model; add
|
|
79
|
+
a custom model by hand if the gateway hides it; per-model protocol,
|
|
80
|
+
context window, output cap, and reasoning levels are all editable.
|
|
81
|
+
- **Keys live in dsh's credential store** — the settings page shows a badge
|
|
82
|
+
(`✓ Key set · NEWAPI_API_KEY` / `⚠ No key set`) and can write the key
|
|
83
|
+
there for you.
|
|
142
84
|
|
|
143
85
|
## Configuration reference
|
|
144
86
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
the
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
|
151
|
-
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
| `
|
|
173
|
-
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
87
|
+
Optional — everything below has a working default. Config lives in the
|
|
88
|
+
`llm-newapi:` section of `~/.dsh/settings.yaml` (the settings page edits
|
|
89
|
+
the same keys). The frequently used ones:
|
|
90
|
+
|
|
91
|
+
| Key | Default | Meaning |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `baseURL` | `https://api.newapi.ai` | Your gateway's base URL. Env fallbacks: `NEWAPI_BASE_URL`, `NEWAPI_API_URL`. |
|
|
94
|
+
| `apiKeyEnv` | `NEWAPI_API_KEY` | Which env/credential variable holds the key. |
|
|
95
|
+
| `label` | `NewAPI` | Route label shown in the picker. |
|
|
96
|
+
| `flavor` | `newapi` | Template label only (`newapi` / `litellm` / `higress` / `openai-compatible` / `custom`). |
|
|
97
|
+
| `gateways` | — | Array of extra gateways: `{ id, baseURL, apiKeyEnv, label, … }`, each becoming a `gateway:<id>` route. |
|
|
98
|
+
| `models` | — | Per-model overrides: `{ id, name, disabled, protocol, contextWindow, maxTokens, reasoningLevels }`. |
|
|
99
|
+
| `useModelsDev` / `modelsUrl` | `true` / models.dev | Parameter enrichment source (supports `file:` URLs for offline). |
|
|
100
|
+
| `excludePatterns` | image/speech/… | Regex list of model ids to keep out of the picker. |
|
|
101
|
+
| `sortModelsByRelease` | `true` | Newest models first. |
|
|
102
|
+
| `catalogMode` | `auto` | `v1` (`/v1/models` only) / `management` (newapi user API) / `auto`. |
|
|
103
|
+
| `endpointPriority` | responses → anthropic → openai → gemini | Which protocol to prefer when a model supports several. |
|
|
104
|
+
| `openaiURL` / `responsesURL` / `anthropicURL` | — | Fully-custom gateways only: per-protocol endpoint URLs; unset = that protocol off. |
|
|
105
|
+
| `maxTokens` / `defaultContextWindow` | `32768` / `128000` | Fallbacks when models.dev has no data. |
|
|
106
|
+
| `streamIdleTimeoutMs` | `600000` | Idle timeout while streaming. |
|
|
107
|
+
| `headers` | — | Extra HTTP headers sent to the gateway. |
|
|
108
|
+
|
|
109
|
+
## Troubleshooting
|
|
110
|
+
|
|
111
|
+
| Symptom | Cause → fix |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| Picker route exists but zero models | The plugin can't read your model list. Check the gateway base URL; try `catalogMode: "management"` for newapi gateways that restrict `/v1/models`. |
|
|
114
|
+
| `401` / auth errors on every request | Key missing or wrong: check the badge in Settings → Gateway Models, or `NEWAPI_API_KEY` in `~/.dsh/.credentials.yaml`. |
|
|
115
|
+
| A model's context window looks wrong | models.dev had no match. Edit the model on the settings page (or a `models:` override). |
|
|
116
|
+
| Wrong format answers / tool calls flaky for one model | That model is routed over a protocol it handles poorly. Pin `protocol` on the model (`openai`, `openai-response`, `anthropic`, `gemini`). |
|
|
117
|
+
| Custom gateway with separate endpoints | Use `flavor: "custom"` and set `openaiURL` / `responsesURL` / `anthropicURL` explicitly. |
|
|
118
|
+
|
|
119
|
+
## How it works (one minute version)
|
|
120
|
+
|
|
121
|
+
At startup the plugin registers one provider route per gateway, pulls the
|
|
122
|
+
model list from the gateway, and fuzzy-matches each model id against
|
|
123
|
+
models.dev to fill in real parameters. When you pick a model, dsh's request
|
|
124
|
+
is translated to the pi-ai SDK's format and sent over that model's native
|
|
125
|
+
protocol; the streamed reply is translated back into dsh chunks. Catalogs
|
|
126
|
+
are cached (30 min by default) per gateway. No hand-written protocol code —
|
|
127
|
+
the bridge is lifted from the official `dsh-llm-pi-ai` adapter.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
git clone https://github.com/Luck9Star/dsh-gateway-provider
|
|
133
|
+
cd dsh-gateway-provider
|
|
134
|
+
npm run link # symlink into your dsh profile (single instanceof safety)
|
|
135
|
+
npm run test:client # settings-UI render, both locales
|
|
136
|
+
npm run test:urls # URL/derivation units
|
|
137
|
+
npm run smoke # live gateway round-trip (needs a real key)
|
|
181
138
|
```
|
|
182
|
-
harness GenerateOptions
|
|
183
|
-
→ toPiContext() lib/pi-bridge.js harness messages → pi-ai Context
|
|
184
|
-
→ models.streamSimple() pi-ai SDK dispatches by each model's `api`
|
|
185
|
-
→ toStreamChunks() lib/pi-bridge.js pi-ai events → harness chunks
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
Each discovered model is built with an `api` field mapped from the gateway's
|
|
189
|
-
advertised `supported_endpoint_types`, honoring `endpointPriority`; the
|
|
190
|
-
pi-ai provider receives an api *map*, so every model routes to its own
|
|
191
|
-
protocol implementation. `sdkBaseURL()` appends `/v1` for OpenAI-protocol
|
|
192
|
-
models and leaves Anthropic/Google bases untouched.
|
|
193
|
-
|
|
194
|
-
```
|
|
195
|
-
dsh-gateway-provider/
|
|
196
|
-
├── index.js # plugin entry: Config, provider registration, settings/credentials
|
|
197
|
-
├── cordis.patch.yml # dsh.bundle patch (auto-mounts via `dsh plugin add`)
|
|
198
|
-
├── lib/ # adapter, pi-provider, pi-bridge, catalog, modelsdev, thinking, client
|
|
199
|
-
├── test/ # smoke (live gateway) + offline units + settings-UI render
|
|
200
|
-
└── scripts/link.sh # link profile node_modules for local checkouts
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## Develop from a checkout
|
|
204
139
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
140
|
+
Developing from a checkout: point the profile's `package.json` at
|
|
141
|
+
`"dsh-gateway-provider": "link:/abs/path"` and re-run `pnpm install` in the
|
|
142
|
+
profile. Do **not** also add an `id: llm-newapi` row to the profile's own
|
|
143
|
+
`cordis.patch.yml` — the bundle patch already provides it (duplicate row =
|
|
144
|
+
loader error).
|
|
209
145
|
|
|
210
|
-
|
|
211
|
-
cd "$DSH_HOME/profiles/web"
|
|
212
|
-
# package.json dependencies: "dsh-gateway-provider": "link:/absolute/path/to/dsh-gateway-provider"
|
|
213
|
-
# and add "dsh-gateway-provider" to the bundles list in the same file
|
|
214
|
-
pnpm install
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
3. Restart the profile.
|
|
218
|
-
|
|
219
|
-
> ⚠️ Do **not** also insert `id: llm-newapi` into the profile's own
|
|
220
|
-
> `cordis.patch.yml` — the bundle layer already mounts it, and a duplicate
|
|
221
|
-
> raises `duplicate loader entry id` at boot.
|
|
222
|
-
|
|
223
|
-
Client-bundle edits hot-apply after a browser reload; host-half edits need a
|
|
224
|
-
profile restart.
|
|
225
|
-
|
|
226
|
-
### Tests
|
|
227
|
-
|
|
228
|
-
```bash
|
|
229
|
-
node test/smoke.mjs # live gateway: catalog / openai × 2 / tools / anthropic / gemini / custom-urls
|
|
230
|
-
node test/smoke.mjs --only custom-urls
|
|
231
|
-
node test/protocol-urls.mjs # offline: URL derivation + gateway resolution
|
|
232
|
-
node test/client-render.mjs # offline: settings-UI render tree (zh + en)
|
|
233
|
-
```
|
|
146
|
+
## References & credits
|
|
234
147
|
|
|
235
|
-
|
|
236
|
-
|
|
148
|
+
- [pi-ai SDK](https://www.npmjs.com/package/@earendil-works/pi-ai) — all
|
|
149
|
+
four wire protocols; the bridge reuses the official `dsh-llm-pi-ai`
|
|
150
|
+
adapter's translation layer.
|
|
151
|
+
- [models.dev](https://models.dev) — the parameter catalog (context
|
|
152
|
+
windows, output caps, reasoning, release dates).
|
|
153
|
+
- [new-api](https://github.com/QuantumNous/new-api),
|
|
154
|
+
[LiteLLM](https://github.com/BerriAI/litellm),
|
|
155
|
+
[Higress](https://github.com/alibaba/higress) — the gateways this plugin
|
|
156
|
+
is tested against (any OpenAI-compatible endpoint works).
|
|
237
157
|
|
|
238
158
|
## Security
|
|
239
159
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
- No runtime `dependencies`; `peerDependencies` reuse the harness-installed
|
|
244
|
-
`@deepseek-ai/*` packages and `@earendil-works/pi-ai`. Commits are gated
|
|
245
|
-
by gitleaks locally (pre-commit) and in CI.
|
|
160
|
+
Keys live in dsh's credential store or the launching environment — never in
|
|
161
|
+
settings YAML. The repo runs [gitleaks](https://github.com/gitleaks/gitleaks)
|
|
162
|
+
in CI and pre-commit to keep secrets out.
|
|
246
163
|
|
|
247
164
|
## License
|
|
248
165
|
|
|
249
|
-
MIT
|
|
166
|
+
[MIT](LICENSE)
|
package/docs/README.zh.md
CHANGED
|
@@ -4,227 +4,114 @@
|
|
|
4
4
|
|
|
5
5
|
> English: [README.md](../README.md)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
DeepSeek Harness。插件自动发现网关的模型列表,用 [models.dev](https://models.dev)
|
|
9
|
-
的真实参数补齐每个模型,并通过 pi-ai SDK 以模型原生协议(OpenAI / Anthropic /
|
|
10
|
-
Gemini)分发请求。
|
|
7
|
+
把 **LLM 网关背后的全部模型** —— newapi、LiteLLM、Higress,或任何 OpenAI 兼容端点 —— 直接搬进 DeepSeek Harness 用。
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
DeepSeek Harness 自带的适配器(`llm-deepseek`、`llm-pi-ai`)各自只服务一个官方
|
|
15
|
-
provider。如果你的模型都在网关后面,就得手工维护一份静态模型清单,上下文窗口、
|
|
16
|
-
输出上限全靠猜。
|
|
9
|
+
装好插件、贴上 API key,网关里的每个模型就会出现在 dsh 的模型选择器里,并且带着从 [models.dev](https://models.dev) 拉来的**真实**参数(上下文窗口、输出上限、推理支持)。请求按每个模型各自的原生协议出去 —— OpenAI、Anthropic 或 Gemini —— 工具调用和流式输出都按该模型官方的方式工作。
|
|
17
10
|
|
|
18
|
-
|
|
19
|
-
带着真实参数,网关侧加模型即可生效——无需重新部署。
|
|
11
|
+
## 为什么需要它
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
dsh 自带的适配器一个 provider 一个。如果你的模型都在网关后面,手工做法是维护一份静态模型清单,上下文窗口和输出上限全靠猜。本插件直接把网关本身挂进来:
|
|
22
14
|
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
配置默认值只是兜底。
|
|
29
|
-
- **全协议覆盖** — 每个模型路由到自己的协议(`openai-completions` /
|
|
30
|
-
`openai-responses` / `anthropic-messages` / `google-generative-ai`),
|
|
31
|
-
无手写 SSE、无手写请求序列化。
|
|
32
|
-
- **图形设置页** — **设置 → 网关模型**:按模板添加/编辑网关、测试连接、
|
|
33
|
-
隐藏/覆写/自定义添加模型——不用碰 YAML。
|
|
34
|
-
- **干净的选择器** — 按发布日期新到旧排序、只保留对话模型、正则剔除。
|
|
15
|
+
- **不用手工维护** —— 模型列表从网关读取(`GET /v1/models`,newapi 附加管理 API 兜底);网关侧加一个模型,dsh 里就多一个,无需重新部署。
|
|
16
|
+
- **真实数字,不是猜测** —— models.dev 数据补齐上下文窗口、输出上限、推理档位、发布日期;配置默认值只做兜底。
|
|
17
|
+
- **一个插件,所有协议** —— 每个模型走自己的协议(OpenAI chat completions / OpenAI responses / Anthropic messages / Gemini),由官方 dsh 适配器同款的 [pi-ai](https://www.npmjs.com/package/@earendil-works/pi-ai) SDK 处理。
|
|
18
|
+
- **多网关并存** —— 默认 `newapi` 路由,外加每个额外网关一条 `gateway:<id>` 路由,各自的缓存与密钥。
|
|
19
|
+
- **设置页面代替 YAML** —— **Settings → Gateway Models**:从模板添加网关(NewAPI / LiteLLM / Higress / OpenAI 兼容 / 完全自定义)、测连接、同步模型、隐藏或覆盖任意模型、手工添加自定义模型。
|
|
35
20
|
|
|
36
21
|
## 环境要求
|
|
37
22
|
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- 该网关的 API Key
|
|
41
|
-
|
|
42
|
-
## 快速开始
|
|
43
|
-
|
|
44
|
-
1. 把插件装进 profile(已发布到 npm):
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
dsh plugin --profile web add dsh-gateway-provider # 最新版
|
|
48
|
-
dsh plugin --profile web add dsh-gateway-provider@<version> # 钉住版本
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
`dsh plugin add` 会在 profile 目录里转发执行 `pnpm add`。bundle 补丁
|
|
52
|
-
(`cordis.patch.yml`)随后自动挂载 `llm-newapi` 加载行——无需手工改 patch。
|
|
23
|
+
- DeepSeek Harness (dsh),带 `web` profile(设置页面是 web UI 扩展;provider 本身任何 profile 都能用)。
|
|
24
|
+
- 一个网关 API key(比如 newapi 令牌)。
|
|
53
25
|
|
|
54
|
-
|
|
26
|
+
## 安装
|
|
55
27
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
28
|
+
```sh
|
|
29
|
+
# 1. 安装插件(dsh plugin add 底层会执行 pnpm add)
|
|
30
|
+
dsh plugin --profile web add dsh-gateway-provider
|
|
59
31
|
|
|
60
|
-
|
|
32
|
+
# 2. 存好你的 key —— 二选一:
|
|
33
|
+
# a) dsh 凭据文件(推荐;以 0600 权限创建,热加载)
|
|
34
|
+
echo "NEWAPI_API_KEY: sk-REPLACE_WITH_YOUR_KEY" >> ~/.dsh/.credentials.yaml
|
|
35
|
+
# b) 或在启动 dsh 的 shell 里 export:
|
|
36
|
+
# export NEWAPI_API_KEY=sk-REPLACE_WITH_YOUR_KEY
|
|
61
37
|
|
|
62
|
-
3.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
上下文窗口是真实值。基础地址解析顺序:`llm-newapi.baseURL` 设置 →
|
|
66
|
-
`NEWAPI_BASE_URL` / `NEWAPI_API_URL` 环境变量 → 公共默认
|
|
67
|
-
`https://api.newapi.ai`。
|
|
68
|
-
|
|
69
|
-
## 添加更多网关
|
|
70
|
-
|
|
71
|
-
额外网关写在 `gateways` 数组——每个成为独立的 `gateway:<id>` provider 路由:
|
|
72
|
-
|
|
73
|
-
```yaml
|
|
74
|
-
llm-newapi:
|
|
75
|
-
baseURL: https://your-newapi-instance.com
|
|
76
|
-
gateways:
|
|
77
|
-
- id: litellm-prod
|
|
78
|
-
label: LiteLLM 生产
|
|
79
|
-
baseURL: https://litellm.example.com
|
|
80
|
-
apiKeyEnv: LITELLM_API_KEY
|
|
81
|
-
flavor: litellm # 表单模板:newapi / litellm / higress / openai-compatible / custom
|
|
82
|
-
|
|
83
|
-
# 完全自定义网关:各协议写完整端点地址,不共享基础地址;
|
|
84
|
-
# 留空的协议不启用。
|
|
85
|
-
- id: edge
|
|
86
|
-
label: 边缘网关
|
|
87
|
-
flavor: custom
|
|
88
|
-
openaiURL: https://edge.example.com/openai/v1/chat/completions
|
|
89
|
-
responsesURL: https://edge.example.com/openai/v1/responses
|
|
90
|
-
anthropicURL: https://edge.example.com/anthropic/v1/messages
|
|
91
|
-
apiKeyEnv: EDGE_API_KEY
|
|
38
|
+
# 3. 重启,打开设置页
|
|
39
|
+
dsh --profile web
|
|
40
|
+
# → Settings → Gateway Models
|
|
92
41
|
```
|
|
93
42
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
## 按网关控制模型清单
|
|
43
|
+
**预期结果:** 模型选择器多出一条 "NewAPI" 路由,列出网关的对话模型,最新在前。在网关卡片上点 **Test** —— 应显示 `✓ Connected — N models`。用的不是 newapi 公有云?先在卡片上改 **Base URL**(或配置里的 `baseURL`)指向你自己的网关地址。
|
|
97
44
|
|
|
98
|
-
|
|
45
|
+
## 日常使用
|
|
99
46
|
|
|
100
|
-
|
|
101
|
-
llm-newapi:
|
|
102
|
-
models:
|
|
103
|
-
- id: glm-5.2
|
|
104
|
-
disabled: true # 从选择器隐藏
|
|
105
|
-
- id: glm-5.2-highspeed
|
|
106
|
-
contextWindow: 1000000 # 覆盖发现值
|
|
107
|
-
protocol: openai # 强制协议(openai/anthropic/gemini/openai-response)
|
|
108
|
-
- id: my-internal-model # 自定义模型(网关未列出)
|
|
109
|
-
name: 我的内测模型
|
|
110
|
-
contextWindow: 200000
|
|
111
|
-
```
|
|
47
|
+
一切都在 **Settings → Gateway Models**:
|
|
112
48
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
> ⚠️ **手工编辑 YAML?** `settings.yaml` 按 YAML 1.2 core 语义解析:不带引号的
|
|
117
|
-
> `true` / `false`(任意大小写)会变成布尔值,不带引号的数字串会变成数值。本节
|
|
118
|
-
> 所有纯字符串字段(`id`、`name`、`label`、`baseURL`、`apiKeyEnv`、`modelsUrl`、
|
|
119
|
-
> `userId`,以及 `reasoningLevels` / `excludePatterns` / `endpointPriority` 的
|
|
120
|
-
> 条目……)都可容忍这些被强转的形式,但最稳妥的习惯是给有含义的 token 加引号
|
|
121
|
-
> ——写 `reasoningLevels: ["off", "low", "high"]`,而不是
|
|
122
|
-
> `reasoningLevels: [off, low, high]`——更不要在该写级别名的地方写 `false`(它会
|
|
123
|
-
> 以垃圾级别 `"false"` 存活,并被静默排除出选择器)。结构上非法的分节(比如字符
|
|
124
|
-
> 串位置出现对象)会在注册时被拒绝:namespace 保持未注册状态,Web 设置页的每次
|
|
125
|
-
> 写入都会得到
|
|
126
|
-
> `settings-rejected: settings namespace "llm-newapi" is not registered`。
|
|
49
|
+
- **加更多网关** —— "Add Gateway",选模板(LiteLLM、Higress、OpenAI 兼容,或按协议分别填 URL 的完全自定义),填 base URL 和 key 的环境变量名,Test、Sync。每个网关在选择器里有自己的路由。
|
|
50
|
+
- **管好模型列表** —— 非对话模型(图像 / 语音 / 向量 / 重排……)默认被正则排除;任意模型可隐藏、可改名;网关藏起来的模型可手工添加;每个模型的协议、上下文窗口、输出上限、推理档位都可改。
|
|
51
|
+
- **密钥放在 dsh 凭据存储里** —— 设置页有状态徽标(`✓ Key set · NEWAPI_API_KEY` / `⚠ No key set`),也可以直接帮你把 key 写进去。
|
|
127
52
|
|
|
128
53
|
## 配置参考
|
|
129
54
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
|
136
|
-
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
| `
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
160
|
-
##
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
55
|
+
全部可选 —— 下表每项都有可用默认值。配置写在 `~/.dsh/settings.yaml` 的 `llm-newapi:` 段(设置页改的就是同一批键)。常用项:
|
|
56
|
+
|
|
57
|
+
| 键 | 默认值 | 含义 |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `baseURL` | `https://api.newapi.ai` | 网关 base URL。环境变量兜底:`NEWAPI_BASE_URL`、`NEWAPI_API_URL`。 |
|
|
60
|
+
| `apiKeyEnv` | `NEWAPI_API_KEY` | key 存在哪个环境/凭据变量里。 |
|
|
61
|
+
| `label` | `NewAPI` | 选择器里显示的路由名。 |
|
|
62
|
+
| `flavor` | `newapi` | 仅作模板标签(`newapi` / `litellm` / `higress` / `openai-compatible` / `custom`)。 |
|
|
63
|
+
| `gateways` | — | 额外网关数组:`{ id, baseURL, apiKeyEnv, label, … }`,每个成为一条 `gateway:<id>` 路由。 |
|
|
64
|
+
| `models` | — | 按模型覆盖:`{ id, name, disabled, protocol, contextWindow, maxTokens, reasoningLevels }`。 |
|
|
65
|
+
| `useModelsDev` / `modelsUrl` | `true` / models.dev | 参数增补来源(支持 `file:` URL 离线用)。 |
|
|
66
|
+
| `excludePatterns` | 图像/语音/…… | 要从选择器排除的模型 id 正则列表。 |
|
|
67
|
+
| `sortModelsByRelease` | `true` | 最新模型排前面。 |
|
|
68
|
+
| `catalogMode` | `auto` | `v1`(只用 `/v1/models`)/ `management`(newapi 用户 API)/ `auto`。 |
|
|
69
|
+
| `endpointPriority` | responses → anthropic → openai → gemini | 模型支持多种协议时的优先序。 |
|
|
70
|
+
| `openaiURL` / `responsesURL` / `anthropicURL` | — | 仅完全自定义网关:按协议分别填端点 URL;不填 = 该协议关闭。 |
|
|
71
|
+
| `maxTokens` / `defaultContextWindow` | `32768` / `128000` | models.dev 无数据时的兜底。 |
|
|
72
|
+
| `streamIdleTimeoutMs` | `600000` | 流式空闲超时。 |
|
|
73
|
+
| `headers` | — | 发往网关的额外 HTTP 头。 |
|
|
74
|
+
|
|
75
|
+
## 常见问题
|
|
76
|
+
|
|
77
|
+
| 症状 | 原因 → 处理 |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| 路由有了但模型为零 | 插件读不到模型列表。检查网关 base URL;newapi 网关若限制 `/v1/models`,试试 `catalogMode: "management"`。 |
|
|
80
|
+
| 每次请求都 `401` / 鉴权错误 | key 缺失或不对:看 Settings → Gateway Models 的徽标,或 `~/.dsh/.credentials.yaml` 里的 `NEWAPI_API_KEY`。 |
|
|
81
|
+
| 某模型的上下文窗口看着不对 | models.dev 没匹配上。在设置页编辑该模型(或写 `models:` 覆盖)。 |
|
|
82
|
+
| 单个模型工具调用不稳、格式怪 | 该模型被路由到了它处理不好的协议。在模型上钉死 `protocol`(`openai`、`openai-response`、`anthropic`、`gemini`)。 |
|
|
83
|
+
| 自定义网关各协议端点分开 | 用 `flavor: "custom"`,显式填 `openaiURL` / `responsesURL` / `anthropicURL`。 |
|
|
84
|
+
|
|
85
|
+
## 工作原理(一分钟版)
|
|
86
|
+
|
|
87
|
+
启动时,插件为每个网关注册一条 provider 路由,从网关拉取模型列表,再把每个模型 id 与 models.dev 模糊匹配补齐真实参数。选中模型后,dsh 的请求被翻译成 pi-ai SDK 的格式、按该模型的原生协议发出;流式回复再翻译回 dsh 的分块。模型目录按网关缓存(默认 30 分钟)。没有任何手写协议代码 —— 桥接层直接复用官方 `dsh-llm-pi-ai` 适配器。
|
|
88
|
+
|
|
89
|
+
## 开发
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
git clone https://github.com/Luck9Star/dsh-gateway-provider
|
|
93
|
+
cd dsh-gateway-provider
|
|
94
|
+
npm run link # 软链进 dsh profile(保证单实例)
|
|
95
|
+
npm run test:client # 设置页渲染测试,双语言
|
|
96
|
+
npm run test:urls # URL 派生单元测试
|
|
97
|
+
npm run smoke # 真实网关往返(需要真 key)
|
|
171
98
|
```
|
|
172
99
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
所以每个模型路由到自己的协议实现。`sdkBaseURL()` 为 OpenAI 协议模型补
|
|
176
|
-
`/v1`,Anthropic/Google 的基础地址保持不变。
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
dsh-gateway-provider/
|
|
180
|
-
├── index.js # 插件入口:Config 校验、provider 注册、设置/凭据接线
|
|
181
|
-
├── cordis.patch.yml # dsh.bundle 补丁(经 `dsh plugin add` 自动挂载)
|
|
182
|
-
├── lib/ # adapter、pi-provider、pi-bridge、catalog、modelsdev、thinking、client
|
|
183
|
-
├── test/ # 冒烟(直连网关)+ 离线单元 + 设置页渲染
|
|
184
|
-
└── scripts/link.sh # 本地检出时链接 profile 的 node_modules
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## 从本地检出开发
|
|
188
|
-
|
|
189
|
-
1. `bash scripts/link.sh` — 把本包 `node_modules` 软链到 profile 的,
|
|
190
|
-
保证 `@deepseek-ai/*` 裸导入解析到 harness 进程使用的同一份模块实例
|
|
191
|
-
(单实例 `instanceof` 安全)。
|
|
192
|
-
2. 把检出注册为 profile 的 link 依赖并安装:
|
|
100
|
+
从本地检出开发:把 profile 的 `package.json` 指向
|
|
101
|
+
`"dsh-gateway-provider": "link:/绝对/路径"`,然后在 profile 里重跑 `pnpm install`。**不要**再往 profile 自己的 `cordis.patch.yml` 里加 `id: llm-newapi` 行 —— bundle patch 已提供(重复行 = 加载器报错)。
|
|
193
102
|
|
|
194
|
-
|
|
195
|
-
cd "$DSH_HOME/profiles/web"
|
|
196
|
-
# package.json dependencies 追加:"dsh-gateway-provider": "link:/本包绝对路径/dsh-gateway-provider"
|
|
197
|
-
# 再把 "dsh-gateway-provider" 加进同一文件的 bundles 列表
|
|
198
|
-
pnpm install
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
3. 重启 profile。
|
|
202
|
-
|
|
203
|
-
> ⚠️ **不要**再往 profile 自己的 `cordis.patch.yml` 里插入 `id: llm-newapi`
|
|
204
|
-
> ——bundle 层已挂载,重复会导致启动时报 `duplicate loader entry id`。
|
|
205
|
-
|
|
206
|
-
客户端 bundle 改动刷新浏览器即生效;宿主侧改动需要重启 profile。
|
|
207
|
-
|
|
208
|
-
### 测试
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
node test/smoke.mjs # 直连网关:catalog / openai × 2 / 工具调用 / anthropic / gemini / custom-urls
|
|
212
|
-
node test/smoke.mjs --only custom-urls
|
|
213
|
-
node test/protocol-urls.mjs # 离线:URL 派生 + 网关解析单元测试
|
|
214
|
-
node test/client-render.mjs # 离线:设置页渲染树(中/英)
|
|
215
|
-
```
|
|
103
|
+
## 参考与致谢
|
|
216
104
|
|
|
217
|
-
|
|
218
|
-
|
|
105
|
+
- [pi-ai SDK](https://www.npmjs.com/package/@earendil-works/pi-ai) —— 四种协议全由它实现;桥接层复用官方 `dsh-llm-pi-ai` 适配器的翻译代码。
|
|
106
|
+
- [models.dev](https://models.dev) —— 参数目录(上下文窗口、输出上限、推理、发布日期)。
|
|
107
|
+
- [new-api](https://github.com/QuantumNous/new-api)、
|
|
108
|
+
[LiteLLM](https://github.com/BerriAI/litellm)、
|
|
109
|
+
[Higress](https://github.com/alibaba/higress) —— 本插件测试过的网关(任何 OpenAI 兼容端点都行)。
|
|
219
110
|
|
|
220
|
-
##
|
|
111
|
+
## 安全
|
|
221
112
|
|
|
222
|
-
|
|
223
|
-
绝不写入日志、配置或对话;凭据字段在 Web UI 中以掩码编辑。
|
|
224
|
-
- 无运行时 `dependencies`;`peerDependencies` 复用 harness 已装的
|
|
225
|
-
`@deepseek-ai/*` 与 `@earendil-works/pi-ai`。提交在本地(pre-commit)
|
|
226
|
-
与 CI 双层接受 gitleaks 扫描把关。
|
|
113
|
+
密钥只存放在 dsh 凭据存储或启动环境里 —— 绝不写进 settings YAML。仓库在 CI 与 pre-commit 中运行 [gitleaks](https://github.com/gitleaks/gitleaks) 防泄漏。
|
|
227
114
|
|
|
228
|
-
##
|
|
115
|
+
## 许可证
|
|
229
116
|
|
|
230
|
-
MIT
|
|
117
|
+
[MIT](../LICENSE)
|
package/lib/adapter.js
CHANGED
|
@@ -189,6 +189,7 @@ export class NewapiAdapter extends LlmAdapter {
|
|
|
189
189
|
...reasoning === undefined ? {} : { reasoning },
|
|
190
190
|
...options.temperature === undefined ? {} : { temperature: options.temperature },
|
|
191
191
|
...options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens },
|
|
192
|
+
...options.sessionId === undefined ? {} : { sessionId: String(options.sessionId) },
|
|
192
193
|
signal: watchdog.signal,
|
|
193
194
|
headers: requestHeaders(connection.headers),
|
|
194
195
|
maxRetries: 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-gateway-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Generic LLM gateway model provider plugin for DeepSeek Harness: mounts newapi / LiteLLM / Higress / any OpenAI-compatible gateway as provider routes, auto-discovers the model list, enriches every model with models.dev parameters, and dispatches each model over its native wire protocol (OpenAI / Anthropic / Gemini) via the pi-ai SDK",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"link": "bash scripts/link.sh",
|
|
28
28
|
"smoke": "node test/smoke.mjs",
|
|
29
|
+
"test:session": "node test/session-affinity.mjs",
|
|
29
30
|
"test:client": "node test/client-render.mjs",
|
|
30
31
|
"test:urls": "node test/protocol-urls.mjs",
|
|
31
32
|
"test:schema": "node test/schema-coercion.mjs",
|