@goodandready/dsh-opencode-provider 0.5.21 → 0.5.23
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 +15 -0
- package/README.md +10 -6
- package/README.ru.md +6 -4
- package/README.zh.md +6 -4
- package/lib/client.js +205 -97
- package/lib/index.d.ts +11 -1
- package/lib/index.js +163 -27
- package/package.json +129 -125
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.23
|
|
4
|
+
|
|
5
|
+
- **DSH 0.2.2 support**: compatibility now accepts every `0.2.2` prerelease and final release with `>=0.2.1-rc.2 <0.2.3-0`; `0.2.2-rc.1` is recorded as verified. Dev/typecheck dependencies moved to `@deepseek-ai/*@0.2.2-rc.1`, Cordis 4.1.0 and Schemastery 3.19.0; peer ranges admit both the 0.2.1 and 0.2.2 lines.
|
|
6
|
+
- **Host webserver compatibility**: route registration now types handlers against `WebRoute` instead of indexing the new `WebRoute | WebResourceRoute` union, preserving 0.2.1 behavior while compiling on 0.2.2.
|
|
7
|
+
- **Verification**: isolated DSH 0.2.2-rc.1 web profile activated both routes, discovered 29 models and completed a real `deepseek-v4.1-flash` request (`OK`); 0.2.1-rc.4 and 0.2.1-rc.2 activation regressions also pass. `npm run check`: 124/124 tests.
|
|
8
|
+
|
|
9
|
+
## 0.5.22
|
|
10
|
+
|
|
11
|
+
- **Proxy transport**: one live `proxyUrl` setting (HTTP, HTTPS or SOCKS5) now covers inference, catalog discovery and usage checks. Dispatchers are shared by address and released when the plugin unloads; proxy credentials are rejected so the settings API never returns a secret.
|
|
12
|
+
- **Account pool parity**: up to 20 accounts, drag-and-drop priority in Settings, promotion of the account that served the last request (persisted alongside sticky state), and a 5-minute invalid-credential cooldown matching the reference plugin.
|
|
13
|
+
- **Per-model overrides**: per-model context window and output caps are now editable per model; `0` omits the output cap. The output override also applies to request construction, not only the model descriptor.
|
|
14
|
+
- **Tool-call validation**: numeric/null ids and names from OpenAI Chat, Anthropic, Responses and Google streams are normalized to string fallbacks before emitting `tool-call-delta`; a repeated `id:null` no longer overwrites the id from the first fragment. Six regression tests cover streaming, continuation, non-SSE and all transports (GitHub #2).
|
|
15
|
+
- **Docs/TUI**: README/RU/ZH mirrors and the TUI connection section expose the same fields; 124 tests.
|
|
16
|
+
- **Not added**: a separate keyless Free route. A live probe returned HTTP 429 `FreeUsageLimitError`; the existing keyed Console/Zen route remains the reliable path.
|
|
17
|
+
|
|
3
18
|
## 0.5.21
|
|
4
19
|
|
|
5
20
|
- **Analytics completeness**: projection scans above 5000 files now select the newest files by metadata instead of an arbitrary readdir page; file-limit and unreadable-projection cases set `AnalyticsReport.partial`, surfaced in the dashboard.
|
package/README.md
CHANGED
|
@@ -91,16 +91,18 @@ graph LR
|
|
|
91
91
|
- **Replay of provider metadata** where the endpoint requires it:
|
|
92
92
|
- Anthropic thinking signatures.
|
|
93
93
|
- Gemini `thoughtSignature` on historical function calls.
|
|
94
|
-
- **Multi-account rotation** before a stream starts: 401/403/429 and quota/usage-limit bodies (400/402/403/429). An invalid key cools down for
|
|
94
|
+
- **Multi-account rotation** before a stream starts: up to 20 accounts in drag-and-drop priority order, 401/403/429 and quota/usage-limit bodies (400/402/403/429). An invalid key cools down for 5 minutes, a quota rejection for 10 minutes or `Retry-After`, a rate limit for 60s or `Retry-After`. The account that served the last request is promoted for following conversations and survives restarts. A request already streaming is never replayed on another account; the next request uses the next usable key.
|
|
95
95
|
- **Per-account quotas**: each account row shows its own 5-hour / weekly / monthly usage, the active account drives the sidebar and quota panel, and the panel lists every account side by side.
|
|
96
96
|
- **Per-account runway**: estimates time to exhaustion from the average burn rate of each window and marks the first window projected to run out before its reset; approximate because the API exposes only the current percentage and reset time.
|
|
97
97
|
- **Model → account rules**: first matching glob pins a preferred account for that model; the other accounts remain fallbacks if it fails or is cooling.
|
|
98
|
+
- **Per-model overrides**: context window and output cap can be changed per model in Settings; `0` omits the output cap for that model.
|
|
98
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.
|
|
99
100
|
- **Stable `x-opencode-session`** derived from the durable session id, so retries and every turn of a conversation reuse the same routing key.
|
|
100
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.
|
|
101
103
|
- **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.
|
|
102
104
|
- **`/opencode` command** for status, usage, accounts, models, rules, and `test [model]`.
|
|
103
|
-
- **TUI settings section** for the terminal profile (`/settings → OpenCode`) with the key, endpoints, allowlist, active account, fallback model, sticky routing and timeouts.
|
|
105
|
+
- **TUI settings section** for the terminal profile (`/settings → OpenCode`) with the key, endpoints, proxy, allowlist, active account, fallback model, sticky routing and timeouts.
|
|
104
106
|
- **Quota fallback**: choose a free model in Settings → OpenCode; a Go quota rejection retries once through Console/Zen.
|
|
105
107
|
- **Sticky account**: the last successful account is preferred for the same conversation and survives restarts via a local `sticky.json` (hashed session ids and key fingerprints only); explicit model rules and pinned accounts win.
|
|
106
108
|
- **Routing trace** in the quota panel and per-account heatmap filter.
|
|
@@ -130,12 +132,12 @@ npm pack
|
|
|
130
132
|
Install the **packed tarball** into a profile:
|
|
131
133
|
|
|
132
134
|
```bash
|
|
133
|
-
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.23.tgz
|
|
134
136
|
# DSH Desktop:
|
|
135
|
-
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.23.tgz
|
|
136
138
|
|
|
137
139
|
# Or install the published package:
|
|
138
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
140
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.23
|
|
139
141
|
```
|
|
140
142
|
|
|
141
143
|
Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (plugin)** in the model picker.
|
|
@@ -147,7 +149,9 @@ Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (
|
|
|
147
149
|
|
|
148
150
|
## Verified in DSH Desktop
|
|
149
151
|
|
|
150
|
-
Tested against DSH **0.2.
|
|
152
|
+
Tested against DSH **0.2.2-rc.1** (all 0.2.2 releases are admitted by the compatibility range) and regression-tested on **0.2.1-rc.2/rc.4**: both routes activate, model discovery returns the live catalog, and a real `deepseek-v4.1-flash` request returns `OK`.
|
|
153
|
+
|
|
154
|
+
The historical Desktop verification below was captured on 0.2.1-rc.4 (and retained rc.2/rc.3 compatibility):
|
|
151
155
|
|
|
152
156
|
- the plugin loads and `/dsh-opencode/state` returns `200`;
|
|
153
157
|
- the key is picked up from `~/.local/share/opencode/auth.json`;
|
package/README.ru.md
CHANGED
|
@@ -93,13 +93,14 @@ graph LR
|
|
|
93
93
|
- Google `thoughtSignature` в истории вызовов функций.
|
|
94
94
|
- **Пул аккаунтов с интеллектуальной авто-ротацией** до старта генерации:
|
|
95
95
|
- Автоматический переход на следующий аккаунт при 401/403/429 или ответах о превышении квот (400/402/403/429).
|
|
96
|
-
- Недействительный ключ охлаждается
|
|
96
|
+
- До 20 аккаунтов с drag-and-drop приоритетом. Недействительный ключ охлаждается 5 минут, превышение квоты — 10 минут (или по заголовку `Retry-After`), rate limit — 60 секунд (или по `Retry-After`). Аккаунт, обслуживший последний запрос, поднимается первым для следующих диалогов и сохраняется после рестарта.
|
|
97
97
|
- Если запрос уже начал стриминг, он не переключается посреди ответа; следующий запрос берёт следующий готовый ключ.
|
|
98
98
|
- **Sidebar-карточка квот**: отдельный переключатель в разделе **Sidebar** (и в TUI); после сохранения карточка появляется/исчезает сразу, без ожидания 60-секундного polling.
|
|
99
99
|
- **Квоты по каждому аккаунту**: отображение индивидуального расхода за 5 часов, неделю и месяц; активный аккаунт управляет сайдбаром и панелью.
|
|
100
100
|
- **Runway по каждому аккаунту**: оценка времени до исчерпания по среднему темпу каждого окна; отмечается первое окно, которое закончится раньше сброса. Оценка приблизительная: API отдаёт только текущий процент и время сброса.
|
|
101
101
|
- **Онбординг аккаунтов**: **Import all from auth.json** переносит все ключи OpenCode в отдельные credential ref и добавляет аккаунты; **Check all accounts** обновляет квоты и health по каждому аккаунту.
|
|
102
102
|
- **Правила «модель ➔ аккаунт»**: сопоставление по glob-паттернам закрепляет предпочтительный аккаунт за моделью; остальные аккаунты остаются в резерве.
|
|
103
|
+
- **Per-model overrides**: контекст и лимит вывода настраиваются для каждой модели; `0` убирает лимит вывода.
|
|
103
104
|
- **Квотный fallback**: выберите бесплатную модель в настройках; при ошибке квоты Go тот же запрос один раз повторяется через Console/Zen. Если fallback тоже не сработал, возвращается исходная ошибка квоты.
|
|
104
105
|
- **Липкий аккаунт**: последний успешный аккаунт предпочитается в том же диалоге и сохраняется между перезапусками в локальном `sticky.json` (только хешированные session id и fingerprint ключей); явные правила модели, закреплённый аккаунт и квотный fallback имеют приоритет.
|
|
105
106
|
- **Трассировка маршрутизации**: панель квот показывает последние решения (`rule`, `sticky`, `rotation`, `quota-fallback`) без промптов и ключей.
|
|
@@ -107,6 +108,7 @@ graph LR
|
|
|
107
108
|
- **Почасовой Heatmap запросов**: сетка «день недели × час» в локальном времени плюс суточная полоса агрегации. Фильтры переключают отображение между всеми запросами, конкретной моделью и аккаунтом. Лог `~/.dsh/storages/dsh-opencode/requests.jsonl` хранит только метаданные `{at, provider, model, purpose, account, reason, ok, latencyMs, ttftMs, error}` (промпты и ответы не сохраняются).
|
|
108
109
|
- **Стабильный заголовок `x-opencode-session`**: вычисляется из устойчивого ID сессии DSH, обеспечивая одинаковый ключ маршрутизации при повторах и диалоге.
|
|
109
110
|
- **Живой каталог моделей**: автоматическое чтение `/v1/models` с резервным fallback из `models.dev`.
|
|
111
|
+
- **Proxy**: live-параметр `proxyUrl` направляет инференс, каталог и проверку квот через HTTP, HTTPS или SOCKS5; dispatcher переиспользуется до смены адреса.
|
|
110
112
|
- **Оценки лимитов подписки OpenCode Go**: долларовый лимит и расчетное число запросов для окон 5h, недели и месяца для планов Go и Go Plus.
|
|
111
113
|
- **Команда `/opencode`**: быстрый просмотр статуса, квот, аккаунтов, моделей, правил и запуск теста связи `test [model]`.
|
|
112
114
|
- **Настройки в терминале (TUI)**: раздел `/settings → OpenCode` для профиля terminal с ключом, эндпоинтами, allowlist, активным аккаунтом, fallback-моделью, sticky-маршрутизацией и таймаутами.
|
|
@@ -129,12 +131,12 @@ npm pack
|
|
|
129
131
|
Установка из **упакованного tarball**:
|
|
130
132
|
|
|
131
133
|
```bash
|
|
132
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
134
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.23.tgz
|
|
133
135
|
# DSH Desktop:
|
|
134
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
136
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.23.tgz
|
|
135
137
|
|
|
136
138
|
# Либо установка опубликованного пакета из npm:
|
|
137
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
139
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.23
|
|
138
140
|
```
|
|
139
141
|
|
|
140
142
|
После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode (plugin)** или **OpenCode Go (plugin)** в списке моделей.
|
package/README.zh.md
CHANGED
|
@@ -92,12 +92,13 @@ graph LR
|
|
|
92
92
|
- Google 历史函数调用中的 `thoughtSignature`。
|
|
93
93
|
- **多账户智能无缝轮换**:
|
|
94
94
|
- 在生成流开始前,遇到 401/403/429 或配额用尽响应(400/402/403/429)自动重试下一个账户。
|
|
95
|
-
-
|
|
95
|
+
- 最多 20 个账户,支持拖拽调整优先级。无效密钥冷却 5 分钟,超额冷却 10 分钟(或遵循 `Retry-After`),速率限制冷却 60 秒。上次成功服务的账户会提升到首位并在重启后保留。
|
|
96
96
|
- 正在流式传输中的请求不会被打断,下一个请求会自动使用下一个可用密钥。
|
|
97
97
|
- **侧边栏配额卡片**:独立 **Sidebar** 开关(TUI 也可设置);保存后立即显示/隐藏,无需等待 60 秒轮询。
|
|
98
98
|
- **多账户额度与健康监控**:每行独立展示 5 小时、每周与每月使用情况,活跃账户驱动侧边栏与配额面板。
|
|
99
99
|
- **每账户预计可用时长**:按各窗口的平均消耗速度估算耗尽时间,并标记在重置前最先耗尽的窗口;API 仅提供当前百分比与重置时间,因此为近似值。
|
|
100
100
|
- **模型 ➔ 账户路由规则**:支持 Glob 通配符(如 `*free*`、`deepseek-*`)将模型优先绑定至特定账户,其他账户保留为备用。
|
|
101
|
+
- **按模型覆盖**:可在设置中为每个模型修改上下文窗口和输出上限;`0` 表示该模型省略输出上限。
|
|
101
102
|
- **配额回退**:在设置中选择免费模型;Go 返回配额错误时,同一请求通过 Console/Zen 重试一次。若回退也失败,返回原始配额错误。
|
|
102
103
|
- **会话粘性账户**:同一会话优先使用上次成功的账户,并通过本地 `sticky.json` 跨重启保留(仅哈希后的 session id 和密钥指纹);显式模型规则、固定账户和配额回退优先。
|
|
103
104
|
- **路由追踪**:配额面板显示最近的账户/模型决策(`rule`、`sticky`、`rotation`、`quota-fallback`),不记录提示词或密钥。
|
|
@@ -105,6 +106,7 @@ graph LR
|
|
|
105
106
|
- **本地时间请求热力图**:星期×小时交互式热力图,排除系统心跳与重复记录,可按模型或账户过滤,仅保存 `{at, provider, model, purpose, account, reason, ok, latencyMs, ttftMs, error}` 元数据,不保存任何提示词与回复。
|
|
106
107
|
- **稳定的 `x-opencode-session` 标头**:根据 DSH 会话 ID 派生,确保重试与连续对话复用相同的网关路由键。
|
|
107
108
|
- **实时模型目录**:自动从 `/v1/models` 同步,并以 `models.dev` 生成文件作为回退。
|
|
109
|
+
- **代理**:一个实时 `proxyUrl` 设置即可让推理、模型目录和额度检查走 HTTP、HTTPS 或 SOCKS5;地址变化前复用 dispatcher。
|
|
108
110
|
- **Go 订阅额度估算**:每月美元额度以及 5 小时、每周和每月的估算请求次数。
|
|
109
111
|
- **`/opencode` 斜杠指令**:快速查询状态、配额、账户、模型及执行联通性测试。
|
|
110
112
|
- **TUI 设置**:`/settings → OpenCode` 支持密钥、端点、allowlist、活动账户、回退模型、粘性路由和超时。
|
|
@@ -124,12 +126,12 @@ npm pack
|
|
|
124
126
|
通过**打包文件 (.tgz)**安装:
|
|
125
127
|
|
|
126
128
|
```bash
|
|
127
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
129
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.23.tgz
|
|
128
130
|
# DSH 桌面端:
|
|
129
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
131
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.23.tgz
|
|
130
132
|
|
|
131
133
|
# 或从 npm 安装已发布版本:
|
|
132
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
134
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.23
|
|
133
135
|
```
|
|
134
136
|
|
|
135
137
|
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
|
package/lib/client.js
CHANGED
|
@@ -53,7 +53,7 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
markInvalid: "Invalid key",
|
|
54
54
|
markTransport: "Transport cooldown",
|
|
55
55
|
accounts: "Accounts",
|
|
56
|
-
accountsHint: "The first usable account serves requests; a 401/403/429 or an exhausted quota before the stream starts switches to the next one.",
|
|
56
|
+
accountsHint: "The first usable account serves requests; a 401/403/429 or an exhausted quota before the stream starts switches to the next one. Drag the handle to change priority.",
|
|
57
57
|
accountLabel: "Name",
|
|
58
58
|
accountRef: "Credential ref",
|
|
59
59
|
accountSource: "Source",
|
|
@@ -62,6 +62,8 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
checkAllAccounts: "Check all accounts",
|
|
63
63
|
importedAll: "Imported all auth.json keys; review the accounts and save.",
|
|
64
64
|
accountsImported: "Imported keys",
|
|
65
|
+
dragAccount: "Drag to reorder accounts",
|
|
66
|
+
accountLimit: "At most 20 accounts are allowed.",
|
|
65
67
|
removeAccount: "Remove",
|
|
66
68
|
pinAccount: "Pin",
|
|
67
69
|
activeAccount: "Pinned account",
|
|
@@ -172,7 +174,11 @@ window.__ModuleLoader__.load({
|
|
|
172
174
|
usageUrl: "Usage endpoint",
|
|
173
175
|
requestTimeoutMs: "Header timeout (ms)",
|
|
174
176
|
streamIdleTimeoutMs: "Stream idle timeout (ms)",
|
|
177
|
+
proxyUrl: "Proxy URL",
|
|
178
|
+
proxyUrlHint: "HTTP, HTTPS or SOCKS5 proxy for inference, catalog and usage. Empty disables it. Credentials are not supported.",
|
|
175
179
|
maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
|
|
180
|
+
contextOverrideShort: "Context",
|
|
181
|
+
maxOutputOverrideShort: "Max output",
|
|
176
182
|
contextWindow: "Context override",
|
|
177
183
|
providerMigrationNotice: "Provider ids changed in 0.5.7: use OpenCode (plugin) / OpenCode Go (plugin). Sessions recorded with opencode/opencode-go stay on the built-in pi-ai routes; legacy traffic is excluded from plugin analytics.",
|
|
178
184
|
sidebar: "Sidebar",
|
|
@@ -184,6 +190,7 @@ window.__ModuleLoader__.load({
|
|
|
184
190
|
routingRule: "Model rule",
|
|
185
191
|
routingSticky: "Sticky session",
|
|
186
192
|
routingPinned: "Pinned account",
|
|
193
|
+
routingPromoted: "Last successful",
|
|
187
194
|
routingDefault: "Automatic",
|
|
188
195
|
routingRotation: "Rotation",
|
|
189
196
|
routingFallback: "Quota fallback",
|
|
@@ -259,7 +266,7 @@ window.__ModuleLoader__.load({
|
|
|
259
266
|
markInvalid: "密钥无效",
|
|
260
267
|
markTransport: "传输冷却",
|
|
261
268
|
accounts: "账户",
|
|
262
|
-
accountsHint: "第一个可用账户处理请求;流开始前遇到 401/403/429
|
|
269
|
+
accountsHint: "第一个可用账户处理请求;流开始前遇到 401/403/429 或配额耗尽时切换到下一个账户。拖动左侧手柄可调整优先级。",
|
|
263
270
|
accountLabel: "名称",
|
|
264
271
|
accountRef: "凭据引用",
|
|
265
272
|
accountSource: "来源",
|
|
@@ -268,6 +275,8 @@ window.__ModuleLoader__.load({
|
|
|
268
275
|
checkAllAccounts: "检查全部账户",
|
|
269
276
|
importedAll: "已导入 auth.json 全部密钥;请检查账户并保存。",
|
|
270
277
|
accountsImported: "已导入密钥",
|
|
278
|
+
dragAccount: "拖动以调整账户顺序",
|
|
279
|
+
accountLimit: "最多允许 20 个账户。",
|
|
271
280
|
removeAccount: "移除",
|
|
272
281
|
pinAccount: "固定",
|
|
273
282
|
activeAccount: "固定账户",
|
|
@@ -378,7 +387,11 @@ window.__ModuleLoader__.load({
|
|
|
378
387
|
usageUrl: "用量端点",
|
|
379
388
|
requestTimeoutMs: "请求头超时(毫秒)",
|
|
380
389
|
streamIdleTimeoutMs: "流空闲超时(毫秒)",
|
|
390
|
+
proxyUrl: "代理地址",
|
|
391
|
+
proxyUrlHint: "用于推理、目录和用量的 HTTP、HTTPS 或 SOCKS5 代理。留空则禁用。不支持代理认证。",
|
|
381
392
|
maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
|
|
393
|
+
contextOverrideShort: "上下文",
|
|
394
|
+
maxOutputOverrideShort: "最大输出",
|
|
382
395
|
contextWindow: "上下文覆盖",
|
|
383
396
|
providerMigrationNotice: "0.5.7 起 provider id 已更改:请选择 OpenCode (plugin) / OpenCode Go (plugin)。使用 opencode/opencode-go 记录的历史会话仍使用内置 pi-ai 路由,旧流量不计入插件分析。",
|
|
384
397
|
sidebar: "侧边栏",
|
|
@@ -390,6 +403,7 @@ window.__ModuleLoader__.load({
|
|
|
390
403
|
routingRule: "模型规则",
|
|
391
404
|
routingSticky: "会话粘性",
|
|
392
405
|
routingPinned: "固定账户",
|
|
406
|
+
routingPromoted: "上次成功",
|
|
393
407
|
routingDefault: "自动",
|
|
394
408
|
routingRotation: "轮换",
|
|
395
409
|
routingFallback: "配额回退",
|
|
@@ -1160,6 +1174,7 @@ window.__ModuleLoader__.load({
|
|
|
1160
1174
|
if (reason === "rule") return t("routingRule");
|
|
1161
1175
|
if (reason === "sticky") return t("routingSticky");
|
|
1162
1176
|
if (reason === "pinned") return t("routingPinned");
|
|
1177
|
+
if (reason === "promoted") return t("routingPromoted");
|
|
1163
1178
|
if (reason === "rotation") return t("routingRotation");
|
|
1164
1179
|
if (reason === "quota-fallback") return t("routingFallback");
|
|
1165
1180
|
return t("routingDefault");
|
|
@@ -1628,8 +1643,11 @@ window.__ModuleLoader__.load({
|
|
|
1628
1643
|
openCodeAuthEntry: state.config.openCodeAuthEntry,
|
|
1629
1644
|
requestTimeoutMs: String(state.config.requestTimeoutMs),
|
|
1630
1645
|
streamIdleTimeoutMs: String(state.config.streamIdleTimeoutMs),
|
|
1646
|
+
proxyUrl: state.config.proxyUrl,
|
|
1631
1647
|
maxOutputTokens: state.config.maxOutputTokens === null ? "" : String(state.config.maxOutputTokens),
|
|
1632
1648
|
contextWindow: state.config.contextWindow === null ? "" : String(state.config.contextWindow),
|
|
1649
|
+
contextWindowOverrides: Object.fromEntries(Object.entries(state.config.contextWindowOverrides).map(([id, value]) => [id, String(value)])),
|
|
1650
|
+
maxOutputTokensOverrides: Object.fromEntries(Object.entries(state.config.maxOutputTokensOverrides).map(([id, value]) => [id, String(value)])),
|
|
1633
1651
|
showSidebarQuota: state.config.showSidebarQuota,
|
|
1634
1652
|
activeAccount: state.config.activeAccount,
|
|
1635
1653
|
accounts: state.config.accounts.map((account) => ({ ...account })),
|
|
@@ -1649,6 +1667,17 @@ window.__ModuleLoader__.load({
|
|
|
1649
1667
|
if (!Number.isInteger(value) || value < 0) throw new Error(`Invalid number: ${text}`);
|
|
1650
1668
|
return value;
|
|
1651
1669
|
}
|
|
1670
|
+
function numberRecordOrNull(record) {
|
|
1671
|
+
const out = {};
|
|
1672
|
+
for (const [id, text] of Object.entries(record)) {
|
|
1673
|
+
const trimmed = text.trim();
|
|
1674
|
+
if (trimmed === "") continue;
|
|
1675
|
+
const value = Number(trimmed);
|
|
1676
|
+
if (!Number.isInteger(value) || value < 0) throw new Error(`Invalid number: ${text}`);
|
|
1677
|
+
out[id] = value;
|
|
1678
|
+
}
|
|
1679
|
+
return out;
|
|
1680
|
+
}
|
|
1652
1681
|
function patchOf(draft) {
|
|
1653
1682
|
return {
|
|
1654
1683
|
apiKeyEnv: draft.apiKeyEnv.trim(),
|
|
@@ -1659,8 +1688,11 @@ window.__ModuleLoader__.load({
|
|
|
1659
1688
|
openCodeAuthEntry: draft.openCodeAuthEntry.trim(),
|
|
1660
1689
|
requestTimeoutMs: numberOrNull(draft.requestTimeoutMs) ?? 3e5,
|
|
1661
1690
|
streamIdleTimeoutMs: numberOrNull(draft.streamIdleTimeoutMs) ?? 3e5,
|
|
1691
|
+
proxyUrl: draft.proxyUrl.trim(),
|
|
1662
1692
|
maxOutputTokens: numberOrNull(draft.maxOutputTokens),
|
|
1663
1693
|
contextWindow: numberOrNull(draft.contextWindow),
|
|
1694
|
+
contextWindowOverrides: numberRecordOrNull(draft.contextWindowOverrides),
|
|
1695
|
+
maxOutputTokensOverrides: numberRecordOrNull(draft.maxOutputTokensOverrides),
|
|
1664
1696
|
showSidebarQuota: draft.showSidebarQuota,
|
|
1665
1697
|
activeAccount: draft.activeAccount.trim(),
|
|
1666
1698
|
accounts: draft.accounts.map((account) => ({
|
|
@@ -1902,6 +1934,7 @@ window.__ModuleLoader__.load({
|
|
|
1902
1934
|
const [testResult, setTestResult] = (0, react.useState)(null);
|
|
1903
1935
|
const [importing, setImporting] = (0, react.useState)(false);
|
|
1904
1936
|
const [checking, setChecking] = (0, react.useState)(null);
|
|
1937
|
+
const [dragFrom, setDragFrom] = (0, react.useState)(null);
|
|
1905
1938
|
const load = (0, react.useCallback)(async () => {
|
|
1906
1939
|
setLoading(true);
|
|
1907
1940
|
try {
|
|
@@ -2039,100 +2072,136 @@ window.__ModuleLoader__.load({
|
|
|
2039
2072
|
const hasPrice = model.cost.input > 0 || model.cost.output > 0;
|
|
2040
2073
|
const zen = !model.free && model.limits === null;
|
|
2041
2074
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2042
|
-
className: "oc-model",
|
|
2043
|
-
children: [
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
model.name,
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2075
|
+
className: "oc-model-entry",
|
|
2076
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2077
|
+
className: "oc-model",
|
|
2078
|
+
children: [
|
|
2079
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2080
|
+
type: "checkbox",
|
|
2081
|
+
checked,
|
|
2082
|
+
onChange: () => toggleModel(model.id)
|
|
2083
|
+
}),
|
|
2084
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2085
|
+
className: "oc-model-name",
|
|
2086
|
+
title: model.name + " · " + model.description,
|
|
2087
|
+
children: [
|
|
2088
|
+
model.name,
|
|
2089
|
+
model.free ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2090
|
+
className: "oc-badge",
|
|
2091
|
+
title: t("legendFree"),
|
|
2092
|
+
children: [" ", t("free")]
|
|
2093
|
+
}) : null,
|
|
2094
|
+
!model.free && hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2095
|
+
className: "oc-badge",
|
|
2096
|
+
title: t("legendPrice"),
|
|
2097
|
+
children: [
|
|
2098
|
+
" ",
|
|
2099
|
+
"$",
|
|
2100
|
+
model.cost.input,
|
|
2101
|
+
"/",
|
|
2102
|
+
"$",
|
|
2103
|
+
model.cost.output
|
|
2104
|
+
]
|
|
2105
|
+
}) : null,
|
|
2106
|
+
!model.free && !hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2107
|
+
className: "oc-badge",
|
|
2108
|
+
title: t("legendUnknownPrice"),
|
|
2109
|
+
children: t("priceUnknown")
|
|
2110
|
+
}) : null,
|
|
2111
|
+
model.limits === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2112
|
+
className: "oc-badge",
|
|
2113
|
+
title: describeLimitsFull(model.limits),
|
|
2114
|
+
children: [
|
|
2115
|
+
"Go ",
|
|
2116
|
+
formatRequests(model.limits.go.fiveHour),
|
|
2117
|
+
"/5h"
|
|
2118
|
+
]
|
|
2119
|
+
}),
|
|
2120
|
+
zen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2121
|
+
className: "oc-badge",
|
|
2122
|
+
title: t("legendZen"),
|
|
2123
|
+
children: t("zenBadge")
|
|
2124
|
+
}) : null,
|
|
2125
|
+
model.input.includes("image") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2126
|
+
className: "oc-badge",
|
|
2127
|
+
title: t("legendImage"),
|
|
2128
|
+
children: [" ", t("vision")]
|
|
2129
|
+
}) : null
|
|
2130
|
+
]
|
|
2131
|
+
}),
|
|
2132
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2133
|
+
className: "oc-select",
|
|
2134
|
+
value: draft.modelTransports[model.id] ?? "",
|
|
2135
|
+
onChange: (event) => {
|
|
2136
|
+
const value = event.target.value;
|
|
2137
|
+
const modelTransports = { ...draft.modelTransports };
|
|
2138
|
+
if (value === "") delete modelTransports[model.id];
|
|
2139
|
+
else modelTransports[model.id] = value;
|
|
2140
|
+
update({ modelTransports });
|
|
2141
|
+
},
|
|
2142
|
+
children: [
|
|
2143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
2144
|
+
value: "",
|
|
2145
|
+
children: [
|
|
2146
|
+
t("transportAuto"),
|
|
2147
|
+
" (",
|
|
2148
|
+
model.transport,
|
|
2149
|
+
")"
|
|
2150
|
+
]
|
|
2151
|
+
}),
|
|
2152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2153
|
+
value: "openai-chat",
|
|
2154
|
+
children: "openai-chat"
|
|
2155
|
+
}),
|
|
2156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2157
|
+
value: "openai-responses",
|
|
2158
|
+
children: "openai-responses"
|
|
2159
|
+
}),
|
|
2160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2161
|
+
value: "anthropic",
|
|
2162
|
+
children: "anthropic"
|
|
2163
|
+
}),
|
|
2164
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2165
|
+
value: "google",
|
|
2166
|
+
children: "google"
|
|
2167
|
+
})
|
|
2168
|
+
]
|
|
2169
|
+
})
|
|
2170
|
+
]
|
|
2171
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2172
|
+
className: "oc-model-overrides",
|
|
2173
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2174
|
+
className: "oc-model-override",
|
|
2175
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("contextOverrideShort") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2176
|
+
className: "oc-input",
|
|
2177
|
+
type: "number",
|
|
2178
|
+
min: 1,
|
|
2179
|
+
placeholder: String(model.contextWindow),
|
|
2180
|
+
value: draft.contextWindowOverrides[model.id] ?? "",
|
|
2181
|
+
onChange: (event) => {
|
|
2182
|
+
const next = { ...draft.contextWindowOverrides };
|
|
2183
|
+
if (event.target.value === "") delete next[model.id];
|
|
2184
|
+
else next[model.id] = event.target.value;
|
|
2185
|
+
update({ contextWindowOverrides: next });
|
|
2186
|
+
}
|
|
2187
|
+
})]
|
|
2188
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2189
|
+
className: "oc-model-override",
|
|
2190
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("maxOutputOverrideShort") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2191
|
+
className: "oc-input",
|
|
2192
|
+
type: "number",
|
|
2193
|
+
min: 0,
|
|
2194
|
+
placeholder: String(model.maxTokens),
|
|
2195
|
+
value: draft.maxOutputTokensOverrides[model.id] ?? "",
|
|
2196
|
+
onChange: (event) => {
|
|
2197
|
+
const next = { ...draft.maxOutputTokensOverrides };
|
|
2198
|
+
if (event.target.value === "") delete next[model.id];
|
|
2199
|
+
else next[model.id] = event.target.value;
|
|
2200
|
+
update({ maxOutputTokensOverrides: next });
|
|
2201
|
+
}
|
|
2202
|
+
})]
|
|
2203
|
+
})]
|
|
2204
|
+
})]
|
|
2136
2205
|
}, model.id);
|
|
2137
2206
|
};
|
|
2138
2207
|
const renderSection = (title, hint, rows) => {
|
|
@@ -2462,10 +2531,32 @@ window.__ModuleLoader__.load({
|
|
|
2462
2531
|
const draftKey = keyDrafts[index] ?? "";
|
|
2463
2532
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2464
2533
|
className: "oc-account-block",
|
|
2534
|
+
onDragOver: (event) => {
|
|
2535
|
+
if (dragFrom !== null) event.preventDefault();
|
|
2536
|
+
},
|
|
2537
|
+
onDrop: () => {
|
|
2538
|
+
if (dragFrom === null || dragFrom === index) return;
|
|
2539
|
+
const accounts = [...draft.accounts];
|
|
2540
|
+
const [moved] = accounts.splice(dragFrom, 1);
|
|
2541
|
+
if (moved !== void 0) accounts.splice(index, 0, moved);
|
|
2542
|
+
update({ accounts });
|
|
2543
|
+
setDragFrom(null);
|
|
2544
|
+
},
|
|
2465
2545
|
children: [
|
|
2466
2546
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2467
2547
|
className: "oc-account",
|
|
2468
2548
|
children: [
|
|
2549
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2550
|
+
className: "oc-drag",
|
|
2551
|
+
draggable: true,
|
|
2552
|
+
title: t("dragAccount"),
|
|
2553
|
+
onDragStart: (event) => {
|
|
2554
|
+
setDragFrom(index);
|
|
2555
|
+
event.dataTransfer.effectAllowed = "move";
|
|
2556
|
+
},
|
|
2557
|
+
onDragEnd: () => setDragFrom(null),
|
|
2558
|
+
children: "⋮⋮"
|
|
2559
|
+
}),
|
|
2469
2560
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2470
2561
|
className: "oc-input",
|
|
2471
2562
|
placeholder: t("accountLabel"),
|
|
@@ -2586,12 +2677,17 @@ window.__ModuleLoader__.load({
|
|
|
2586
2677
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2587
2678
|
type: "button",
|
|
2588
2679
|
className: "oc-button",
|
|
2680
|
+
disabled: draft.accounts.length >= 20,
|
|
2589
2681
|
onClick: () => update({ accounts: [...draft.accounts, {
|
|
2590
2682
|
label: "",
|
|
2591
2683
|
apiKeyEnv: ""
|
|
2592
2684
|
}] }),
|
|
2593
2685
|
children: t("addAccount")
|
|
2594
2686
|
}),
|
|
2687
|
+
draft.accounts.length >= 20 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2688
|
+
className: "oc-hint",
|
|
2689
|
+
children: t("accountLimit")
|
|
2690
|
+
}) : null,
|
|
2595
2691
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2596
2692
|
type: "button",
|
|
2597
2693
|
className: "oc-button",
|
|
@@ -2944,6 +3040,12 @@ window.__ModuleLoader__.load({
|
|
|
2944
3040
|
value: draft.streamIdleTimeoutMs,
|
|
2945
3041
|
onChange: (value) => update({ streamIdleTimeoutMs: value })
|
|
2946
3042
|
}),
|
|
3043
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3044
|
+
label: t("proxyUrl"),
|
|
3045
|
+
hint: t("proxyUrlHint"),
|
|
3046
|
+
value: draft.proxyUrl,
|
|
3047
|
+
onChange: (value) => update({ proxyUrl: value })
|
|
3048
|
+
}),
|
|
2947
3049
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2948
3050
|
label: t("maxOutputTokens"),
|
|
2949
3051
|
type: "number",
|
|
@@ -3092,6 +3194,10 @@ window.__ModuleLoader__.load({
|
|
|
3092
3194
|
.oc-bar-fill-bad { background: var(--oc-danger); opacity: 1; }
|
|
3093
3195
|
.oc-models { display: flex; flex-direction: column; gap: 6px; max-height: 340px; overflow: auto; padding-right: 4px; }
|
|
3094
3196
|
.oc-model { display: grid; grid-template-columns: 22px minmax(0,1fr) 120px; gap: 8px; align-items: center; font-size: 12px; padding: 3px 0; }
|
|
3197
|
+
.oc-model-entry { display: flex; flex-direction: column; gap: 2px; }
|
|
3198
|
+
.oc-model-overrides { display: flex; gap: 8px; padding-left: 30px; }
|
|
3199
|
+
.oc-model-override { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--oc-text-tertiary); }
|
|
3200
|
+
.oc-model-override .oc-input { width: 92px; padding: 3px 6px; font-size: 11px; }
|
|
3095
3201
|
.oc-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3096
3202
|
.oc-model-legend { display: flex; flex-wrap: wrap; gap: 6px 12px; align-items: center; color: var(--oc-text-secondary); font-size: 11px; }
|
|
3097
3203
|
.oc-model-legend > span { display: inline-flex; align-items: center; gap: 5px; }
|
|
@@ -3100,8 +3206,10 @@ window.__ModuleLoader__.load({
|
|
|
3100
3206
|
.oc-model-section-head { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; font-weight: 650; }
|
|
3101
3207
|
.oc-model-section-head .oc-badge { padding: 0 6px; font-size: 10px; }
|
|
3102
3208
|
.oc-model-section .oc-hint { margin: 0 0 4px; }
|
|
3103
|
-
.oc-account { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr) auto; gap: 8px; align-items: center; }
|
|
3209
|
+
.oc-account { display: grid; grid-template-columns: 18px minmax(0,1fr) minmax(0,1fr) auto; gap: 8px; align-items: center; }
|
|
3104
3210
|
.oc-account-block { display: flex; flex-direction: column; gap: 6px; padding: 9px; border: 1px solid var(--oc-border-soft); border-radius: 8px; background: var(--oc-fill); }
|
|
3211
|
+
.oc-drag { cursor: grab; color: var(--oc-text-tertiary); user-select: none; font-size: 13px; line-height: 1; }
|
|
3212
|
+
.oc-drag:active { cursor: grabbing; }
|
|
3105
3213
|
.oc-notice { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-accent-soft); border: 1px solid var(--oc-accent-border); }
|
|
3106
3214
|
.oc-error { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-danger-soft); border: 1px solid var(--oc-danger-border); color: var(--oc-danger); }
|
|
3107
3215
|
[class*="_footerActions"]:has(.oc-foot), [class*="_footerActions"]:has(.oc-railButton) { flex-direction: column; }
|