@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 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.6.tgz
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.6.tgz
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.6
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 `OpenCode` or `OpenCode Go` in the model picker.
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.6.tgz
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.6.tgz
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.6
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.6.tgz
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.6.tgz
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.6
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 = "opencode-go") {
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: "opencode-go",
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.6";
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 !== "opencode-go" || modelInGoPlan(id, config))).sort((left, right) => {
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 === "opencode-go") {
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.`, "INVALID_CREDENTIAL", options);
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 === "opencode-go" && classified.code === "QUOTA") break;
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 === "opencode-go" && (lastError === void 0 || lastError.code === "QUOTA")) try {
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: "opencode",
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 (status === 401 || status === 403) {
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 ["opencode-go", "opencode"]) {
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("opencode-go", { unfiltered: true });
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: "opencode-go",
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("opencode-go");
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 !== "opencode" && provider !== "opencode-go" || model === void 0 || model === "") continue;
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("opencode-go").catch(() => []);
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("opencode-go", key);
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("opencode-go", { unfiltered: true })).map((model) => {
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("opencode-go", { unfiltered: true }).catch(() => []);
4765
+ const models = await deps.adapter.listModels(GO_PROVIDER, { unfiltered: true }).catch(() => []);
4751
4766
  json(res, 200, {
4752
4767
  ok: true,
4753
- providers: ["opencode", "opencode-go"],
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" ? "opencode" : "opencode-go";
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: "opencode",
5313
- displayName: "OpenCode",
5327
+ provider: CONSOLE_PROVIDER,
5328
+ displayName: "OpenCode (plugin)",
5314
5329
  settingsNs: NS,
5315
5330
  settingsPath: []
5316
5331
  }, {
5317
- provider: "opencode-go",
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(["opencode", "opencode-go"], adapter);
5323
- ctx.logger("llm-opencode").info("registered providers: opencode, opencode-go");
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-opencode-provider",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "description": "OpenCode / OpenCode Go LLM provider plugin for DeepSeek Harness (dsh).",
5
5
  "license": "MIT",
6
6
  "type": "module",