@goodandready/dsh-opencode-provider 0.5.31 → 0.5.32
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/CHANGELOG.md +6 -0
- package/README.md +5 -4
- package/README.ru.md +5 -4
- package/README.zh.md +5 -4
- package/lib/client.js +150 -2
- package/lib/index.d.ts +13 -0
- package/lib/index.js +145 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.32 — release 9: proxy auth and route switches
|
|
4
|
+
|
|
5
|
+
- **#71 — authenticated proxy**: `proxyUsername` plus a password stored in the credential service enable Basic auth for HTTP/HTTPS and SOCKS5 proxies. The authenticated URL exists only in memory; `/state` returns the credential-free address and a `configured` flag, never the password.
|
|
6
|
+
- **#80 — route switches**: `goEnabled` and `consoleEnabled` are exposed in Settings. Disabling a route removes it from the provider directory, the picker and request handling at runtime; at least one route stays enabled.
|
|
7
|
+
- 144 tests.
|
|
8
|
+
|
|
3
9
|
## 0.5.31 — release 8: command localization
|
|
4
10
|
|
|
5
11
|
- **#48 — `/opencode` localization**: new `commandLocale` setting (`en` default, `zh`) localizes status, usage, accounts, models, rules and errors. Exposed in Settings → OpenCode.
|
package/README.md
CHANGED
|
@@ -99,7 +99,8 @@ graph LR
|
|
|
99
99
|
- **Request heatmap** in the quota panel: a Monday-first weekday × hour grid in local time, plus a 24-hour aggregate strip underneath. Selectors switch between all requests, any single model, and any single account. A per-account table shows requests, errors and average latency/TTFT. The adapter appends `{at, provider, model, purpose?, account, reason, ok, latencyMs, ttftMs?, error?}` to `~/.dsh/storages/dsh-opencode/requests.jsonl`; no prompt, response or key is stored. Auxiliary `session-title`/`compaction` calls are excluded from the heatmap and exact duplicate records collapse. The log compacts at 2 MB to the newest 5000 lines. Collection starts with this build — the projection cache only has `lastPromptAt`, not per-request timestamps.
|
|
100
100
|
- **Stable `x-opencode-session`** derived from the durable session id, so retries and every turn of a conversation reuse the same routing key.
|
|
101
101
|
- **Live catalog** from `/v1/models` with a generated `models.dev` fallback.
|
|
102
|
-
- **Proxy support**: one live `proxyUrl` setting routes inference, model discovery and usage checks through HTTP, HTTPS or SOCKS5; the dispatcher is reused until the address changes.
|
|
102
|
+
- **Proxy support**: one live `proxyUrl` setting routes inference, model discovery and usage checks through HTTP, HTTPS or SOCKS5; the dispatcher is reused until the address changes. `proxyUsername` and a password stored in the credential service add Basic auth for both HTTP and SOCKS5. Passwords are never returned to the browser.
|
|
103
|
+
- **Route switches**: enable or disable the Console/Zen and Go/Go Plus routes independently; a disabled route is removed from the picker and refuses requests. At least one route stays enabled.
|
|
103
104
|
- **Per-model subscription limits** from the OpenCode Go pricing table: monthly dollar allowance plus estimated requests for the 5-hour, weekly and monthly windows, for both Go and Go Plus. Shown in the model picker and the settings page; estimates only — the gateway remains the authority.
|
|
104
105
|
- **`/opencode` command** for status, usage, accounts, models, rules, and `test [model]`.
|
|
105
106
|
- **TUI settings section** for the terminal profile (`/settings → OpenCode`) with the key, endpoints, proxy, allowlist, active account, fallback model, sticky routing and timeouts.
|
|
@@ -132,12 +133,12 @@ npm pack
|
|
|
132
133
|
Install the **packed tarball** into a profile:
|
|
133
134
|
|
|
134
135
|
```bash
|
|
135
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
136
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.32.tgz
|
|
136
137
|
# DSH Desktop:
|
|
137
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
138
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.32.tgz
|
|
138
139
|
|
|
139
140
|
# Or install the published package:
|
|
140
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
141
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.32
|
|
141
142
|
```
|
|
142
143
|
|
|
143
144
|
Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (plugin)** in the model picker.
|
package/README.ru.md
CHANGED
|
@@ -108,7 +108,8 @@ graph LR
|
|
|
108
108
|
- **Почасовой Heatmap запросов**: сетка «день недели × час» в локальном времени плюс суточная полоса агрегации. Фильтры переключают отображение между всеми запросами, конкретной моделью и аккаунтом. Лог `~/.dsh/storages/dsh-opencode/requests.jsonl` хранит только метаданные `{at, provider, model, purpose, account, reason, ok, latencyMs, ttftMs, error}` (промпты и ответы не сохраняются).
|
|
109
109
|
- **Стабильный заголовок `x-opencode-session`**: вычисляется из устойчивого ID сессии DSH, обеспечивая одинаковый ключ маршрутизации при повторах и диалоге.
|
|
110
110
|
- **Живой каталог моделей**: автоматическое чтение `/v1/models` с резервным fallback из `models.dev`.
|
|
111
|
-
- **Proxy**: live-параметр `proxyUrl` направляет инференс, каталог и проверку квот через HTTP, HTTPS или SOCKS5; dispatcher переиспользуется до смены адреса.
|
|
111
|
+
- **Proxy**: live-параметр `proxyUrl` направляет инференс, каталог и проверку квот через HTTP, HTTPS или SOCKS5; dispatcher переиспользуется до смены адреса. `proxyUsername` и пароль из credential-сервиса включают Basic auth для HTTP и SOCKS5. Пароль не возвращается в браузер.
|
|
112
|
+
- **Переключатели маршрутов**: Console/Zen и Go/Go Plus включаются и отключаются независимо; отключённый маршрут исчезает из выбора и отклоняет запросы. Хотя бы один маршрут остаётся включённым.
|
|
112
113
|
- **Оценки лимитов подписки OpenCode Go**: долларовый лимит и расчетное число запросов для окон 5h, недели и месяца для планов Go и Go Plus.
|
|
113
114
|
- **Команда `/opencode`**: быстрый просмотр статуса, квот, аккаунтов, моделей, правил и запуск теста связи `test [model]`.
|
|
114
115
|
- **Настройки в терминале (TUI)**: раздел `/settings → OpenCode` для профиля terminal с ключом, эндпоинтами, allowlist, активным аккаунтом, fallback-моделью, sticky-маршрутизацией и таймаутами.
|
|
@@ -131,12 +132,12 @@ npm pack
|
|
|
131
132
|
Установка из **упакованного tarball**:
|
|
132
133
|
|
|
133
134
|
```bash
|
|
134
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
135
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.32.tgz
|
|
135
136
|
# DSH Desktop:
|
|
136
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
137
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.32.tgz
|
|
137
138
|
|
|
138
139
|
# Либо установка опубликованного пакета из npm:
|
|
139
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
140
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.32
|
|
140
141
|
```
|
|
141
142
|
|
|
142
143
|
После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode (plugin)** или **OpenCode Go (plugin)** в списке моделей.
|
package/README.zh.md
CHANGED
|
@@ -106,7 +106,8 @@ graph LR
|
|
|
106
106
|
- **本地时间请求热力图**:星期×小时交互式热力图,排除系统心跳与重复记录,可按模型或账户过滤,仅保存 `{at, provider, model, purpose, account, reason, ok, latencyMs, ttftMs, error}` 元数据,不保存任何提示词与回复。
|
|
107
107
|
- **稳定的 `x-opencode-session` 标头**:根据 DSH 会话 ID 派生,确保重试与连续对话复用相同的网关路由键。
|
|
108
108
|
- **实时模型目录**:自动从 `/v1/models` 同步,并以 `models.dev` 生成文件作为回退。
|
|
109
|
-
- **代理**:一个实时 `proxyUrl` 设置即可让推理、模型目录和额度检查走 HTTP、HTTPS 或 SOCKS5;地址变化前复用 dispatcher
|
|
109
|
+
- **代理**:一个实时 `proxyUrl` 设置即可让推理、模型目录和额度检查走 HTTP、HTTPS 或 SOCKS5;地址变化前复用 dispatcher。`proxyUsername` 与凭据服务中的密码可为 HTTP 和 SOCKS5 启用 Basic 认证,密码不会返回浏览器。
|
|
110
|
+
- **路由开关**:可独立启用或禁用 Console/Zen 与 Go/Go Plus 路由;禁用的路由会从选择器移除并拒绝请求,至少保留一个路由。
|
|
110
111
|
- **Go 订阅额度估算**:每月美元额度以及 5 小时、每周和每月的估算请求次数。
|
|
111
112
|
- **`/opencode` 斜杠指令**:快速查询状态、配额、账户、模型及执行联通性测试。
|
|
112
113
|
- **TUI 设置**:`/settings → OpenCode` 支持密钥、端点、allowlist、活动账户、回退模型、粘性路由和超时。
|
|
@@ -126,12 +127,12 @@ npm pack
|
|
|
126
127
|
通过**打包文件 (.tgz)**安装:
|
|
127
128
|
|
|
128
129
|
```bash
|
|
129
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
130
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.32.tgz
|
|
130
131
|
# DSH 桌面端:
|
|
131
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
132
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.32.tgz
|
|
132
133
|
|
|
133
134
|
# 或从 npm 安装已发布版本:
|
|
134
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
135
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.32
|
|
135
136
|
```
|
|
136
137
|
|
|
137
138
|
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
|
package/lib/client.js
CHANGED
|
@@ -190,7 +190,18 @@ window.__ModuleLoader__.load({
|
|
|
190
190
|
commandLocale: "/opencode command language",
|
|
191
191
|
commandLocaleHint: "Language for /opencode status, accounts, usage, models and rules.",
|
|
192
192
|
proxyUrl: "Proxy URL",
|
|
193
|
-
proxyUrlHint: "HTTP, HTTPS or SOCKS5 proxy for inference, catalog and usage. Empty disables it.
|
|
193
|
+
proxyUrlHint: "HTTP, HTTPS or SOCKS5 proxy for inference, catalog and usage. Empty disables it. Do not put credentials in this URL.",
|
|
194
|
+
proxyUsername: "Proxy username",
|
|
195
|
+
proxyPassword: "Proxy password",
|
|
196
|
+
proxyPasswordSet: "configured — enter a new one to replace it",
|
|
197
|
+
proxyPasswordUnset: "not configured",
|
|
198
|
+
proxyPasswordHint: "Stored in the credential service, never returned to the browser.",
|
|
199
|
+
saveProxyPassword: "Save proxy password",
|
|
200
|
+
clearProxyPassword: "Clear proxy password",
|
|
201
|
+
enabledRoutes: "Enabled routes",
|
|
202
|
+
routeConsole: "OpenCode Console / Zen",
|
|
203
|
+
routeGo: "OpenCode Go / Go Plus",
|
|
204
|
+
enabledRoutesHint: "Disabled routes are removed from the picker; at least one route stays enabled.",
|
|
194
205
|
maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
|
|
195
206
|
contextOverrideShort: "Context",
|
|
196
207
|
maxOutputOverrideShort: "Max output",
|
|
@@ -426,7 +437,18 @@ window.__ModuleLoader__.load({
|
|
|
426
437
|
commandLocale: "/opencode 命令语言",
|
|
427
438
|
commandLocaleHint: "用于 /opencode 的状态、账户、用量、模型和规则输出。",
|
|
428
439
|
proxyUrl: "代理地址",
|
|
429
|
-
proxyUrlHint: "用于推理、目录和用量的 HTTP、HTTPS 或 SOCKS5
|
|
440
|
+
proxyUrlHint: "用于推理、目录和用量的 HTTP、HTTPS 或 SOCKS5 代理。留空则禁用。请勿在此 URL 中填写凭据。",
|
|
441
|
+
proxyUsername: "代理用户名",
|
|
442
|
+
proxyPassword: "代理密码",
|
|
443
|
+
proxyPasswordSet: "已配置 — 输入新密码可替换",
|
|
444
|
+
proxyPasswordUnset: "未配置",
|
|
445
|
+
proxyPasswordHint: "保存在凭据服务中,不会返回给浏览器。",
|
|
446
|
+
saveProxyPassword: "保存代理密码",
|
|
447
|
+
clearProxyPassword: "清除代理密码",
|
|
448
|
+
enabledRoutes: "启用的路由",
|
|
449
|
+
routeConsole: "OpenCode Console / Zen",
|
|
450
|
+
routeGo: "OpenCode Go / Go Plus",
|
|
451
|
+
enabledRoutesHint: "禁用的路由会从选择器中移除;至少保留一个启用的路由。",
|
|
430
452
|
maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
|
|
431
453
|
contextOverrideShort: "上下文",
|
|
432
454
|
maxOutputOverrideShort: "最大输出",
|
|
@@ -556,6 +578,18 @@ window.__ModuleLoader__.load({
|
|
|
556
578
|
body: JSON.stringify({ ref })
|
|
557
579
|
});
|
|
558
580
|
}
|
|
581
|
+
function saveProxyPassword(password) {
|
|
582
|
+
return call("/dsh-opencode/proxy-password", {
|
|
583
|
+
method: "POST",
|
|
584
|
+
body: JSON.stringify({ password })
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function clearProxyPassword() {
|
|
588
|
+
return call("/dsh-opencode/proxy-password/clear", {
|
|
589
|
+
method: "POST",
|
|
590
|
+
body: "{}"
|
|
591
|
+
});
|
|
592
|
+
}
|
|
559
593
|
function importAuth(entry, ref) {
|
|
560
594
|
return call("/dsh-opencode/auth-import", {
|
|
561
595
|
method: "POST",
|
|
@@ -1718,6 +1752,9 @@ window.__ModuleLoader__.load({
|
|
|
1718
1752
|
requestImagePixelBudget: String(state.config.requestImagePixelBudget),
|
|
1719
1753
|
requestImageMaxBytes: String(state.config.requestImageMaxBytes),
|
|
1720
1754
|
proxyUrl: state.config.proxyUrl,
|
|
1755
|
+
proxyUsername: state.config.proxyUsername,
|
|
1756
|
+
goEnabled: state.config.goEnabled,
|
|
1757
|
+
consoleEnabled: state.config.consoleEnabled,
|
|
1721
1758
|
catalogRefreshMinutes: String(state.config.catalogRefreshMinutes),
|
|
1722
1759
|
commandLocale: state.config.commandLocale,
|
|
1723
1760
|
maxOutputTokens: state.config.maxOutputTokens === null ? "" : String(state.config.maxOutputTokens),
|
|
@@ -1772,6 +1809,9 @@ window.__ModuleLoader__.load({
|
|
|
1772
1809
|
requestImagePixelBudget: numberOrNull(draft.requestImagePixelBudget) ?? 2458624,
|
|
1773
1810
|
requestImageMaxBytes: numberOrNull(draft.requestImageMaxBytes) ?? 1048576,
|
|
1774
1811
|
proxyUrl: draft.proxyUrl.trim(),
|
|
1812
|
+
proxyUsername: draft.proxyUsername.trim(),
|
|
1813
|
+
goEnabled: draft.goEnabled,
|
|
1814
|
+
consoleEnabled: draft.consoleEnabled,
|
|
1775
1815
|
catalogRefreshMinutes: numberOrNull(draft.catalogRefreshMinutes) ?? 10,
|
|
1776
1816
|
commandLocale: draft.commandLocale,
|
|
1777
1817
|
maxOutputTokens: numberOrNull(draft.maxOutputTokens),
|
|
@@ -2014,6 +2054,8 @@ window.__ModuleLoader__.load({
|
|
|
2014
2054
|
const [error, setError] = (0, react.useState)(null);
|
|
2015
2055
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
2016
2056
|
const [keyDraft, setKeyDraft] = (0, react.useState)("");
|
|
2057
|
+
const [proxyPassword, setProxyPassword] = (0, react.useState)("");
|
|
2058
|
+
const [proxyBusy, setProxyBusy] = (0, react.useState)(false);
|
|
2017
2059
|
const [keyDrafts, setKeyDrafts] = (0, react.useState)({});
|
|
2018
2060
|
const [search, setSearch] = (0, react.useState)("");
|
|
2019
2061
|
const [testModel, setTestModel] = (0, react.useState)("deepseek-v4.1-flash");
|
|
@@ -2077,6 +2119,34 @@ window.__ModuleLoader__.load({
|
|
|
2077
2119
|
setSaving(false);
|
|
2078
2120
|
}
|
|
2079
2121
|
};
|
|
2122
|
+
const storeProxyPassword = async () => {
|
|
2123
|
+
if (proxyPassword === "") return;
|
|
2124
|
+
setProxyBusy(true);
|
|
2125
|
+
setError(null);
|
|
2126
|
+
try {
|
|
2127
|
+
await saveProxyPassword(proxyPassword);
|
|
2128
|
+
setProxyPassword("");
|
|
2129
|
+
setNotice(t("saved"));
|
|
2130
|
+
await load();
|
|
2131
|
+
} catch (caught) {
|
|
2132
|
+
setError(caught instanceof Error ? caught.message : t("failed"));
|
|
2133
|
+
} finally {
|
|
2134
|
+
setProxyBusy(false);
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
const removeProxyPassword = async () => {
|
|
2138
|
+
setProxyBusy(true);
|
|
2139
|
+
setError(null);
|
|
2140
|
+
try {
|
|
2141
|
+
await clearProxyPassword();
|
|
2142
|
+
setNotice(t("saved"));
|
|
2143
|
+
await load();
|
|
2144
|
+
} catch (caught) {
|
|
2145
|
+
setError(caught instanceof Error ? caught.message : t("failed"));
|
|
2146
|
+
} finally {
|
|
2147
|
+
setProxyBusy(false);
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2080
2150
|
const checkKey = async (label) => {
|
|
2081
2151
|
setChecking(label);
|
|
2082
2152
|
setError(null);
|
|
@@ -3245,6 +3315,84 @@ window.__ModuleLoader__.load({
|
|
|
3245
3315
|
value: draft.proxyUrl,
|
|
3246
3316
|
onChange: (value) => update({ proxyUrl: value })
|
|
3247
3317
|
}),
|
|
3318
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3319
|
+
label: t("proxyUsername"),
|
|
3320
|
+
value: draft.proxyUsername,
|
|
3321
|
+
onChange: (value) => update({ proxyUsername: value })
|
|
3322
|
+
}),
|
|
3323
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3324
|
+
className: "oc-field",
|
|
3325
|
+
children: [
|
|
3326
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3327
|
+
className: "oc-label",
|
|
3328
|
+
children: t("proxyPassword")
|
|
3329
|
+
}),
|
|
3330
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3331
|
+
className: "oc-input",
|
|
3332
|
+
type: "password",
|
|
3333
|
+
value: proxyPassword,
|
|
3334
|
+
placeholder: state.proxy.configured ? t("proxyPasswordSet") : t("proxyPasswordUnset"),
|
|
3335
|
+
onChange: (event) => setProxyPassword(event.target.value)
|
|
3336
|
+
}),
|
|
3337
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3338
|
+
className: "oc-hint",
|
|
3339
|
+
children: t("proxyPasswordHint")
|
|
3340
|
+
}),
|
|
3341
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3342
|
+
className: "oc-actions",
|
|
3343
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3344
|
+
type: "button",
|
|
3345
|
+
className: "oc-button",
|
|
3346
|
+
disabled: proxyBusy || proxyPassword === "",
|
|
3347
|
+
onClick: () => {
|
|
3348
|
+
storeProxyPassword();
|
|
3349
|
+
},
|
|
3350
|
+
children: t("saveProxyPassword")
|
|
3351
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3352
|
+
type: "button",
|
|
3353
|
+
className: "oc-button",
|
|
3354
|
+
disabled: proxyBusy || !state.proxy.configured,
|
|
3355
|
+
onClick: () => {
|
|
3356
|
+
removeProxyPassword();
|
|
3357
|
+
},
|
|
3358
|
+
children: t("clearProxyPassword")
|
|
3359
|
+
})]
|
|
3360
|
+
})
|
|
3361
|
+
]
|
|
3362
|
+
}),
|
|
3363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3364
|
+
className: "oc-field",
|
|
3365
|
+
children: [
|
|
3366
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3367
|
+
className: "oc-label",
|
|
3368
|
+
children: t("enabledRoutes")
|
|
3369
|
+
}),
|
|
3370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3371
|
+
className: "oc-actions",
|
|
3372
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
3373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3374
|
+
type: "checkbox",
|
|
3375
|
+
checked: draft.consoleEnabled,
|
|
3376
|
+
onChange: (event) => update({ consoleEnabled: event.target.checked })
|
|
3377
|
+
}),
|
|
3378
|
+
" ",
|
|
3379
|
+
t("routeConsole")
|
|
3380
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
3381
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3382
|
+
type: "checkbox",
|
|
3383
|
+
checked: draft.goEnabled,
|
|
3384
|
+
onChange: (event) => update({ goEnabled: event.target.checked })
|
|
3385
|
+
}),
|
|
3386
|
+
" ",
|
|
3387
|
+
t("routeGo")
|
|
3388
|
+
] })]
|
|
3389
|
+
}),
|
|
3390
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3391
|
+
className: "oc-hint",
|
|
3392
|
+
children: t("enabledRoutesHint")
|
|
3393
|
+
})
|
|
3394
|
+
]
|
|
3395
|
+
}),
|
|
3248
3396
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3249
3397
|
label: t("maxOutputTokens"),
|
|
3250
3398
|
type: "number",
|
package/lib/index.d.ts
CHANGED
|
@@ -50,6 +50,14 @@ interface Config {
|
|
|
50
50
|
requestImageMaxBytes?: number;
|
|
51
51
|
/** HTTP/HTTPS/SOCKS5 proxy for inference, catalog and usage requests. */
|
|
52
52
|
proxyUrl?: string;
|
|
53
|
+
/** Proxy username; the password is resolved from `proxyPasswordRef`. */
|
|
54
|
+
proxyUsername?: string;
|
|
55
|
+
/** Credential reference for the proxy password. */
|
|
56
|
+
proxyPasswordRef?: string;
|
|
57
|
+
/** Enable the Go / Go Plus subscription route. */
|
|
58
|
+
goEnabled?: boolean;
|
|
59
|
+
/** Enable the Console / Zen pay-as-you-go route. */
|
|
60
|
+
consoleEnabled?: boolean;
|
|
53
61
|
/** Live catalog cache lifetime in minutes; defaults to 10. */
|
|
54
62
|
catalogRefreshMinutes?: number;
|
|
55
63
|
/** Language for `/opencode` output; defaults to English. */
|
|
@@ -106,6 +114,10 @@ interface ResolvedConfig {
|
|
|
106
114
|
requestImagePixelBudget: number;
|
|
107
115
|
requestImageMaxBytes: number;
|
|
108
116
|
proxyUrl: string;
|
|
117
|
+
proxyUsername: string;
|
|
118
|
+
proxyPasswordRef: string;
|
|
119
|
+
goEnabled: boolean;
|
|
120
|
+
consoleEnabled: boolean;
|
|
109
121
|
catalogRefreshMinutes: number;
|
|
110
122
|
commandLocale: CommandLocale;
|
|
111
123
|
visibleModels: readonly string[];
|
|
@@ -408,6 +420,7 @@ interface RouteDeps {
|
|
|
408
420
|
readonly analytics?: (days: number) => Promise<AnalyticsReport>;
|
|
409
421
|
readonly heatmap?: (days: number) => Promise<RequestHeatmap>;
|
|
410
422
|
readonly refreshTui?: () => void;
|
|
423
|
+
readonly syncRoutes?: () => void;
|
|
411
424
|
/** Test seam; defaults to the local managed-credentials registry. */
|
|
412
425
|
readonly managedPath?: string;
|
|
413
426
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
4
|
import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
@@ -6,15 +7,15 @@ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
6
7
|
import { LlmAdapter, LlmError, ReasoningEffortId, ToolCallId, attributionHeaders, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
7
8
|
import z from "@deepseek-ai/schemastery";
|
|
8
9
|
import { ProxyAgent, Socks5ProxyAgent } from "undici";
|
|
9
|
-
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
10
10
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { isIP } from "node:net";
|
|
14
14
|
// src/proxy.ts
|
|
15
15
|
/**
|
|
16
|
-
* Proxy addresses
|
|
17
|
-
*
|
|
16
|
+
* Proxy addresses stay credential-free: the settings API returns the configured
|
|
17
|
+
* address to the browser. Authentication is supplied separately from the
|
|
18
|
+
* credential service and is only assembled in memory.
|
|
18
19
|
*/
|
|
19
20
|
function normalizeProxyUrl(raw) {
|
|
20
21
|
const value = raw?.trim() ?? "";
|
|
@@ -31,7 +32,24 @@ function normalizeProxyUrl(raw) {
|
|
|
31
32
|
"socks5:"
|
|
32
33
|
].includes(url.protocol)) throw new Error("proxy address must use http://, https:// or socks5://");
|
|
33
34
|
if (url.hostname === "" || url.port === "0" || url.pathname !== "" && url.pathname !== "/" || url.search !== "" || url.hash !== "") throw new Error("proxy address must contain a host and optional port, without a path, query or fragment");
|
|
34
|
-
if (url.username !== "" || url.password !== "") throw new Error("proxy credentials
|
|
35
|
+
if (url.username !== "" || url.password !== "") throw new Error("proxy credentials must be set through the proxy username and password fields");
|
|
36
|
+
return url.href;
|
|
37
|
+
}
|
|
38
|
+
/** Validate a proxy username; the password lives in the credential service. */
|
|
39
|
+
function normalizeProxyUsername(raw) {
|
|
40
|
+
const value = raw?.trim() ?? "";
|
|
41
|
+
if (value === "") return "";
|
|
42
|
+
if (value.length > 256 || value.includes(":") || /[\u0000-\u001f\u007f]/.test(value)) throw new Error("proxy username must be 1–256 characters without a colon or control characters");
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
/** Assemble the authenticated proxy URL in memory; never persist or return it. */
|
|
46
|
+
function buildProxyUrl(address, username, password) {
|
|
47
|
+
const base = normalizeProxyUrl(address);
|
|
48
|
+
const user = normalizeProxyUsername(username);
|
|
49
|
+
if (base === "" || user === "") return base;
|
|
50
|
+
const url = new URL(base);
|
|
51
|
+
url.username = encodeURIComponent(user);
|
|
52
|
+
url.password = encodeURIComponent(password ?? "");
|
|
35
53
|
return url.href;
|
|
36
54
|
}
|
|
37
55
|
/**
|
|
@@ -43,7 +61,7 @@ function createProxyFetch(address, baseFetch = fetch) {
|
|
|
43
61
|
let disposed = false;
|
|
44
62
|
const wrapped = (async (input, init) => {
|
|
45
63
|
if (disposed) throw new Error("proxy transport is disposed");
|
|
46
|
-
const proxy = normalizeProxyUrl(address());
|
|
64
|
+
const proxy = normalizeProxyUrl(await address());
|
|
47
65
|
if (proxy === "") return baseFetch(input, init);
|
|
48
66
|
let agent = agents.get(proxy);
|
|
49
67
|
if (agent === void 0) {
|
|
@@ -81,7 +99,7 @@ const DEFAULT_GO_API_BASE = "https://opencode.ai/inference/go";
|
|
|
81
99
|
const DEFAULT_GO_ZEN_BASE = "https://opencode.ai/zen/go/v1";
|
|
82
100
|
const DEFAULT_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
83
101
|
const DEFAULT_AUTH_ENTRY = "opencode-go";
|
|
84
|
-
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.
|
|
102
|
+
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.32";
|
|
85
103
|
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
86
104
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
87
105
|
const DEFAULT_GENERATE_MAX_TOKENS = 65536;
|
|
@@ -89,6 +107,7 @@ const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 25165824;
|
|
|
89
107
|
const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 2458624;
|
|
90
108
|
const DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
91
109
|
const DEFAULT_REQUEST_PREPARATION_TIMEOUT_MS = 6e4;
|
|
110
|
+
const DEFAULT_PROXY_PASSWORD_REF = "OPENCODE_PROXY_PASSWORD";
|
|
92
111
|
function markVolatile(schema) {
|
|
93
112
|
const mark = schema.volatile;
|
|
94
113
|
return typeof mark === "function" ? mark.call(schema) : schema;
|
|
@@ -109,6 +128,10 @@ const Config = z.object({
|
|
|
109
128
|
requestImagePixelBudget: markVolatile(z.number().min(1).max(1e8).step(1).default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET)),
|
|
110
129
|
requestImageMaxBytes: markVolatile(z.number().min(1).max(1e8).step(1).default(DEFAULT_REQUEST_IMAGE_MAX_BYTES)),
|
|
111
130
|
proxyUrl: markVolatile(z.string().default("")),
|
|
131
|
+
proxyUsername: markVolatile(z.string().default("")),
|
|
132
|
+
proxyPasswordRef: markVolatile(z.string().role("credential-ref").default(DEFAULT_PROXY_PASSWORD_REF)),
|
|
133
|
+
goEnabled: markVolatile(z.boolean().default(true)),
|
|
134
|
+
consoleEnabled: markVolatile(z.boolean().default(true)),
|
|
112
135
|
catalogRefreshMinutes: markVolatile(z.number().min(1).max(10080).step(1).default(10)),
|
|
113
136
|
commandLocale: markVolatile(z.union(["en", "zh"]).default("en")),
|
|
114
137
|
visibleModels: markVolatile(z.array(z.string()).default([])),
|
|
@@ -229,6 +252,19 @@ function resolveConfig(config) {
|
|
|
229
252
|
requestImagePixelBudget: positiveInt(config.requestImagePixelBudget, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
|
|
230
253
|
requestImageMaxBytes: positiveInt(config.requestImageMaxBytes, DEFAULT_REQUEST_IMAGE_MAX_BYTES),
|
|
231
254
|
proxyUrl: normalizeProxyUrl(config.proxyUrl),
|
|
255
|
+
proxyUsername: normalizeProxyUsername(config.proxyUsername),
|
|
256
|
+
proxyPasswordRef: typeof config.proxyPasswordRef === "string" && config.proxyPasswordRef.trim() !== "" ? config.proxyPasswordRef.trim() : DEFAULT_PROXY_PASSWORD_REF,
|
|
257
|
+
...(() => {
|
|
258
|
+
const goEnabled = config.goEnabled !== false;
|
|
259
|
+
const consoleEnabled = config.consoleEnabled !== false;
|
|
260
|
+
return goEnabled || consoleEnabled ? {
|
|
261
|
+
goEnabled,
|
|
262
|
+
consoleEnabled
|
|
263
|
+
} : {
|
|
264
|
+
goEnabled: true,
|
|
265
|
+
consoleEnabled: false
|
|
266
|
+
};
|
|
267
|
+
})(),
|
|
232
268
|
catalogRefreshMinutes: positiveInt(config.catalogRefreshMinutes, 10),
|
|
233
269
|
commandLocale: config.commandLocale === "zh" ? "zh" : "en",
|
|
234
270
|
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : [],
|
|
@@ -3501,6 +3537,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3501
3537
|
}
|
|
3502
3538
|
async listModels(provider, opts) {
|
|
3503
3539
|
const config = this.deps.config();
|
|
3540
|
+
if (isGoProvider(provider) ? !config.goEnabled : !config.consoleEnabled) return [];
|
|
3504
3541
|
let apiKey;
|
|
3505
3542
|
try {
|
|
3506
3543
|
apiKey = (await this.deps.resolveKey(provider)).key;
|
|
@@ -3524,6 +3561,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3524
3561
|
}
|
|
3525
3562
|
async resolveModel(provider, model) {
|
|
3526
3563
|
const config = this.deps.config();
|
|
3564
|
+
if (isGoProvider(provider) ? !config.goEnabled : !config.consoleEnabled) throw new Error(`OpenCode route "${provider}" is disabled`);
|
|
3527
3565
|
const meta = effectiveModel(model, config);
|
|
3528
3566
|
const maxTokens = effectiveMaxTokens(meta, config);
|
|
3529
3567
|
return {
|
|
@@ -3546,6 +3584,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3546
3584
|
async *run(options, fallback) {
|
|
3547
3585
|
const record = this.deps.recordRequest ?? ((provider, model, purpose, account, reason, outcome) => recordRequest(provider, model, outcome?.at ?? Date.now(), purpose, account, reason, outcome));
|
|
3548
3586
|
const config = this.deps.config();
|
|
3587
|
+
if (isGoProvider(options.provider) ? !config.goEnabled : !config.consoleEnabled) throw new Error(`OpenCode route "${options.provider}" is disabled`);
|
|
3549
3588
|
const runStartedAt = Date.now();
|
|
3550
3589
|
const model = effectiveModel(options.model, config);
|
|
3551
3590
|
const sessionId = openCodeSessionId(options);
|
|
@@ -5035,6 +5074,10 @@ function publicConfig(config) {
|
|
|
5035
5074
|
requestImagePixelBudget: config.requestImagePixelBudget,
|
|
5036
5075
|
requestImageMaxBytes: config.requestImageMaxBytes,
|
|
5037
5076
|
proxyUrl: config.proxyUrl,
|
|
5077
|
+
proxyUsername: config.proxyUsername,
|
|
5078
|
+
proxyPasswordRef: config.proxyPasswordRef,
|
|
5079
|
+
goEnabled: config.goEnabled,
|
|
5080
|
+
consoleEnabled: config.consoleEnabled,
|
|
5038
5081
|
catalogRefreshMinutes: config.catalogRefreshMinutes,
|
|
5039
5082
|
commandLocale: config.commandLocale,
|
|
5040
5083
|
visibleModels: config.visibleModels,
|
|
@@ -5238,6 +5281,10 @@ function configOps(patch) {
|
|
|
5238
5281
|
if ("requestImagePixelBudget" in patch) set("requestImagePixelBudget", patch.requestImagePixelBudget);
|
|
5239
5282
|
if ("requestImageMaxBytes" in patch) set("requestImageMaxBytes", patch.requestImageMaxBytes);
|
|
5240
5283
|
if ("proxyUrl" in patch) set("proxyUrl", normalizeProxyUrl(typeof patch.proxyUrl === "string" ? patch.proxyUrl : ""));
|
|
5284
|
+
if ("proxyUsername" in patch) set("proxyUsername", normalizeProxyUsername(typeof patch.proxyUsername === "string" ? patch.proxyUsername : ""));
|
|
5285
|
+
if ("proxyPasswordRef" in patch) set("proxyPasswordRef", patch.proxyPasswordRef);
|
|
5286
|
+
if ("goEnabled" in patch) set("goEnabled", patch.goEnabled === true);
|
|
5287
|
+
if ("consoleEnabled" in patch) set("consoleEnabled", patch.consoleEnabled === true);
|
|
5241
5288
|
if ("catalogRefreshMinutes" in patch) set("catalogRefreshMinutes", patch.catalogRefreshMinutes);
|
|
5242
5289
|
if ("commandLocale" in patch) set("commandLocale", patch.commandLocale === "zh" ? "zh" : "en");
|
|
5243
5290
|
if ("visibleModels" in patch) set("visibleModels", cleanStringArray(patch.visibleModels));
|
|
@@ -5277,6 +5324,12 @@ async function buildState(ctx, deps) {
|
|
|
5277
5324
|
...info.source === void 0 ? {} : { source: info.source }
|
|
5278
5325
|
};
|
|
5279
5326
|
} catch {}
|
|
5327
|
+
let proxyConfigured = false;
|
|
5328
|
+
if (config.proxyUsername !== "" && credentials !== void 0) try {
|
|
5329
|
+
proxyConfigured = (await credentials.describe(credentialRef(config.proxyPasswordRef))).configured;
|
|
5330
|
+
} catch {
|
|
5331
|
+
proxyConfigured = false;
|
|
5332
|
+
}
|
|
5280
5333
|
const resolved = await deps.pool.resolveAll(GO_PROVIDER).catch(() => []);
|
|
5281
5334
|
if (!keyInfo.configured) keyInfo = {
|
|
5282
5335
|
...keyInfo,
|
|
@@ -5334,9 +5387,10 @@ async function buildState(ctx, deps) {
|
|
|
5334
5387
|
managed: slot.ref !== "" && managedRefs.has(slot.ref)
|
|
5335
5388
|
});
|
|
5336
5389
|
}
|
|
5390
|
+
const listingProvider = config.goEnabled ? GO_PROVIDER : CONSOLE_PROVIDER;
|
|
5337
5391
|
let models = [];
|
|
5338
5392
|
try {
|
|
5339
|
-
models = (await deps.adapter.listModels(
|
|
5393
|
+
models = (await deps.adapter.listModels(listingProvider, { unfiltered: true })).map((model) => {
|
|
5340
5394
|
const meta = effectiveModel(model.id, config);
|
|
5341
5395
|
return {
|
|
5342
5396
|
id: model.id,
|
|
@@ -5351,8 +5405,8 @@ async function buildState(ctx, deps) {
|
|
|
5351
5405
|
input: meta.input,
|
|
5352
5406
|
reasoning: meta.reasoning,
|
|
5353
5407
|
visible: modelVisible(model.id, config),
|
|
5354
|
-
...catalogReleaseDate(
|
|
5355
|
-
isNew: isNewRelease(catalogReleaseDate(
|
|
5408
|
+
...catalogReleaseDate(listingProvider, model.id, config) === void 0 ? {} : { releaseDate: catalogReleaseDate(listingProvider, model.id, config) },
|
|
5409
|
+
isNew: isNewRelease(catalogReleaseDate(listingProvider, model.id, config))
|
|
5356
5410
|
};
|
|
5357
5411
|
});
|
|
5358
5412
|
} catch {
|
|
@@ -5363,9 +5417,14 @@ async function buildState(ctx, deps) {
|
|
|
5363
5417
|
return {
|
|
5364
5418
|
config: publicConfig(config),
|
|
5365
5419
|
key: keyInfo,
|
|
5420
|
+
proxy: {
|
|
5421
|
+
configured: proxyConfigured,
|
|
5422
|
+
username: config.proxyUsername,
|
|
5423
|
+
passwordRef: config.proxyPasswordRef
|
|
5424
|
+
},
|
|
5366
5425
|
accounts,
|
|
5367
5426
|
models,
|
|
5368
|
-
catalog: catalogStatus(
|
|
5427
|
+
catalog: catalogStatus(listingProvider, config, models.length),
|
|
5369
5428
|
usage: usage ?? null,
|
|
5370
5429
|
service: {
|
|
5371
5430
|
...usage?.checkedAt === void 0 ? {} : { usageCheckedAt: usage.checkedAt },
|
|
@@ -5501,6 +5560,7 @@ function applyRoutes(ctx, web, deps) {
|
|
|
5501
5560
|
await settings.mutate(NS, ops, revision);
|
|
5502
5561
|
deps.usage.clear();
|
|
5503
5562
|
deps.refreshTui?.();
|
|
5563
|
+
deps.syncRoutes?.();
|
|
5504
5564
|
json(res, 200, { ok: true });
|
|
5505
5565
|
} catch (error) {
|
|
5506
5566
|
const message = error instanceof Error ? error.message : "config write failed";
|
|
@@ -5552,6 +5612,50 @@ function applyRoutes(ctx, web, deps) {
|
|
|
5552
5612
|
json(res, 500, { error: error instanceof Error ? error.message : "key clear failed" });
|
|
5553
5613
|
}
|
|
5554
5614
|
});
|
|
5615
|
+
route("/dsh-opencode/proxy-password", async (req, res) => {
|
|
5616
|
+
if (!requirePost(req, res)) return;
|
|
5617
|
+
const credentials = getCredentials(ctx);
|
|
5618
|
+
if (credentials === void 0) {
|
|
5619
|
+
json(res, 503, { error: "credentials service unavailable" });
|
|
5620
|
+
return;
|
|
5621
|
+
}
|
|
5622
|
+
try {
|
|
5623
|
+
const body = await readJson(req);
|
|
5624
|
+
const password = typeof body.password === "string" ? body.password : "";
|
|
5625
|
+
if (password === "") {
|
|
5626
|
+
json(res, 400, { error: "proxy password is required" });
|
|
5627
|
+
return;
|
|
5628
|
+
}
|
|
5629
|
+
const refName = deps.config().proxyPasswordRef;
|
|
5630
|
+
await credentials.set(credentialRef(refName), password);
|
|
5631
|
+
await addManagedCredentialRef(refName, deps.managedPath);
|
|
5632
|
+
json(res, 200, {
|
|
5633
|
+
ok: true,
|
|
5634
|
+
ref: refName
|
|
5635
|
+
});
|
|
5636
|
+
} catch (error) {
|
|
5637
|
+
json(res, 500, { error: error instanceof Error ? error.message : "proxy password write failed" });
|
|
5638
|
+
}
|
|
5639
|
+
});
|
|
5640
|
+
route("/dsh-opencode/proxy-password/clear", async (req, res) => {
|
|
5641
|
+
if (!requirePost(req, res)) return;
|
|
5642
|
+
const credentials = getCredentials(ctx);
|
|
5643
|
+
if (credentials === void 0) {
|
|
5644
|
+
json(res, 503, { error: "credentials service unavailable" });
|
|
5645
|
+
return;
|
|
5646
|
+
}
|
|
5647
|
+
try {
|
|
5648
|
+
const refName = deps.config().proxyPasswordRef;
|
|
5649
|
+
await credentials.unset(credentialRef(refName));
|
|
5650
|
+
await removeManagedCredentialRef(refName, deps.managedPath);
|
|
5651
|
+
json(res, 200, {
|
|
5652
|
+
ok: true,
|
|
5653
|
+
ref: refName
|
|
5654
|
+
});
|
|
5655
|
+
} catch (error) {
|
|
5656
|
+
json(res, 500, { error: error instanceof Error ? error.message : "proxy password clear failed" });
|
|
5657
|
+
}
|
|
5658
|
+
});
|
|
5555
5659
|
route("/dsh-opencode/auth-import", async (req, res) => {
|
|
5556
5660
|
if (!requirePost(req, res)) return;
|
|
5557
5661
|
const credentials = getCredentials(ctx);
|
|
@@ -6114,7 +6218,19 @@ const inject = ["llm"];
|
|
|
6114
6218
|
function apply(ctx, config) {
|
|
6115
6219
|
const current = () => resolveConfig(unwrapVolatileConfig(config));
|
|
6116
6220
|
const keys = createKeyPool(ctx, current);
|
|
6117
|
-
const proxy = createProxyFetch(() =>
|
|
6221
|
+
const proxy = createProxyFetch(async () => {
|
|
6222
|
+
const config = current();
|
|
6223
|
+
let password = "";
|
|
6224
|
+
if (config.proxyUsername !== "") {
|
|
6225
|
+
const credentials = ctx.get("credentials");
|
|
6226
|
+
if (credentials?.resolve !== void 0) try {
|
|
6227
|
+
password = (await credentials.resolve(credentialRef(config.proxyPasswordRef)))?.value ?? "";
|
|
6228
|
+
} catch {
|
|
6229
|
+
password = "";
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
return buildProxyUrl(config.proxyUrl, config.proxyUsername, password);
|
|
6233
|
+
});
|
|
6118
6234
|
const usage = createUsageReader(current, (provider) => keys.resolve(provider), proxy.fetch);
|
|
6119
6235
|
const adapter = new OpenCodeAdapter({
|
|
6120
6236
|
config: current,
|
|
@@ -6127,19 +6243,30 @@ function apply(ctx, config) {
|
|
|
6127
6243
|
ctx.effect(() => () => {
|
|
6128
6244
|
proxy.dispose();
|
|
6129
6245
|
});
|
|
6130
|
-
|
|
6246
|
+
const providersFor = () => {
|
|
6247
|
+
const config = current();
|
|
6248
|
+
return [...config.consoleEnabled ? [CONSOLE_PROVIDER] : [], ...config.goEnabled ? [GO_PROVIDER] : []];
|
|
6249
|
+
};
|
|
6250
|
+
const entriesFor = (providers) => [...providers.includes("opencode-direct") ? [{
|
|
6131
6251
|
provider: CONSOLE_PROVIDER,
|
|
6132
6252
|
displayName: "OpenCode (plugin)",
|
|
6133
6253
|
settingsNs: NS,
|
|
6134
6254
|
settingsPath: []
|
|
6135
|
-
}, {
|
|
6255
|
+
}] : [], ...providers.includes("opencode-go-direct") ? [{
|
|
6136
6256
|
provider: GO_PROVIDER,
|
|
6137
6257
|
displayName: "OpenCode Go (plugin)",
|
|
6138
6258
|
settingsNs: NS,
|
|
6139
6259
|
settingsPath: []
|
|
6140
|
-
}]
|
|
6141
|
-
ctx.llm.
|
|
6142
|
-
ctx.
|
|
6260
|
+
}] : []];
|
|
6261
|
+
const directory = ctx.llm.registerConfigurableProviders(entriesFor(providersFor()));
|
|
6262
|
+
const registration = ctx.llm.registerAdapter(providersFor(), adapter);
|
|
6263
|
+
const syncRoutes = () => {
|
|
6264
|
+
const providers = providersFor();
|
|
6265
|
+
registration.replace(providers);
|
|
6266
|
+
directory.replace(entriesFor(providers));
|
|
6267
|
+
ctx.logger("llm-opencode").info(`enabled providers: ${providers.join(", ")}`);
|
|
6268
|
+
};
|
|
6269
|
+
ctx.logger("llm-opencode").info(`registered providers: ${providersFor().join(", ")}`);
|
|
6143
6270
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
6144
6271
|
settingsCtx.settings.configure({ auto: false }, ctx.fiber);
|
|
6145
6272
|
});
|
|
@@ -6183,7 +6310,8 @@ function apply(ctx, config) {
|
|
|
6183
6310
|
pool: keys,
|
|
6184
6311
|
usage,
|
|
6185
6312
|
heatmap: readRequestHeatmap,
|
|
6186
|
-
refreshTui: () => refreshTuiSettings?.()
|
|
6313
|
+
refreshTui: () => refreshTuiSettings?.(),
|
|
6314
|
+
syncRoutes
|
|
6187
6315
|
});
|
|
6188
6316
|
webCtx.effect(() => registerPluginUpdater(webCtx, {
|
|
6189
6317
|
endpoint: "/dsh-opencode/update",
|