dsh-gateway-provider 1.1.0 → 1.1.2
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 +4 -3
- package/docs/README.zh.md +4 -3
- package/index.js +13 -2
- package/lib/catalog.js +7 -6
- package/lib/client.js +13 -7
- package/lib/pi-provider.js +17 -4
- package/lib/protocols.js +22 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ the first start where the plugin loads. The frequently used ones:
|
|
|
92
92
|
|
|
93
93
|
| Key | Default | Meaning |
|
|
94
94
|
| --- | --- | --- |
|
|
95
|
-
| `baseURL` | `https://api.newapi.ai` | Your gateway's base URL. Env fallbacks: `NEWAPI_BASE_URL`, `NEWAPI_API_URL`. |
|
|
95
|
+
| `baseURL` | `https://api.newapi.ai` | Your gateway's base URL. Env fallbacks: `NEWAPI_BASE_URL`, `NEWAPI_API_URL`. A trailing `/v1` or `/v1beta` is tolerated (stripped for discovery); Gemini requests always go to `{base}/v1beta/models/…`. |
|
|
96
96
|
| `apiKeyEnv` | `NEWAPI_API_KEY` | Which env/credential variable holds the key. |
|
|
97
97
|
| `label` | `NewAPI` | Route label shown in the picker. |
|
|
98
98
|
| `flavor` | `newapi` | Template label only (`newapi` / `litellm` / `higress` / `openai-compatible` / `custom`). |
|
|
@@ -103,7 +103,7 @@ the first start where the plugin loads. The frequently used ones:
|
|
|
103
103
|
| `sortModelsByRelease` | `true` | Newest models first. |
|
|
104
104
|
| `catalogMode` | `auto` | `v1` (`/v1/models` only) / `management` (newapi user API) / `auto`. |
|
|
105
105
|
| `endpointPriority` | responses → anthropic → openai → gemini | Which protocol to prefer when a model supports several. |
|
|
106
|
-
| `openaiURL` / `responsesURL` / `anthropicURL` | — | Fully-custom gateways only: per-protocol endpoint URLs; unset = that protocol off. |
|
|
106
|
+
| `openaiURL` / `responsesURL` / `anthropicURL` / `geminiURL` | — | Fully-custom gateways only: per-protocol endpoint URLs; unset = that protocol off. `geminiURL` may be a bare host, `…/v1beta`, or `…/v1beta/models` — the `/v1beta` segment is ensured (pi-ai expects it in the base). |
|
|
107
107
|
| `maxTokens` / `defaultContextWindow` | `32768` / `128000` | Fallbacks when models.dev has no data. |
|
|
108
108
|
| `streamIdleTimeoutMs` | `600000` | Idle timeout while streaming. |
|
|
109
109
|
| `headers` | — | Extra HTTP headers sent to the gateway. |
|
|
@@ -116,7 +116,8 @@ the first start where the plugin loads. The frequently used ones:
|
|
|
116
116
|
| `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`. |
|
|
117
117
|
| A model's context window looks wrong | models.dev had no match. Edit the model on the settings page (or a `models:` override). |
|
|
118
118
|
| 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`). |
|
|
119
|
-
| Custom gateway with separate endpoints | Use `flavor: "custom"` and set `openaiURL` / `responsesURL` / `anthropicURL` explicitly. |
|
|
119
|
+
| Custom gateway with separate endpoints | Use `flavor: "custom"` and set `openaiURL` / `responsesURL` / `anthropicURL` / `geminiURL` explicitly. |
|
|
120
|
+
| Gemini model returns HTML / `Unexpected token '<'` (issue #4) | The gateway serves Gemini under `/v1beta`; fixed in 1.1.2 — a plain `baseURL` now sends Gemini to `{base}/v1beta/models/…` (a `baseURL` ending in `/v1beta` also works). No older-version workaround exists; update the plugin. |
|
|
120
121
|
|
|
121
122
|
## How it works (one minute version)
|
|
122
123
|
|
package/docs/README.zh.md
CHANGED
|
@@ -56,7 +56,7 @@ dsh --profile web
|
|
|
56
56
|
|
|
57
57
|
| 键 | 默认值 | 含义 |
|
|
58
58
|
| --- | --- | --- |
|
|
59
|
-
| `baseURL` | `https://api.newapi.ai` | 网关 base URL。环境变量兜底:`NEWAPI_BASE_URL`、`NEWAPI_API_URL
|
|
59
|
+
| `baseURL` | `https://api.newapi.ai` | 网关 base URL。环境变量兜底:`NEWAPI_BASE_URL`、`NEWAPI_API_URL`。结尾带 `/v1` 或 `/v1beta` 均可(发现时会剥掉);Gemini 请求固定发往 `{base}/v1beta/models/…`。 |
|
|
60
60
|
| `apiKeyEnv` | `NEWAPI_API_KEY` | key 存在哪个环境/凭据变量里。 |
|
|
61
61
|
| `label` | `NewAPI` | 选择器里显示的路由名。 |
|
|
62
62
|
| `flavor` | `newapi` | 仅作模板标签(`newapi` / `litellm` / `higress` / `openai-compatible` / `custom`)。 |
|
|
@@ -67,7 +67,7 @@ dsh --profile web
|
|
|
67
67
|
| `sortModelsByRelease` | `true` | 最新模型排前面。 |
|
|
68
68
|
| `catalogMode` | `auto` | `v1`(只用 `/v1/models`)/ `management`(newapi 用户 API)/ `auto`。 |
|
|
69
69
|
| `endpointPriority` | responses → anthropic → openai → gemini | 模型支持多种协议时的优先序。 |
|
|
70
|
-
| `openaiURL` / `responsesURL` / `anthropicURL` | — | 仅完全自定义网关:按协议分别填端点 URL;不填 =
|
|
70
|
+
| `openaiURL` / `responsesURL` / `anthropicURL` / `geminiURL` | — | 仅完全自定义网关:按协议分别填端点 URL;不填 = 该协议关闭。`geminiURL` 可填裸主机名、`…/v1beta` 或 `…/v1beta/models` —— `/v1beta` 段会自动确保(pi-ai 要求它含在 base 里)。 |
|
|
71
71
|
| `maxTokens` / `defaultContextWindow` | `32768` / `128000` | models.dev 无数据时的兜底。 |
|
|
72
72
|
| `streamIdleTimeoutMs` | `600000` | 流式空闲超时。 |
|
|
73
73
|
| `headers` | — | 发往网关的额外 HTTP 头。 |
|
|
@@ -80,7 +80,8 @@ dsh --profile web
|
|
|
80
80
|
| 每次请求都 `401` / 鉴权错误 | key 缺失或不对:看 Settings → Gateway Models 的徽标,或 `~/.dsh/.credentials.yaml` 里的 `NEWAPI_API_KEY`。 |
|
|
81
81
|
| 某模型的上下文窗口看着不对 | models.dev 没匹配上。在设置页编辑该模型(或写 `models:` 覆盖)。 |
|
|
82
82
|
| 单个模型工具调用不稳、格式怪 | 该模型被路由到了它处理不好的协议。在模型上钉死 `protocol`(`openai`、`openai-response`、`anthropic`、`gemini`)。 |
|
|
83
|
-
| 自定义网关各协议端点分开 | 用 `flavor: "custom"`,显式填 `openaiURL` / `responsesURL` / `anthropicURL`。 |
|
|
83
|
+
| 自定义网关各协议端点分开 | 用 `flavor: "custom"`,显式填 `openaiURL` / `responsesURL` / `anthropicURL` / `geminiURL`。 |
|
|
84
|
+
| Gemini 模型返回 HTML / `Unexpected token '<'`(issue #4) | 网关把 Gemini 挂在 `/v1beta` 下;1.1.2 已修复 —— 普通 `baseURL` 也会把 Gemini 请求发到 `{base}/v1beta/models/…`(`baseURL` 以 `/v1beta` 结尾同样可用)。旧版本没有任何绕过手段,请升级插件。 |
|
|
84
85
|
|
|
85
86
|
## 工作原理(一分钟版)
|
|
86
87
|
|
package/index.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Registers one or more `gateway:*` provider routes on `ctx.llm`, each backed
|
|
5
5
|
* by an OpenAI-compatible `/v1/models` gateway (newapi, LiteLLM, Higress, or
|
|
6
6
|
* any generic gateway), or by a fully-custom gateway whose per-protocol
|
|
7
|
-
* endpoint URLs (`openaiURL` / `responsesURL` / `anthropicURL`
|
|
7
|
+
* endpoint URLs (`openaiURL` / `responsesURL` / `anthropicURL` / `geminiURL`)
|
|
8
|
+
* are written
|
|
8
9
|
* out in full. Backwards-compatible with the original single-connection
|
|
9
10
|
* `newapi` route: the legacy flat fields (`baseURL`, `apiKeyEnv`, …) build a
|
|
10
11
|
* default gateway, while the `gateways` array adds more.
|
|
@@ -121,6 +122,12 @@ const GatewaySchema = z.object({
|
|
|
121
122
|
responsesURL: coercedString(),
|
|
122
123
|
/** Full Anthropic messages endpoint URL (custom template). */
|
|
123
124
|
anthropicURL: coercedString(),
|
|
125
|
+
/**
|
|
126
|
+
* Gemini native endpoint base (custom template): a bare host, a base
|
|
127
|
+
* ending in `/v1beta`, or one with a trailing `/models`. The `/v1beta`
|
|
128
|
+
* segment is ensured — pi-ai's Gemini client expects it in the base.
|
|
129
|
+
*/
|
|
130
|
+
geminiURL: coercedString(),
|
|
124
131
|
/** Environment-variable name (credential ref) holding the API key. */
|
|
125
132
|
apiKeyEnv: coercedString().role("credential-ref"),
|
|
126
133
|
/** Gateway type label (newapi/litellm/higress/openai-compatible/custom). */
|
|
@@ -149,6 +156,8 @@ export const Config = z.object({
|
|
|
149
156
|
responsesURL: coercedString(),
|
|
150
157
|
/** Full Anthropic messages endpoint URL (custom template for the default gateway). */
|
|
151
158
|
anthropicURL: coercedString(),
|
|
159
|
+
/** Gemini native endpoint base (custom template for the default gateway). */
|
|
160
|
+
geminiURL: coercedString(),
|
|
152
161
|
/** Gateway type label shown in the UI (newapi/litellm/higress/openai-compatible/custom). */
|
|
153
162
|
flavor: z.union(FLAVORS).default("newapi"),
|
|
154
163
|
/** models.dev catalog URL (any fetch-able URL; file: works for offline mirrors). */
|
|
@@ -193,7 +202,8 @@ export const Config = z.object({
|
|
|
193
202
|
/**
|
|
194
203
|
* Normalize one gateway entry into the connection facts the adapter needs.
|
|
195
204
|
* Per-gateway fields fall back to the shared defaults. Protocol URL fields
|
|
196
|
-
* (`openaiURL` / `responsesURL` / `anthropicURL`, the "custom"
|
|
205
|
+
* (`openaiURL` / `responsesURL` / `anthropicURL` / `geminiURL`, the "custom"
|
|
206
|
+
* template)
|
|
197
207
|
* redefine the gateway's protocol surface: each configured URL becomes the
|
|
198
208
|
* exact SDK base for its wire protocol, unconfigured protocols are
|
|
199
209
|
* unavailable, and the plain `baseURL` is no longer inherited from the
|
|
@@ -290,6 +300,7 @@ export function resolveGateways(config, environment) {
|
|
|
290
300
|
openaiURL: config.openaiURL,
|
|
291
301
|
responsesURL: config.responsesURL,
|
|
292
302
|
anthropicURL: config.anthropicURL,
|
|
303
|
+
geminiURL: config.geminiURL,
|
|
293
304
|
}, defaults, PROVIDER, defaultLabel) },
|
|
294
305
|
];
|
|
295
306
|
// Additional gateways from the array.
|
package/lib/catalog.js
CHANGED
|
@@ -122,15 +122,16 @@ async function fetchManagementModels(baseURL, apiKey, userId, signal) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* Strip trailing slashes and a trailing version segment (`/v1`, `/v2`,
|
|
126
|
-
* from a gateway base URL. The catalog always
|
|
127
|
-
* (`/v1/models`, `/api/user/models`), so a
|
|
128
|
-
* carries
|
|
129
|
-
*
|
|
125
|
+
* Strip trailing slashes and a trailing version segment (`/v1`, `/v2`, and
|
|
126
|
+
* the Gemini-style `/v1beta`) from a gateway base URL. The catalog always
|
|
127
|
+
* appends its own full paths (`/v1/models`, `/api/user/models`), so a
|
|
128
|
+
* user-supplied base that already carries a version (the SDK conventions,
|
|
129
|
+
* which the chat path also accepts) must not leak through as
|
|
130
|
+
* `/v1/v1/models` or `/v1beta/v1/models`. Mirrors the derivation the custom
|
|
130
131
|
* (protocol-URL) template performs in `protocols.js`.
|
|
131
132
|
*/
|
|
132
133
|
function stripVersion(baseURL) {
|
|
133
|
-
return String(baseURL ?? "").replace(/\/+$/, "").replace(/\/v\d
|
|
134
|
+
return String(baseURL ?? "").replace(/\/+$/, "").replace(/\/v\d+(?:beta)?$/i, "");
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
/**
|
package/lib/client.js
CHANGED
|
@@ -26,7 +26,7 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
var CATALOG_MODES = ["auto", "v1", "management"];
|
|
27
27
|
var PROTOCOLS = ["openai", "openai-response", "anthropic", "gemini"];
|
|
28
28
|
/** Gateway template fields (suffix "URL") → card protocol tag. */
|
|
29
|
-
var PROTOCOL_URL_KEYS = ["openaiURL", "responsesURL", "anthropicURL"];
|
|
29
|
+
var PROTOCOL_URL_KEYS = ["openaiURL", "responsesURL", "anthropicURL", "geminiURL"];
|
|
30
30
|
|
|
31
31
|
// ---- i18n (manual zh/en switch; no locale registration needed) ----
|
|
32
32
|
var ZH = (typeof navigator !== "undefined" && navigator.language && navigator.language.startsWith("zh"));
|
|
@@ -65,19 +65,21 @@ window.__ModuleLoader__.load({
|
|
|
65
65
|
tplHintLitellm: "LiteLLM Proxy:/v1/chat/completions 与 /v1/responses,经 /v1/models 自动发现",
|
|
66
66
|
tplHintHigress: "Higress AI 网关:OpenAI 格式为主,新版本同端口支持 Anthropic /v1/messages",
|
|
67
67
|
tplHintOpenaiCompatible: "任意 OpenAI 兼容网关:填一个基础地址,各协议路径自动派生",
|
|
68
|
-
tplHintCustom: "
|
|
68
|
+
tplHintCustom: "各协议地址分别填写(Gemini 可只填主机名,自动补 /v1beta);留空的协议不启用,模型仅以已启用协议请求",
|
|
69
69
|
tplNewapi: "NewAPI", tplLitellm: "LiteLLM", tplHigress: "Higress",
|
|
70
70
|
tplOpenaiCompatible: "OpenAI 兼容", tplCustom: "完全自定义",
|
|
71
|
-
openaiURL: "OpenAI 兼容地址", responsesURL: "Responses 地址", anthropicURL: "Anthropic 地址",
|
|
71
|
+
openaiURL: "OpenAI 兼容地址", responsesURL: "Responses 地址", anthropicURL: "Anthropic 地址", geminiURL: "Gemini 地址",
|
|
72
72
|
openaiURLHint: "完整地址,以 /chat/completions 结尾(也可只填到 /v1)",
|
|
73
73
|
responsesURLHint: "完整地址,以 /responses 结尾(也可只填到 /v1)",
|
|
74
74
|
anthropicURLHint: "完整地址,以 /v1/messages 结尾",
|
|
75
|
+
geminiURLHint: "以 /v1beta 结尾(也可只填主机名,自动补 /v1beta)",
|
|
75
76
|
customNeedOne: "至少填写一个协议地址",
|
|
76
77
|
customURLPlaceholder: "https://gw.example.com/v1/chat/completions",
|
|
77
78
|
responsesURLPlaceholder: "https://gw.example.com/v1/responses",
|
|
78
79
|
anthropicURLPlaceholder: "https://gw.example.com/v1/messages",
|
|
80
|
+
geminiURLPlaceholder: "https://gw.example.com/v1beta",
|
|
79
81
|
catalogHint: "auto:优先 /v1/models,鉴权失败时回退管理 API",
|
|
80
|
-
baseURLClearHint: "留空则回退到环境变量 NEWAPI_BASE_URL 或官方云端地址;结尾带不带 /v1 均可",
|
|
82
|
+
baseURLClearHint: "留空则回退到环境变量 NEWAPI_BASE_URL 或官方云端地址;结尾带不带 /v1 或 /v1beta 均可",
|
|
81
83
|
labelClearHint: "留空则回退为 gateway:<id>",
|
|
82
84
|
keyEnvHint: "留空则继承默认网关的 Key(NEWAPI_API_KEY)",
|
|
83
85
|
keyKeepHint: "留空保持不变;填写则覆盖存储的 Key",
|
|
@@ -125,19 +127,21 @@ window.__ModuleLoader__.load({
|
|
|
125
127
|
tplHintLitellm: "LiteLLM proxy: /v1/chat/completions and /v1/responses; discovery via /v1/models",
|
|
126
128
|
tplHintHigress: "Higress AI gateway: OpenAI format; newer versions also serve Anthropic /v1/messages",
|
|
127
129
|
tplHintOpenaiCompatible: "Any OpenAI-compatible gateway: one base address, protocol paths derived automatically",
|
|
128
|
-
tplHintCustom: "Enter each protocol's
|
|
130
|
+
tplHintCustom: "Enter each protocol's URL (Gemini accepts a bare host; /v1beta is added automatically); empty protocols stay disabled and models use enabled protocols only",
|
|
129
131
|
tplNewapi: "NewAPI", tplLitellm: "LiteLLM", tplHigress: "Higress",
|
|
130
132
|
tplOpenaiCompatible: "OpenAI-compatible", tplCustom: "Fully custom",
|
|
131
|
-
openaiURL: "OpenAI-compatible URL", responsesURL: "Responses URL", anthropicURL: "Anthropic URL",
|
|
133
|
+
openaiURL: "OpenAI-compatible URL", responsesURL: "Responses URL", anthropicURL: "Anthropic URL", geminiURL: "Gemini URL",
|
|
132
134
|
openaiURLHint: "Full address ending in /chat/completions (a /v1 base also works)",
|
|
133
135
|
responsesURLHint: "Full address ending in /responses (a /v1 base also works)",
|
|
134
136
|
anthropicURLHint: "Full address ending in /v1/messages",
|
|
137
|
+
geminiURLHint: "Ending in /v1beta (a bare host also works; /v1beta is added)",
|
|
135
138
|
customNeedOne: "Fill at least one protocol URL",
|
|
136
139
|
customURLPlaceholder: "https://gw.example.com/v1/chat/completions",
|
|
137
140
|
responsesURLPlaceholder: "https://gw.example.com/v1/responses",
|
|
138
141
|
anthropicURLPlaceholder: "https://gw.example.com/v1/messages",
|
|
142
|
+
geminiURLPlaceholder: "https://gw.example.com/v1beta",
|
|
139
143
|
catalogHint: "auto: prefer /v1/models, fall back to the management API on auth failure",
|
|
140
|
-
baseURLClearHint: "Empty falls back to NEWAPI_BASE_URL or the public cloud default; a trailing /v1 is optional",
|
|
144
|
+
baseURLClearHint: "Empty falls back to NEWAPI_BASE_URL or the public cloud default; a trailing /v1 or /v1beta is optional",
|
|
141
145
|
labelClearHint: "Empty falls back to gateway:<id>",
|
|
142
146
|
keyEnvHint: "Empty inherits the default gateway's key (NEWAPI_API_KEY)",
|
|
143
147
|
keyKeepHint: "Empty keeps the stored key; type to overwrite",
|
|
@@ -166,6 +170,7 @@ window.__ModuleLoader__.load({
|
|
|
166
170
|
openaiURL: { label: T.openaiURL, hint: T.openaiURLHint, ph: T.customURLPlaceholder, tag: "openai" },
|
|
167
171
|
responsesURL: { label: T.responsesURL, hint: T.responsesURLHint, ph: T.responsesURLPlaceholder, tag: "responses" },
|
|
168
172
|
anthropicURL: { label: T.anthropicURL, hint: T.anthropicURLHint, ph: T.anthropicURLPlaceholder, tag: "anthropic" },
|
|
173
|
+
geminiURL: { label: T.geminiURL, hint: T.geminiURLHint, ph: T.geminiURLPlaceholder, tag: "gemini" },
|
|
169
174
|
};
|
|
170
175
|
|
|
171
176
|
/** Protocol tags a gateway card shows (configured URL fields only). */
|
|
@@ -346,6 +351,7 @@ window.__ModuleLoader__.load({
|
|
|
346
351
|
openaiURL: protoOf(isDefault ? props.snapshot : gw, "openaiURL"),
|
|
347
352
|
responsesURL: protoOf(isDefault ? props.snapshot : gw, "responsesURL"),
|
|
348
353
|
anthropicURL: protoOf(isDefault ? props.snapshot : gw, "anthropicURL"),
|
|
354
|
+
geminiURL: protoOf(isDefault ? props.snapshot : gw, "geminiURL"),
|
|
349
355
|
apiKey: "",
|
|
350
356
|
};
|
|
351
357
|
var _v = React.useState(initial); var values = _v[0]; var setValues = _v[1];
|
package/lib/pi-provider.js
CHANGED
|
@@ -52,17 +52,30 @@ const PROTOCOLS = {
|
|
|
52
52
|
* `/responses`. A gateway baseURL without `/v1` makes the SDK request
|
|
53
53
|
* `{base}/responses`, which the gateway answers with its HTML UI (HTTP 200,
|
|
54
54
|
* content-type text/html) instead of an SSE stream, so the SDK receives zero
|
|
55
|
-
* events.
|
|
56
|
-
*
|
|
55
|
+
* events.
|
|
56
|
+
*
|
|
57
|
+
* pi-ai's Gemini client follows the same convention with `/v1beta`: whenever
|
|
58
|
+
* a model carries a baseUrl it sets `httpOptions.apiVersion = ""` (its own
|
|
59
|
+
* Google provider ships `…googleapis.com/v1beta` as the base), so the version
|
|
60
|
+
* segment must come from the base itself. Without it the SDK requests
|
|
61
|
+
* `{base}/models/{model}:generateContent`, which gateways serving Gemini
|
|
62
|
+
* under `/v1beta` (e.g. new-api) answer with their HTML UI fallback.
|
|
63
|
+
* The Anthropic SDK builds its own full path, so it needs no version appended.
|
|
57
64
|
* @param baseURL - the gateway base URL as configured.
|
|
58
65
|
* @param api - the pi-ai API identifier the model will use.
|
|
59
|
-
* @returns the baseURL the
|
|
66
|
+
* @returns the baseURL the SDK should point at.
|
|
60
67
|
*/
|
|
61
|
-
function sdkBaseURL(baseURL, api) {
|
|
68
|
+
export function sdkBaseURL(baseURL, api) {
|
|
62
69
|
const trimmed = String(baseURL ?? "").replace(/\/+$/, "");
|
|
63
70
|
if (api === "openai-completions" || api === "openai-responses") {
|
|
64
71
|
if (!/\/v\d+$/.test(trimmed)) return `${trimmed}/v1`;
|
|
65
72
|
}
|
|
73
|
+
// pi-ai pins apiVersion = "" when a baseUrl is set, so the version segment
|
|
74
|
+
// must already live in the base (issue #4). Case-insensitive, matching the
|
|
75
|
+
// anthropic branch's tolerance in protocols.js.
|
|
76
|
+
if (api === "google-generative-ai" && !/\/v\d+beta$/i.test(trimmed)) {
|
|
77
|
+
return `${trimmed}/v1beta`;
|
|
78
|
+
}
|
|
66
79
|
return trimmed;
|
|
67
80
|
}
|
|
68
81
|
|
package/lib/protocols.js
CHANGED
|
@@ -24,6 +24,10 @@ const ENDPOINT_SUFFIXES = {
|
|
|
24
24
|
"openai-completions": "/chat/completions",
|
|
25
25
|
"openai-responses": "/responses",
|
|
26
26
|
"anthropic-messages": "/v1/messages",
|
|
27
|
+
// Gemini has no fixed endpoint suffix (the path embeds the model id:
|
|
28
|
+
// `{base}/v1beta/models/{model}:generateContent`), so only a trailing
|
|
29
|
+
// `/models` — copied from the docs' list URL — is tolerated.
|
|
30
|
+
"google-generative-ai": "/models",
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
/** Trim a configured URL and strip one known endpoint suffix if present. */
|
|
@@ -36,14 +40,16 @@ function stripSuffix(url, suffix) {
|
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* Derive the per-protocol facts from fully-qualified endpoint URLs
|
|
39
|
-
* (`openaiURL` / `responsesURL` / `anthropicURL`). Unset URLs
|
|
40
|
-
* protocol is unavailable on this gateway ("没填的就是没有"): the
|
|
41
|
-
* `availableTypes` is exactly the configured set.
|
|
43
|
+
* (`openaiURL` / `responsesURL` / `anthropicURL` / `geminiURL`). Unset URLs
|
|
44
|
+
* mean the protocol is unavailable on this gateway ("没填的就是没有"): the
|
|
45
|
+
* returned `availableTypes` is exactly the configured set.
|
|
42
46
|
*
|
|
43
47
|
* Each URL may be the complete endpoint address (`…/v1/chat/completions`),
|
|
44
48
|
* an SDK base (`…/v1`), or — for anthropic — a bare host; the derivation
|
|
45
49
|
* strips the known suffix and re-adds the version segment where the SDK
|
|
46
|
-
* expects it.
|
|
50
|
+
* expects it. Gemini URLs accept a bare host, a base ending in `/v1beta`,
|
|
51
|
+
* or one with a trailing `/models`; the `/v1beta` segment is always ensured
|
|
52
|
+
* (pi-ai's Gemini client disables its own apiVersion when baseUrl is set).
|
|
47
53
|
* @param urls - the gateway's protocol URL fields (any may be absent).
|
|
48
54
|
* @returns `{ apiBases, availableTypes, catalogBase }` where apiBases maps
|
|
49
55
|
* pi-ai API id → SDK base URL, availableTypes lists gateway endpoint
|
|
@@ -80,7 +86,19 @@ export function deriveProtocolURLs(urls) {
|
|
|
80
86
|
availableTypes.push("anthropic");
|
|
81
87
|
}
|
|
82
88
|
}
|
|
89
|
+
// Gemini: pi-ai's client pins `apiVersion = ""` whenever the model carries
|
|
90
|
+
// a baseUrl (mirroring its builtin Google provider, whose baseUrl is
|
|
91
|
+
// `https://generativelanguage.googleapis.com/v1beta`), so the version
|
|
92
|
+
// segment must live in the base. Accept a bare host (append `/v1beta`), a
|
|
93
|
+
// base already ending in `/v1beta`, or one with a trailing `/models`.
|
|
94
|
+
const gm = stripSuffix(urls.geminiURL, ENDPOINT_SUFFIXES["google-generative-ai"]);
|
|
95
|
+
if (gm !== undefined) {
|
|
96
|
+
apiBases["google-generative-ai"] = /\/v\d+beta$/i.test(gm) ? gm : `${gm}/v1beta`;
|
|
97
|
+
availableTypes.push("gemini");
|
|
98
|
+
}
|
|
83
99
|
if (availableTypes.length === 0) return undefined;
|
|
100
|
+
// Gemini has no OpenAI-shaped `/v1/models` list endpoint, so only the
|
|
101
|
+
// OpenAI-style URLs contribute the discovery base.
|
|
84
102
|
for (const api of ["openai-completions", "openai-responses"]) {
|
|
85
103
|
if (apiBases[api] !== undefined) { catalogBase = apiBases[api].replace(/\/v\d+$/, ""); break; }
|
|
86
104
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-gateway-provider",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
72
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
73
|
-
"@deepseek-ai/dsh-credentials": "0.2.
|
|
74
|
-
"@deepseek-ai/dsh-launch-environment": "0.2.
|
|
75
|
-
"@deepseek-ai/dsh-llm": "0.2.
|
|
76
|
-
"@deepseek-ai/dsh-timeout": "0.2.
|
|
73
|
+
"@deepseek-ai/dsh-credentials": "0.2.1-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.1-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-timeout": "0.2.1-rc.2",
|
|
77
77
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
78
78
|
}
|
|
79
79
|
}
|