@goodandready/dsh-opencode-provider 0.5.6 → 0.5.8
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 +10 -0
- package/README.md +19 -12
- package/README.ru.md +17 -10
- package/README.zh.md +15 -10
- package/lib/client.js +5 -2
- package/lib/index.js +47 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.8
|
|
4
|
+
|
|
5
|
+
- **Free-tier error cooldown**: a `FreeTierError` (free model used with a free/anonymous key) is now a short `RATE_LIMIT`, not `INVALID_CREDENTIAL`. The account rotates to the next key and is cooled for 60s instead of 24h; the key itself is not invalid. Added an adapter regression test.
|
|
6
|
+
- 90 tests.
|
|
7
|
+
|
|
8
|
+
## 0.5.7
|
|
9
|
+
|
|
10
|
+
- **Provider-id conflict fix**: the plugin now registers `opencode-direct` and `opencode-go-direct` instead of `opencode`/`opencode-go`. The built-in `llm-pi-ai` catalog owns those ids; reusing them triggered `DUPLICATE_DIRECTORY`, dropped the built-in catalog, hid other providers and disabled the add button. Existing sessions recorded under the old ids stay on the built-in routes; select **OpenCode (plugin)** or **OpenCode Go (plugin)** for this plugin's features.
|
|
11
|
+
- 89 tests.
|
|
12
|
+
|
|
3
13
|
## 0.5.6
|
|
4
14
|
|
|
5
15
|
- **Persistent sticky routing**: the last successful account/model per conversation survives adapter and Desktop restarts. `~/.dsh/storages/dsh-opencode/sticky.json` stores hashed session ids and key fingerprints only (no keys or prompt text), keeps 500 entries for 14 days.
|
package/README.md
CHANGED
|
@@ -30,11 +30,18 @@ It registers two native LLM routes:
|
|
|
30
30
|
|
|
31
31
|
| Route | Product | Base |
|
|
32
32
|
|---|---|---|
|
|
33
|
-
| `opencode` | OpenCode Console / Zen (pay-as-you-go) | `https://opencode.ai/inference` |
|
|
34
|
-
| `opencode-go` | OpenCode Go / Go Plus subscription | `https://opencode.ai/inference/go` |
|
|
33
|
+
| `opencode-direct` | OpenCode Console / Zen (pay-as-you-go) | `https://opencode.ai/inference` |
|
|
34
|
+
| `opencode-go-direct` | OpenCode Go / Go Plus subscription | `https://opencode.ai/inference/go` |
|
|
35
35
|
|
|
36
36
|
Requests resolve the key per call, so a changed credential reaches the next request without a restart.
|
|
37
37
|
|
|
38
|
+
The ids use the `-direct` suffix because the built-in `llm-pi-ai` catalog already owns
|
|
39
|
+
`opencode` and `opencode-go`. Reusing those ids makes the core registry reject the
|
|
40
|
+
duplicate (`DUPLICATE_DIRECTORY`) and drop the built-in catalog, which hides the other
|
|
41
|
+
providers and disables the add button. Existing sessions recorded under the old ids stay on
|
|
42
|
+
the built-in routes; select **OpenCode (plugin)** or **OpenCode Go (plugin)** for this
|
|
43
|
+
plugin's accounts, quotas, fallback and telemetry.
|
|
44
|
+
|
|
38
45
|
## Architecture
|
|
39
46
|
|
|
40
47
|
```mermaid
|
|
@@ -58,14 +65,14 @@ graph LR
|
|
|
58
65
|
end
|
|
59
66
|
|
|
60
67
|
subgraph OpenCode["OpenCode Gateway"]
|
|
61
|
-
Console["opencode (Console / Zen)"]
|
|
62
|
-
Go["opencode-go (Go / Go Plus)"]
|
|
68
|
+
Console["opencode-direct (Console / Zen)"]
|
|
69
|
+
Go["opencode-go-direct (Go / Go Plus)"]
|
|
63
70
|
end
|
|
64
71
|
|
|
65
72
|
Agent --> Router
|
|
66
73
|
Creds -.-> Pool
|
|
67
|
-
Router -->|opencode| Pool
|
|
68
|
-
Router -->|opencode-go| Pool
|
|
74
|
+
Router -->|opencode-direct| Pool
|
|
75
|
+
Router -->|opencode-go-direct| Pool
|
|
69
76
|
Pool --> Heatmap
|
|
70
77
|
Pool --> Transports
|
|
71
78
|
T1 & T2 & T3 & T4 --> Console & Go
|
|
@@ -123,15 +130,15 @@ npm pack
|
|
|
123
130
|
Install the **packed tarball** into a profile:
|
|
124
131
|
|
|
125
132
|
```bash
|
|
126
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
133
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.8.tgz
|
|
127
134
|
# DSH Desktop:
|
|
128
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
135
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.8.tgz
|
|
129
136
|
|
|
130
137
|
# Or install the published package:
|
|
131
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
138
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.8
|
|
132
139
|
```
|
|
133
140
|
|
|
134
|
-
Restart dsh (or DSH Desktop), then pick
|
|
141
|
+
Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (plugin)** in the model picker.
|
|
135
142
|
|
|
136
143
|
> **Use the tarball, not the directory.** On dsh 0.2.1-rc.2 the Desktop CLI aliases a local
|
|
137
144
|
> directory passed to `dsh plugin add` as `@deepseek-ai/dsh-desktop-runtime` and overwrites
|
|
@@ -205,7 +212,7 @@ Cooldowns survive restarts: deadlines and reasons are stored in `~/.dsh/storages
|
|
|
205
212
|
|
|
206
213
|
### Model → account rules
|
|
207
214
|
|
|
208
|
-
Pin a model to a preferred account with a glob. The first matching rule wins; `*` and `?` are supported and matching is case-insensitive. The named account is tried first, and the other accounts remain fallbacks if it is cooling, missing, or rejected — a rule is a preference, not a hard filter. An unknown account label is ignored, and rules apply to both `opencode` and `opencode-go`.
|
|
215
|
+
Pin a model to a preferred account with a glob. The first matching rule wins; `*` and `?` are supported and matching is case-insensitive. The named account is tried first, and the other accounts remain fallbacks if it is cooling, missing, or rejected — a rule is a preference, not a hard filter. An unknown account label is ignored, and rules apply to both `opencode-direct` and `opencode-go-direct`.
|
|
209
216
|
|
|
210
217
|
Configure them in **Settings → OpenCode → Model → account rules**, or in YAML:
|
|
211
218
|
|
|
@@ -298,7 +305,7 @@ GitHub Actions runs `npm run check` and `npm pack` on push/PR (`.github/workflow
|
|
|
298
305
|
- Automatic update requires the DSH CLI entry in the runtime. When it is not exposed, the update card shows the current/latest status and directs the user to the DSH plugin manager.
|
|
299
306
|
- Subscription request counts are OpenCode's published estimates, not enforced quotas. They can change when OpenCode updates the Go pricing table; the gateway is the final authority.
|
|
300
307
|
- Usage-by-model aggregates the local projection cache and attributes a whole session to its last-used model; a session that switched models is therefore approximate. It never reads prompt text or titles.
|
|
301
|
-
- `opencode` (Console) billing shows no per-model allowance; the Go route is the primary target.
|
|
308
|
+
- `opencode-direct` (Console) billing shows no per-model allowance; the Go route is the primary target.
|
|
302
309
|
- The anonymous free lane (`Bearer public` + CLI impersonation) is not implemented.
|
|
303
310
|
- The UI requires a web profile (the Host needs `ctx.webServer`); the TUI keeps the `/opencode` command and the model routes.
|
|
304
311
|
- Write routes are loopback-only; a web profile exposed to a LAN can read state but cannot save configuration or keys until the host auth token is integrated.
|
package/README.ru.md
CHANGED
|
@@ -30,11 +30,18 @@
|
|
|
30
30
|
|
|
31
31
|
| Маршрут | Продукт / Тариф | Базовый эндпоинт | Назначение |
|
|
32
32
|
|---|---|---|---|
|
|
33
|
-
| `opencode` | OpenCode Console / Zen | `https://opencode.ai/inference` | Поминутная тарификация (pay-as-you-go) |
|
|
34
|
-
| `opencode-go` | OpenCode Go / Go Plus | `https://opencode.ai/inference/go` | Фиксированная подписка с лимитами 5h / недели / месяца |
|
|
33
|
+
| `opencode-direct` | OpenCode Console / Zen | `https://opencode.ai/inference` | Поминутная тарификация (pay-as-you-go) |
|
|
34
|
+
| `opencode-go-direct` | OpenCode Go / Go Plus | `https://opencode.ai/inference/go` | Фиксированная подписка с лимитами 5h / недели / месяца |
|
|
35
35
|
|
|
36
36
|
Ключи разрешаются динамически на каждый вызов: смена токена в настройках или в файле окружения вступает в силу мгновенно без перезапуска сервера.
|
|
37
37
|
|
|
38
|
+
Суффикс `-direct` нужен потому, что встроенный каталог `llm-pi-ai` уже объявляет
|
|
39
|
+
`opencode` и `opencode-go`. Повтор этих id даёт `DUPLICATE_DIRECTORY`, из-за чего
|
|
40
|
+
ядро отбрасывает встроенный каталог, исчезают остальные провайдеры и отключается
|
|
41
|
+
кнопка добавления. Старые сессии с прежними id остаются на встроенных маршрутах;
|
|
42
|
+
для возможностей плагина выбирайте **OpenCode (plugin)** или
|
|
43
|
+
**OpenCode Go (plugin)**.
|
|
44
|
+
|
|
38
45
|
## Архитектура
|
|
39
46
|
|
|
40
47
|
```mermaid
|
|
@@ -58,14 +65,14 @@ graph LR
|
|
|
58
65
|
end
|
|
59
66
|
|
|
60
67
|
subgraph OpenCode["Шлюз OpenCode Gateway"]
|
|
61
|
-
Console["opencode (Console / Zen)"]
|
|
62
|
-
Go["opencode-go (Go / Go Plus)"]
|
|
68
|
+
Console["opencode-direct (Console / Zen)"]
|
|
69
|
+
Go["opencode-go-direct (Go / Go Plus)"]
|
|
63
70
|
end
|
|
64
71
|
|
|
65
72
|
Agent --> Router
|
|
66
73
|
Creds -.-> Pool
|
|
67
|
-
Router -->|opencode| Pool
|
|
68
|
-
Router -->|opencode-go| Pool
|
|
74
|
+
Router -->|opencode-direct| Pool
|
|
75
|
+
Router -->|opencode-go-direct| Pool
|
|
69
76
|
Pool --> Heatmap
|
|
70
77
|
Pool --> Transports
|
|
71
78
|
T1 & T2 & T3 & T4 --> Console & Go
|
|
@@ -122,15 +129,15 @@ npm pack
|
|
|
122
129
|
Установка из **упакованного tarball**:
|
|
123
130
|
|
|
124
131
|
```bash
|
|
125
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
132
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.8.tgz
|
|
126
133
|
# DSH Desktop:
|
|
127
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
134
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.8.tgz
|
|
128
135
|
|
|
129
136
|
# Либо установка опубликованного пакета из npm:
|
|
130
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
137
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.8
|
|
131
138
|
```
|
|
132
139
|
|
|
133
|
-
После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode** или **OpenCode Go** в списке моделей.
|
|
140
|
+
После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode (plugin)** или **OpenCode Go (plugin)** в списке моделей.
|
|
134
141
|
|
|
135
142
|
## Конфигурация
|
|
136
143
|
|
package/README.zh.md
CHANGED
|
@@ -30,11 +30,16 @@
|
|
|
30
30
|
|
|
31
31
|
| 路由 | 产品 / 方案 | 基础接口 | 特性说明 |
|
|
32
32
|
|---|---|---|---|
|
|
33
|
-
| `opencode` | OpenCode Console / Zen | `https://opencode.ai/inference` | 按量计费(pay-as-you-go) |
|
|
34
|
-
| `opencode-go` | OpenCode Go / Go Plus | `https://opencode.ai/inference/go` | 订阅专属,含 5小时/每周/每月配额估算 |
|
|
33
|
+
| `opencode-direct` | OpenCode Console / Zen | `https://opencode.ai/inference` | 按量计费(pay-as-you-go) |
|
|
34
|
+
| `opencode-go-direct` | OpenCode Go / Go Plus | `https://opencode.ai/inference/go` | 订阅专属,含 5小时/每周/每月配额估算 |
|
|
35
35
|
|
|
36
36
|
密钥在每次调用时动态解析:在设置中或环境文件中更换凭据无需重启服务器即可立即生效。
|
|
37
37
|
|
|
38
|
+
`-direct` 后缀用于避免与内置 `llm-pi-ai` 已声明的 `opencode` / `opencode-go` 冲突。
|
|
39
|
+
重复 id 会触发 `DUPLICATE_DIRECTORY`,导致核心丢弃内置目录、其他提供商消失并禁用添加按钮。
|
|
40
|
+
旧会话继续使用内置路由;要使用本插件的账户、配额、回退和遥测,请选择
|
|
41
|
+
**OpenCode (plugin)** 或 **OpenCode Go (plugin)**。
|
|
42
|
+
|
|
38
43
|
## 架构示意
|
|
39
44
|
|
|
40
45
|
```mermaid
|
|
@@ -58,14 +63,14 @@ graph LR
|
|
|
58
63
|
end
|
|
59
64
|
|
|
60
65
|
subgraph OpenCode["OpenCode 网关"]
|
|
61
|
-
Console["opencode (Console / Zen)"]
|
|
62
|
-
Go["opencode-go (Go / Go Plus)"]
|
|
66
|
+
Console["opencode-direct (Console / Zen)"]
|
|
67
|
+
Go["opencode-go-direct (Go / Go Plus)"]
|
|
63
68
|
end
|
|
64
69
|
|
|
65
70
|
Agent --> Router
|
|
66
71
|
Creds -.-> Pool
|
|
67
|
-
Router -->|opencode| Pool
|
|
68
|
-
Router -->|opencode-go| Pool
|
|
72
|
+
Router -->|opencode-direct| Pool
|
|
73
|
+
Router -->|opencode-go-direct| Pool
|
|
69
74
|
Pool --> Heatmap
|
|
70
75
|
Pool --> Transports
|
|
71
76
|
T1 & T2 & T3 & T4 --> Console & Go
|
|
@@ -119,15 +124,15 @@ npm pack
|
|
|
119
124
|
通过**打包文件 (.tgz)**安装:
|
|
120
125
|
|
|
121
126
|
```bash
|
|
122
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
127
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.8.tgz
|
|
123
128
|
# DSH 桌面端:
|
|
124
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
129
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.8.tgz
|
|
125
130
|
|
|
126
131
|
# 或从 npm 安装已发布版本:
|
|
127
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
132
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.8
|
|
128
133
|
```
|
|
129
134
|
|
|
130
|
-
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode** 或 **OpenCode Go** 即可。
|
|
135
|
+
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
|
|
131
136
|
|
|
132
137
|
## 配置示例
|
|
133
138
|
|
package/lib/client.js
CHANGED
|
@@ -379,6 +379,9 @@ window.__ModuleLoader__.load({
|
|
|
379
379
|
}
|
|
380
380
|
};
|
|
381
381
|
//#endregion
|
|
382
|
+
//#region src/provider-ids.ts
|
|
383
|
+
const GO_PROVIDER = "opencode-go-direct";
|
|
384
|
+
//#endregion
|
|
382
385
|
//#region src/client/api.ts
|
|
383
386
|
async function call(path, init) {
|
|
384
387
|
const response = await fetch(path, {
|
|
@@ -452,7 +455,7 @@ window.__ModuleLoader__.load({
|
|
|
452
455
|
function fetchAnalytics(days = 30) {
|
|
453
456
|
return call(`/dsh-opencode/analytics?days=${encodeURIComponent(String(days))}`);
|
|
454
457
|
}
|
|
455
|
-
function testConnection(model, provider =
|
|
458
|
+
function testConnection(model, provider = GO_PROVIDER) {
|
|
456
459
|
return call("/dsh-opencode/test", {
|
|
457
460
|
method: "POST",
|
|
458
461
|
body: JSON.stringify({
|
|
@@ -2205,7 +2208,7 @@ window.__ModuleLoader__.load({
|
|
|
2205
2208
|
testConnection(testModel).then(setTestResult).catch((caught) => setTestResult({
|
|
2206
2209
|
ok: false,
|
|
2207
2210
|
model: testModel,
|
|
2208
|
-
provider:
|
|
2211
|
+
provider: GO_PROVIDER,
|
|
2209
2212
|
latencyMs: 0,
|
|
2210
2213
|
error: caught instanceof Error ? caught.message : t("failed")
|
|
2211
2214
|
})).finally(() => setTesting(false));
|
package/lib/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const DEFAULT_GO_API_BASE = "https://opencode.ai/inference/go";
|
|
|
17
17
|
const DEFAULT_GO_ZEN_BASE = "https://opencode.ai/zen/go/v1";
|
|
18
18
|
const DEFAULT_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
19
19
|
const DEFAULT_AUTH_ENTRY = "opencode-go";
|
|
20
|
-
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.
|
|
20
|
+
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.8";
|
|
21
21
|
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
22
22
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
23
23
|
const DEFAULT_GENERATE_MAX_TOKENS = 32768;
|
|
@@ -791,9 +791,24 @@ const GENERATED_MODELS = {
|
|
|
791
791
|
}
|
|
792
792
|
};
|
|
793
793
|
//#endregion
|
|
794
|
+
//#region src/provider-ids.ts
|
|
795
|
+
/**
|
|
796
|
+
* Provider ids owned by this plugin. The built-in llm-pi-ai catalog already
|
|
797
|
+
* declares `opencode` and `opencode-go`; the core registry rejects duplicate
|
|
798
|
+
* configurable provider ids and would drop the whole catalog when they clash.
|
|
799
|
+
*/
|
|
800
|
+
const CONSOLE_PROVIDER = "opencode-direct";
|
|
801
|
+
const GO_PROVIDER = "opencode-go-direct";
|
|
802
|
+
const PLUGIN_AND_LEGACY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
803
|
+
CONSOLE_PROVIDER,
|
|
804
|
+
GO_PROVIDER,
|
|
805
|
+
"opencode",
|
|
806
|
+
"opencode-go"
|
|
807
|
+
]);
|
|
808
|
+
//#endregion
|
|
794
809
|
//#region src/routing.ts
|
|
795
810
|
function isGoProvider(provider) {
|
|
796
|
-
return provider === "opencode-go";
|
|
811
|
+
return provider === "opencode-go-direct" || provider === "opencode-go";
|
|
797
812
|
}
|
|
798
813
|
function rootFor(provider, config) {
|
|
799
814
|
return isGoProvider(provider) ? config.goApiBase : config.apiBase;
|
|
@@ -1059,7 +1074,7 @@ async function discoverModelIds(provider, config, apiKey, fetchImpl = fetch) {
|
|
|
1059
1074
|
return Object.keys(GENERATED_MODELS);
|
|
1060
1075
|
}
|
|
1061
1076
|
function sortForPicker(ids, config, unfiltered = false, provider) {
|
|
1062
|
-
return ids.filter((id) => unfiltered || modelVisible(id, config) && (provider
|
|
1077
|
+
return ids.filter((id) => unfiltered || modelVisible(id, config) && (!isGoProvider(provider) || modelInGoPlan(id, config))).sort((left, right) => {
|
|
1063
1078
|
const a = effectiveModel(left, config);
|
|
1064
1079
|
const b = effectiveModel(right, config);
|
|
1065
1080
|
if (a.free !== b.free) return a.free ? -1 : 1;
|
|
@@ -1072,7 +1087,7 @@ function describeModel(meta, provider) {
|
|
|
1072
1087
|
else if (meta.cost.input > 0 || meta.cost.output > 0) parts.push(`$${meta.cost.input}/$${meta.cost.output} per 1M`);
|
|
1073
1088
|
if (meta.input.includes("image")) parts.push("Image");
|
|
1074
1089
|
if (meta.reasoning.length > 0) parts.push(`reasoning: ${meta.reasoning.join("/")}`);
|
|
1075
|
-
if (provider
|
|
1090
|
+
if (isGoProvider(provider)) {
|
|
1076
1091
|
const limits = limitsFor(meta.id);
|
|
1077
1092
|
if (!meta.free) parts.unshift(limits === void 0 ? "Console" : "Go");
|
|
1078
1093
|
if (limits !== void 0) parts.push(describeLimitsShort(limits));
|
|
@@ -1154,7 +1169,7 @@ function classifyResponseError(status, text, headers) {
|
|
|
1154
1169
|
};
|
|
1155
1170
|
const quota = /\bquota\b/.test(detail) || detail.includes("usage limit") || detail.includes("usage_limit") || detail.includes("monthly limit") || detail.includes("weekly limit") || detail.includes("5-hour limit") || detail.includes("limit reached") || detail.includes("insufficient credit") || detail.includes("insufficient balance") || detail.includes("insufficient funds") || detail.includes("credit balance") || detail.includes("out of credit");
|
|
1156
1171
|
if (type === "MissingSessionID" || detail.includes("missing x-opencode-session")) return new LlmError(`OpenCode rejected the request: ${message}. The plugin always sends a stable x-opencode-session; a proxy in front of it may be stripping the header.`, "PROVIDER_HTTP_ERROR", options);
|
|
1157
|
-
if (type === "FreeTierError" || detail.includes("free tier can only be used")) return new LlmError(`OpenCode refused the free tier: ${message}. Free models require a Go/paid key; an anonymous free key is only accepted inside the OpenCode client.`, "
|
|
1172
|
+
if (type === "FreeTierError" || detail.includes("free tier can only be used")) return new LlmError(`OpenCode refused the free tier: ${message}. Free models require a Go/paid key; an anonymous free key is only accepted inside the OpenCode client.`, "RATE_LIMIT", options);
|
|
1158
1173
|
if (type === "ModelProtocolUnsupported" || detail.includes("does not support this protocol")) return new LlmError(`OpenCode model does not support the selected protocol: ${message}. Set config.modelTransports[model] to the protocol named in the model catalog.`, "UNSUPPORTED_MODEL", options);
|
|
1159
1174
|
if (status === 402 || quota) return new LlmError(`OpenCode quota is exhausted: ${message}`, "QUOTA", options);
|
|
1160
1175
|
if (status === 401 || status === 403 || detail.includes("invalid credential") || detail.includes("unauthorized")) return new LlmError(`OpenCode rejected the credential: ${message}. Check the key, the selected auth.json entry, or whether the account has an active subscription.`, "INVALID_CREDENTIAL", options);
|
|
@@ -3104,7 +3119,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3104
3119
|
providerInfo(provider) {
|
|
3105
3120
|
return {
|
|
3106
3121
|
id: provider,
|
|
3107
|
-
name: isGoProvider(provider) ? "OpenCode Go" : "OpenCode"
|
|
3122
|
+
name: isGoProvider(provider) ? "OpenCode Go (plugin)" : "OpenCode (plugin)"
|
|
3108
3123
|
};
|
|
3109
3124
|
}
|
|
3110
3125
|
providerRetryPolicy(_provider) {
|
|
@@ -3276,7 +3291,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3276
3291
|
const transient = this.isTransient(classified);
|
|
3277
3292
|
if (transient && !responseFailure && !fallback) this.markCooldown(entry.key, 0);
|
|
3278
3293
|
if (!(index < keys.length - 1 && transient)) {
|
|
3279
|
-
if (!fallback && config.quotaFallbackModel !== "" && options.provider
|
|
3294
|
+
if (!fallback && config.quotaFallbackModel !== "" && isGoProvider(options.provider) && classified.code === "QUOTA") break;
|
|
3280
3295
|
throw classified;
|
|
3281
3296
|
}
|
|
3282
3297
|
}
|
|
@@ -3298,10 +3313,10 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3298
3313
|
ok: false,
|
|
3299
3314
|
error: finalError.code
|
|
3300
3315
|
});
|
|
3301
|
-
if (!fallback && config.quotaFallbackModel !== "" && options.provider
|
|
3316
|
+
if (!fallback && config.quotaFallbackModel !== "" && isGoProvider(options.provider) && (lastError === void 0 || lastError.code === "QUOTA")) try {
|
|
3302
3317
|
yield* this.run({
|
|
3303
3318
|
...options,
|
|
3304
|
-
provider:
|
|
3319
|
+
provider: CONSOLE_PROVIDER,
|
|
3305
3320
|
model: config.quotaFallbackModel
|
|
3306
3321
|
}, true);
|
|
3307
3322
|
return;
|
|
@@ -3490,14 +3505,14 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3490
3505
|
const seconds = raw === null || raw === void 0 ? NaN : Number(raw);
|
|
3491
3506
|
delayMs = Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : 6e5;
|
|
3492
3507
|
reason = "quota";
|
|
3493
|
-
} else if (
|
|
3494
|
-
delayMs = 864e5;
|
|
3495
|
-
reason = "invalid-credential";
|
|
3496
|
-
} else if (status === 429) {
|
|
3508
|
+
} else if (code === "RATE_LIMIT" || status === 429) {
|
|
3497
3509
|
const raw = headers?.get("retry-after");
|
|
3498
3510
|
const seconds = raw === null || raw === void 0 ? NaN : Number(raw);
|
|
3499
3511
|
delayMs = Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : 6e4;
|
|
3500
3512
|
reason = "rate-limit";
|
|
3513
|
+
} else if (status === 401 || status === 403) {
|
|
3514
|
+
delayMs = 864e5;
|
|
3515
|
+
reason = "invalid-credential";
|
|
3501
3516
|
}
|
|
3502
3517
|
this.cooldowns.set(this.keyId(key), {
|
|
3503
3518
|
until: Date.now() + delayMs,
|
|
@@ -3549,7 +3564,7 @@ function relative(until) {
|
|
|
3549
3564
|
}
|
|
3550
3565
|
async function accountsText(deps) {
|
|
3551
3566
|
const lines = [];
|
|
3552
|
-
for (const provider of [
|
|
3567
|
+
for (const provider of [GO_PROVIDER, CONSOLE_PROVIDER]) {
|
|
3553
3568
|
let keys;
|
|
3554
3569
|
try {
|
|
3555
3570
|
keys = await deps.pool.resolveAll(provider);
|
|
@@ -3565,7 +3580,7 @@ async function accountsText(deps) {
|
|
|
3565
3580
|
lines.push(`${provider}:`);
|
|
3566
3581
|
for (const [index, entry] of keys.entries()) {
|
|
3567
3582
|
const health = deps.adapter.accountHealth(entry.key);
|
|
3568
|
-
const usage = provider === "opencode-go" ? await deps.usage(provider, entry.key) : void 0;
|
|
3583
|
+
const usage = provider === "opencode-go-direct" ? await deps.usage(provider, entry.key) : void 0;
|
|
3569
3584
|
const tags = [
|
|
3570
3585
|
entry.source,
|
|
3571
3586
|
entry === active ? "active" : "",
|
|
@@ -3585,7 +3600,7 @@ function rulesText(deps) {
|
|
|
3585
3600
|
async function modelsText(deps) {
|
|
3586
3601
|
const config = deps.config();
|
|
3587
3602
|
try {
|
|
3588
|
-
const models = await deps.adapter.listModels(
|
|
3603
|
+
const models = await deps.adapter.listModels(GO_PROVIDER, { unfiltered: true });
|
|
3589
3604
|
if (models.length === 0) return "No models.";
|
|
3590
3605
|
return models.map((model) => {
|
|
3591
3606
|
const meta = effectiveModel(model.id, config);
|
|
@@ -3602,7 +3617,7 @@ async function testText(deps, model) {
|
|
|
3602
3617
|
let text = "";
|
|
3603
3618
|
try {
|
|
3604
3619
|
for await (const chunk of deps.adapter.stream({
|
|
3605
|
-
provider:
|
|
3620
|
+
provider: GO_PROVIDER,
|
|
3606
3621
|
model,
|
|
3607
3622
|
messages: [{
|
|
3608
3623
|
role: "user",
|
|
@@ -3650,7 +3665,7 @@ function applyCommands(ctx, deps) {
|
|
|
3650
3665
|
text: await statusText(deps)
|
|
3651
3666
|
};
|
|
3652
3667
|
case "usage": {
|
|
3653
|
-
const usage = await deps.usage(
|
|
3668
|
+
const usage = await deps.usage(GO_PROVIDER);
|
|
3654
3669
|
if (usage?.report === void 0) return {
|
|
3655
3670
|
kind: "error",
|
|
3656
3671
|
text: `Quota unavailable: ${usage?.error ?? "no report"}`
|
|
@@ -4195,7 +4210,7 @@ function aggregateSessions(sessions, days, now = Date.now()) {
|
|
|
4195
4210
|
for (const session of sessions) {
|
|
4196
4211
|
const provider = session.lastUsed?.provider;
|
|
4197
4212
|
const model = session.lastUsed?.model;
|
|
4198
|
-
if (provider !== "
|
|
4213
|
+
if (typeof provider !== "string" || !PLUGIN_AND_LEGACY_PROVIDERS.has(provider) || model === void 0 || model === "") continue;
|
|
4199
4214
|
const usage = session.usage;
|
|
4200
4215
|
if (usage === void 0) continue;
|
|
4201
4216
|
const input = count(usage.uncachedInputTokens);
|
|
@@ -4620,14 +4635,14 @@ async function buildState(ctx, deps) {
|
|
|
4620
4635
|
...info.source === void 0 ? {} : { source: info.source }
|
|
4621
4636
|
};
|
|
4622
4637
|
} catch {}
|
|
4623
|
-
const resolved = await deps.pool.resolveAll(
|
|
4638
|
+
const resolved = await deps.pool.resolveAll(GO_PROVIDER).catch(() => []);
|
|
4624
4639
|
if (!keyInfo.configured) keyInfo = {
|
|
4625
4640
|
...keyInfo,
|
|
4626
4641
|
configured: resolved.length > 0
|
|
4627
4642
|
};
|
|
4628
4643
|
const usageByKey = /* @__PURE__ */ new Map();
|
|
4629
4644
|
await Promise.all([...new Set(resolved.map((entry) => entry.key))].map(async (key) => {
|
|
4630
|
-
const result = await deps.usage(
|
|
4645
|
+
const result = await deps.usage(GO_PROVIDER, key);
|
|
4631
4646
|
usageByKey.set(key, result ?? null);
|
|
4632
4647
|
}));
|
|
4633
4648
|
const usageByLabel = /* @__PURE__ */ new Map();
|
|
@@ -4674,7 +4689,7 @@ async function buildState(ctx, deps) {
|
|
|
4674
4689
|
}
|
|
4675
4690
|
let models = [];
|
|
4676
4691
|
try {
|
|
4677
|
-
models = (await deps.adapter.listModels(
|
|
4692
|
+
models = (await deps.adapter.listModels(GO_PROVIDER, { unfiltered: true })).map((model) => {
|
|
4678
4693
|
const meta = effectiveModel(model.id, config);
|
|
4679
4694
|
return {
|
|
4680
4695
|
id: model.id,
|
|
@@ -4747,10 +4762,10 @@ function applyRoutes(ctx, web, deps) {
|
|
|
4747
4762
|
}
|
|
4748
4763
|
try {
|
|
4749
4764
|
const config = deps.config();
|
|
4750
|
-
const models = await deps.adapter.listModels(
|
|
4765
|
+
const models = await deps.adapter.listModels(GO_PROVIDER, { unfiltered: true }).catch(() => []);
|
|
4751
4766
|
json(res, 200, {
|
|
4752
4767
|
ok: true,
|
|
4753
|
-
providers: [
|
|
4768
|
+
providers: [CONSOLE_PROVIDER, GO_PROVIDER],
|
|
4754
4769
|
accounts: config.accounts.length + 1,
|
|
4755
4770
|
models: models.length
|
|
4756
4771
|
});
|
|
@@ -4904,7 +4919,7 @@ function applyRoutes(ctx, web, deps) {
|
|
|
4904
4919
|
return;
|
|
4905
4920
|
}
|
|
4906
4921
|
const model = typeof body.model === "string" && body.model !== "" ? body.model : "deepseek-v4.1-flash";
|
|
4907
|
-
const provider = body.provider === "opencode" ?
|
|
4922
|
+
const provider = body.provider === "opencode-direct" ? CONSOLE_PROVIDER : GO_PROVIDER;
|
|
4908
4923
|
const started = Date.now();
|
|
4909
4924
|
const controller = new AbortController();
|
|
4910
4925
|
const timer = setTimeout(() => controller.abort(), 6e4);
|
|
@@ -5227,7 +5242,7 @@ function fingerprint(key) {
|
|
|
5227
5242
|
function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
5228
5243
|
const cache = /* @__PURE__ */ new Map();
|
|
5229
5244
|
const read = async (provider, explicitKey) => {
|
|
5230
|
-
if (provider !== "opencode-go") return void 0;
|
|
5245
|
+
if (provider !== "opencode-go-direct" && provider !== "opencode-go") return void 0;
|
|
5231
5246
|
const current = config();
|
|
5232
5247
|
let key;
|
|
5233
5248
|
if (explicitKey !== void 0 && explicitKey !== "") key = explicitKey;
|
|
@@ -5309,18 +5324,18 @@ function apply(ctx, config) {
|
|
|
5309
5324
|
warn: (message) => ctx.logger("llm-opencode").warn(message)
|
|
5310
5325
|
});
|
|
5311
5326
|
ctx.llm.registerConfigurableProviders([{
|
|
5312
|
-
provider:
|
|
5313
|
-
displayName: "OpenCode",
|
|
5327
|
+
provider: CONSOLE_PROVIDER,
|
|
5328
|
+
displayName: "OpenCode (plugin)",
|
|
5314
5329
|
settingsNs: NS,
|
|
5315
5330
|
settingsPath: []
|
|
5316
5331
|
}, {
|
|
5317
|
-
provider:
|
|
5318
|
-
displayName: "OpenCode Go",
|
|
5332
|
+
provider: GO_PROVIDER,
|
|
5333
|
+
displayName: "OpenCode Go (plugin)",
|
|
5319
5334
|
settingsNs: NS,
|
|
5320
5335
|
settingsPath: []
|
|
5321
5336
|
}]);
|
|
5322
|
-
ctx.llm.registerAdapter([
|
|
5323
|
-
ctx.logger("llm-opencode").info(
|
|
5337
|
+
ctx.llm.registerAdapter([CONSOLE_PROVIDER, GO_PROVIDER], adapter);
|
|
5338
|
+
ctx.logger("llm-opencode").info(`registered providers: ${CONSOLE_PROVIDER}, ${GO_PROVIDER}`);
|
|
5324
5339
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
5325
5340
|
settingsCtx.settings.configure({ auto: false }, ctx.fiber);
|
|
5326
5341
|
});
|