dsh-multi-provider-usage 0.0.0-stage → 0.7.1
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/LICENSE +21 -0
- package/README.md +380 -2
- package/README.zh.md +341 -0
- package/cordis.patch.yml +15 -0
- package/docs/widget.png +0 -0
- package/lib/auto-update.js +559 -0
- package/lib/client.js +1753 -0
- package/lib/index.js +1278 -0
- package/lib/opencode-console.js +321 -0
- package/lib/opencode-session.js +135 -0
- package/lib/providers.js +902 -0
- package/lib/util.js +27 -0
- package/package.json +64 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-multi-provider-usage contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,381 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-multi-provider-usage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A [DSH](https://github.com/deepseek-ai/deepseek-harness) plugin that shows the
|
|
4
|
+
quotas of **OpenCode GO**, **Command Code** and **GLM Code Plan** in one sidebar
|
|
5
|
+
widget. It is modelled on
|
|
6
|
+
[`@astervolans/dsh-opencode-go-usage`](https://github.com/Astervolans/dsh-opencode-go-usage)
|
|
7
|
+
— same dual-half package, same-origin proxy, same sidebar slot, same Plugins
|
|
8
|
+
panel settings page — extended from one provider to three.
|
|
9
|
+
|
|
10
|
+
> The package name is `dsh-multi-provider-usage` because the unscoped
|
|
11
|
+
> `dsh-provider-usage` name is already taken on npm by a different plugin. The
|
|
12
|
+
> HTTP routes (`/provider-usage/*`) and chat command (`/provider-usage`) stay
|
|
13
|
+
> short and stable.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
- **Three providers, one column.** All enabled providers are stacked vertically;
|
|
21
|
+
each header carries a health dot, the provider's primary percentage and its
|
|
22
|
+
top-up balance. Click a provider header to collapse/expand that provider's
|
|
23
|
+
plan windows, quota bars and time bars; the collapse state is remembered
|
|
24
|
+
locally, so any subset can stay folded.
|
|
25
|
+
- **OpenCode GO** — 5-hour, weekly and monthly windows (the API calls the 5-hour field `rolling`). Prefers the live
|
|
26
|
+
`dsh-opencode-go` plugin's active account through its `opencodeGoUsage`
|
|
27
|
+
host service, then falls back to the official `GET /v1/usage` endpoint.
|
|
28
|
+
- **Command Code** — monthly credit pool, 5-hour window and weekly window from
|
|
29
|
+
the official billing/usage endpoints (`/alpha/billing/*`,
|
|
30
|
+
`/alpha/usage/summary`).
|
|
31
|
+
- **GLM Code Plan** — 5-hour token window, weekly token window and monthly MCP
|
|
32
|
+
tool limit from Z.ai / BigModel `/api/monitor/usage/quota/limit`.
|
|
33
|
+
- **Top-up balances** — OpenCode GO shows the OpenCode Zen prepaid balance and
|
|
34
|
+
Command Code shows its purchased + free "Extra Credits" balance, both as a
|
|
35
|
+
badge next to the account/plan and in the `/provider-usage` text. The OpenCode
|
|
36
|
+
console token is auto-discovered from `OPENCODE_CONSOLE_TOKEN`, the OpenCode
|
|
37
|
+
CLI auth file, or its local `opencode.db`; the token never leaves the host.
|
|
38
|
+
When that token is missing, the balance slot becomes a clickable "Set up" chip
|
|
39
|
+
that opens the plugin settings instead of silently showing nothing. Command
|
|
40
|
+
Code's tooltip breaks its amount into purchased + free credits; a credits
|
|
41
|
+
response without either pool renders a muted **No top-up** badge.
|
|
42
|
+
- **Auto-update** — on boot and every configured interval the host checks the
|
|
43
|
+
GitHub release feed. A local `link:`/`file:` checkout is fast-forwarded when it
|
|
44
|
+
is on `main`, clean, and a strict fast-forward. An npm / market / tarball copy
|
|
45
|
+
updates itself: the release tarball is unpacked and each packaged file
|
|
46
|
+
(`lib/`, `package.json`, `cordis.patch.yml`, `docs/`) is written to a temp file
|
|
47
|
+
and renamed over the target, so pnpm's hardlinked store is never edited in
|
|
48
|
+
place. Either way the running Host keeps the old module graph, so the
|
|
49
|
+
widget/settings show "updated — restart DSH". The plugin never restarts DSH by
|
|
50
|
+
itself.
|
|
51
|
+
- **Balance-change animation** — the widget polls on its normal interval; when
|
|
52
|
+
a provider's balance differs from the previous poll, the number counts from
|
|
53
|
+
the old value to the refreshed one while a red/green delta chip (for example
|
|
54
|
+
`-0.1`) floats up and fades away. There is no popup and no turn-event
|
|
55
|
+
tracking, so the animation may appear up to one poll interval later.
|
|
56
|
+
- **Same-origin proxy** — the browser only talks to `/provider-usage/usage` and
|
|
57
|
+
`/provider-usage/settings`; API keys stay in the host process.
|
|
58
|
+
- **Failure isolation** — one broken provider never hides the other two. The
|
|
59
|
+
route answers `200` while any provider succeeds and `502` only when all
|
|
60
|
+
enabled providers fail. Partial endpoint failures keep the provider's numbers
|
|
61
|
+
but turn its health dot amber and name the failed calls in the tooltip.
|
|
62
|
+
- **Settings shortcut** — the widget header's **Settings** button opens this
|
|
63
|
+
bundle's page in the sidebar **Plugins** panel, directly on the configuration
|
|
64
|
+
page below.
|
|
65
|
+
- **Bundle settings page** — sidebar **Plugins** panel → this bundle's card:
|
|
66
|
+
enable/disable each provider, edit credential refs and endpoints, choose the
|
|
67
|
+
GLM region, and see non-network detection status. Save applies immediately.
|
|
68
|
+
The GLM card also carries the **referral link** (Z.ai "Invite Friends, Get
|
|
69
|
+
Credits") as a clickable, copyable row. The URL is a bundle constant: it never
|
|
70
|
+
goes through the settings route and a settings save cannot overwrite it.
|
|
71
|
+
- **`/provider-usage` chat command** — prints every enabled provider's numbers
|
|
72
|
+
as text.
|
|
73
|
+
- **Time bars** — under each quota bar, gray is time already elapsed and blue is
|
|
74
|
+
time remaining, matching the reference widget's geometry.
|
|
75
|
+
- **`x-opencode-session` fix** — injects the real per-conversation harness
|
|
76
|
+
session id into OpenCode GO chat requests at runtime, without patching DSH.
|
|
77
|
+
- **Chinese and English UI** through the DSH locale service.
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
From a checkout:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
dsh plugin --profile desktop add /path/to/dsh-provider-usage
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
or, for the web profile:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
dsh plugin --profile web add /path/to/dsh-provider-usage
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The package declares `dsh.bundle.patch`, so the CLI reconciles it into the
|
|
94
|
+
profile's bundle layer stack automatically. The widget and client bundle are
|
|
95
|
+
part of the profile's client-modules graph, so **restart DSH once** after
|
|
96
|
+
installing (a running host does not rebuild its `window.__DSH_BOOT__` manifest
|
|
97
|
+
for a newly linked package).
|
|
98
|
+
|
|
99
|
+
For a local `link:` dependency pnpm prints a peer-dependency warning
|
|
100
|
+
(`@deepseek-ai/schemastery`, `@deepseek-ai/dsh`). That is expected: the linked
|
|
101
|
+
checkout resolves schemastery from its own `node_modules` in development; a
|
|
102
|
+
normal npm install resolves the peers from the DSH runtime.
|
|
103
|
+
|
|
104
|
+
## Credentials
|
|
105
|
+
|
|
106
|
+
All values are resolved through the DSH credentials domain (the process
|
|
107
|
+
environment, `$DSH_HOME/.credentials.yaml` and `.env` layers). They are never
|
|
108
|
+
returned by the settings route and never reach the browser.
|
|
109
|
+
|
|
110
|
+
| Provider | Default ref | Resolution order | Fallback source |
|
|
111
|
+
|---|---|---|---|
|
|
112
|
+
| OpenCode GO | `OPENCODE_GO_API_KEY` | `opencodeGoApiKeyEnv`, the `dsh-opencode-go` active-account ref (only when that entry is enabled), `OPENCODE_GO_API_KEY`, `OPENCODE_API_KEY` | `GET {baseUrl}/v1/usage` |
|
|
113
|
+
| Command Code | `COMMANDCODE_API_KEY` | `commandCodeApiKeyEnv`, `COMMANDCODE_API_KEY`, then the CLI auth file | `~/.commandcode/auth.json` (`apiKey`) |
|
|
114
|
+
| GLM Code Plan | `ZAI_API_KEY` | `glmApiKeyEnv`, `ZAI_CODING_CN_API_KEY`, `ZAI_API_KEY`, `ZHIPU_API_KEY`, `GLM_API_KEY`, `BIGMODEL_API_KEY` | — |
|
|
115
|
+
|
|
116
|
+
Set a ref in **Plugins → dsh-multi-provider-usage → settings** (or in the
|
|
117
|
+
auto-generated **Settings → Plugins → Plugin configuration** form). A ref is a
|
|
118
|
+
name such as `OPENCODE_GO_API_KEY`; the secret itself lives in DSH's credential
|
|
119
|
+
store.
|
|
120
|
+
|
|
121
|
+
### OpenCode console balance
|
|
122
|
+
|
|
123
|
+
The GO quota API does not include the account's prepaid balance. That comes from
|
|
124
|
+
`GET https://opencode.ai/console/api/billing/status`, which needs the OpenCode
|
|
125
|
+
**console** OAuth token (the GO API key returns 403 there). The host resolves it
|
|
126
|
+
in this order:
|
|
127
|
+
|
|
128
|
+
1. `opencodeConsoleApiKeyEnv` (`OPENCODE_CONSOLE_TOKEN` by default), resolved
|
|
129
|
+
through DSH credentials;
|
|
130
|
+
2. `~/.local/share/opencode/auth.json` (`$XDG_DATA_HOME/opencode/auth.json` when
|
|
131
|
+
set), provider `opencode`;
|
|
132
|
+
3. the `credential` table of `~/.local/share/opencode/opencode.db` (read via
|
|
133
|
+
`node:sqlite` when available; this is where current OpenCode releases keep
|
|
134
|
+
the OAuth grant).
|
|
135
|
+
|
|
136
|
+
The optional `opencodeConsoleAuthFile` overrides the auth file (and its
|
|
137
|
+
directory is used for `opencode.db`), `opencodeConsoleOrgId` supplies the
|
|
138
|
+
`x-org-id` header, and `opencodeConsoleEnabled: false` turns the whole balance
|
|
139
|
+
read off. Balance failures never hide the GO quota: the provider stays `ok` with
|
|
140
|
+
`balance: null` and a `meta.balanceError`. A missing console credential is
|
|
141
|
+
actionable rather than hidden: the balance slot renders a clickable **Set up**
|
|
142
|
+
chip that opens this bundle's page in the Plugins panel, whose tooltip names the
|
|
143
|
+
fix (`opencode auth login` or `OPENCODE_CONSOLE_TOKEN`). Any other balance
|
|
144
|
+
failure renders a muted **Balance —** badge whose tooltip carries the error.
|
|
145
|
+
|
|
146
|
+
### Command Code balance
|
|
147
|
+
|
|
148
|
+
Command Code's quota and balance share one API key and one billing family, so
|
|
149
|
+
they cannot diverge the way OpenCode's do: a missing key (`COMMANDCODE_API_KEY`
|
|
150
|
+
or `~/.commandcode/auth.json`) fails the whole provider with an explicit
|
|
151
|
+
`no API key` message rather than blanking only the balance. Two narrower gaps
|
|
152
|
+
are reported instead of hidden:
|
|
153
|
+
|
|
154
|
+
- when `/alpha/billing/credits` fails, that request carries every window, so the
|
|
155
|
+
provider errors as well; its message now includes the underlying interface and
|
|
156
|
+
status instead of only "no usable quota windows", and `meta.balanceError` lets
|
|
157
|
+
the header show a muted **Balance —** badge carrying the cause;
|
|
158
|
+
- when the credits response succeeds but carries neither `purchasedCredits` nor
|
|
159
|
+
`freeCredits`, `balance` stays `null` with `meta.balanceMissing: true` and the
|
|
160
|
+
header shows a muted **No top-up** badge (a real `0` still renders `$0.00`).
|
|
161
|
+
|
|
162
|
+
### OpenCode GO integration and fallback
|
|
163
|
+
|
|
164
|
+
When `opencodeGoSource` is `opencode-go` (the default), the host probes:
|
|
165
|
+
|
|
166
|
+
1. a live loader entry named `opencode-go` / `dsh-opencode-go`;
|
|
167
|
+
2. that entry is not disabled;
|
|
168
|
+
3. `ctx.get("opencodeGoUsage")` exposes `read()`;
|
|
169
|
+
4. `read()` returns valid `rolling` / `weekly` / `monthly` data;
|
|
170
|
+
5. the active account is read from that entry's `apiKeyEnv` + `accounts[]`.
|
|
171
|
+
|
|
172
|
+
If any step fails, the host reads the built-in endpoint and reports a `fallback`
|
|
173
|
+
object with one of: `not-installed`, `entry-disabled`, `service-unavailable`,
|
|
174
|
+
`read-failed`, `invalid-response`. The widget shows a **Built-in** badge with the
|
|
175
|
+
reason and message in its tooltip. `opencodeGoSource: "builtin"` pins the HTTP
|
|
176
|
+
path and never touches the other plugin.
|
|
177
|
+
|
|
178
|
+
## Plan windows
|
|
179
|
+
|
|
180
|
+
| Provider | Windows | Window length |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| OpenCode GO | `rolling`, `weekly`, `monthly` | 5 h, 7 d, ~30 d |
|
|
183
|
+
| Command Code | `five-hour`, `weekly`, `monthly` | 5 h, 7 d, real billing period |
|
|
184
|
+
| GLM Code Plan | `five-hour`, `weekly`, `mcp` | 5 h, 7 d, monthly |
|
|
185
|
+
|
|
186
|
+
Percentages are **used** percentages and are clamped to `[0, 100]` for display.
|
|
187
|
+
Command Code monthly utilization is
|
|
188
|
+
`totalCost / (totalCost + monthlyCredits)`; purchased and free credits are
|
|
189
|
+
reported separately in `meta.monthly` and do not change the plan bar, while
|
|
190
|
+
their sum is the visible top-up balance (`balance.extra`). The
|
|
191
|
+
5-hour/weekly bars use `windowLimits.fiveHour` / `weekly` (`used`, `cap`,
|
|
192
|
+
`resetAt`, `exceeded`).
|
|
193
|
+
|
|
194
|
+
A GLM account without a Coding Plan answers HTTP 200 with
|
|
195
|
+
`{ "success": false, "msg": ... }`; the adapter surfaces that message and the
|
|
196
|
+
widget shows the provider in an error state.
|
|
197
|
+
|
|
198
|
+
## Configuration
|
|
199
|
+
|
|
200
|
+
The loader reads the exported `Config` schema; every field is `.volatile()`, so
|
|
201
|
+
the settings page and the auto-generated form hot-apply.
|
|
202
|
+
|
|
203
|
+
| Field | Default | Meaning |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `opencodeGoEnabled` | `true` | Show OpenCode GO |
|
|
206
|
+
| `opencodeGoSource` | `"opencode-go"` | `opencode-go` or `builtin` |
|
|
207
|
+
| `opencodeGoApiKeyEnv` | `"OPENCODE_GO_API_KEY"` | Credential ref |
|
|
208
|
+
| `opencodeGoBaseUrl` | `"https://opencode.ai/zen/go"` | Gateway base |
|
|
209
|
+
| `opencodeConsoleEnabled` | `true` | Read the Zen prepaid balance |
|
|
210
|
+
| `opencodeConsoleApiKeyEnv` | `"OPENCODE_CONSOLE_TOKEN"` | Console token ref |
|
|
211
|
+
| `opencodeConsoleOrgId` | `""` | Optional `x-org-id` |
|
|
212
|
+
| `opencodeConsoleAuthFile` | `""` | Optional auth.json path; empty auto-discovers |
|
|
213
|
+
| `opencodeConsoleDbFile` | `""` | Optional opencode.db path; empty derives it |
|
|
214
|
+
| `opencodeConsoleBaseUrl` | `"https://opencode.ai"` | Console origin |
|
|
215
|
+
| `commandCodeEnabled` | `true` | Show Command Code |
|
|
216
|
+
| `commandCodeApiKeyEnv` | `"COMMANDCODE_API_KEY"` | Credential ref |
|
|
217
|
+
| `commandCodeBaseUrl` | `"https://api.commandcode.ai"` | API base |
|
|
218
|
+
| `commandCodeAuthFile` | `""` | Empty = `~/.commandcode/auth.json` |
|
|
219
|
+
| `glmEnabled` | `true` | Show GLM Code Plan |
|
|
220
|
+
| `glmApiKeyEnv` | `"ZAI_API_KEY"` | Credential ref |
|
|
221
|
+
| `glmBaseUrl` | `""` | Optional base override |
|
|
222
|
+
| `glmRegion` | `"auto"` | `auto`, `zai`, `bigmodel`, `custom` |
|
|
223
|
+
| `cacheMs` | `30000` | Per-provider host cache TTL |
|
|
224
|
+
| `updateCheck` | `true` | npm update check (24 h) |
|
|
225
|
+
| `autoUpdate` | `true` | Check GitHub releases; fast-forward a clean checkout, otherwise replace an installed package |
|
|
226
|
+
| `autoUpdateCheckHours` | `6` | Hours between auto-update checks (1–168) |
|
|
227
|
+
| `injectSessionHeader` | `true` | OpenCode GO session-header fix |
|
|
228
|
+
| `providerOrder` | all three ids | Provider display order in the widget; drag the cards in the plugin settings to reorder |
|
|
229
|
+
|
|
230
|
+
`glmRegion: "custom"` requires `glmBaseUrl`; `auto` probes
|
|
231
|
+
`https://api.z.ai` first and then `https://open.bigmodel.cn`.
|
|
232
|
+
|
|
233
|
+
## HTTP API
|
|
234
|
+
|
|
235
|
+
### `GET /provider-usage/usage`
|
|
236
|
+
|
|
237
|
+
`?refresh=1` (or `?force=1`) bypasses the host cache.
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"ok": true,
|
|
242
|
+
"enabled": ["opencode-go", "command-code", "glm"],
|
|
243
|
+
"providerOrder": ["opencode-go", "command-code", "glm"],
|
|
244
|
+
"providers": [
|
|
245
|
+
{
|
|
246
|
+
"id": "opencode-go",
|
|
247
|
+
"name": "OpenCode GO",
|
|
248
|
+
"short": "GO",
|
|
249
|
+
"status": "ok",
|
|
250
|
+
"account": { "ref": "OPENCODE_GO_API_KEY", "id": null, "name": "" },
|
|
251
|
+
"plan": null,
|
|
252
|
+
"source": "builtin",
|
|
253
|
+
"requested": "opencode-go",
|
|
254
|
+
"fallback": {
|
|
255
|
+
"from": "opencode-go",
|
|
256
|
+
"reason": "not-installed",
|
|
257
|
+
"message": "dsh-opencode-go is not installed, or its profile entry is inactive"
|
|
258
|
+
},
|
|
259
|
+
"balance": {
|
|
260
|
+
"amount": 5.01495352,
|
|
261
|
+
"currency": "USD",
|
|
262
|
+
"labelKey": "balance.zen",
|
|
263
|
+
"source": "opencode-db",
|
|
264
|
+
"details": { "billingMode": "prepaid", "mode": "pay-as-you-go" }
|
|
265
|
+
},
|
|
266
|
+
"windows": [
|
|
267
|
+
{
|
|
268
|
+
"key": "rolling",
|
|
269
|
+
"labelKey": "window.five-hour",
|
|
270
|
+
"hintKey": "window.five-hour.hint",
|
|
271
|
+
"percent": 0,
|
|
272
|
+
"status": "ok",
|
|
273
|
+
"resetsAt": "2026-11-05T13:50:46.363Z",
|
|
274
|
+
"windowMs": 18000000
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
"meta": {}
|
|
278
|
+
}
|
|
279
|
+
],
|
|
280
|
+
"update": { "current": "0.1.0", "available": false, "latest": null },
|
|
281
|
+
"sessionHeader": { "active": true, "count": 0, "diag": {} }
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Failed providers have `"status": "error"`, `code`, `error` and `windows: []`;
|
|
286
|
+
their siblings are still returned. A body with `providers: []` means no provider
|
|
287
|
+
is enabled. `meta.balanceError` carries a tolerated balance failure (the
|
|
288
|
+
provider still answers `ok`); `meta.balanceMissing` marks a Command Code credits
|
|
289
|
+
response without purchased/free pools. `meta.errors` lists partial Command Code
|
|
290
|
+
endpoint failures: the numbers stay, the health dot turns amber, and the tooltip
|
|
291
|
+
names the failed calls. Error cards keep the plan they already resolved.
|
|
292
|
+
|
|
293
|
+
### `GET /provider-usage/settings`
|
|
294
|
+
|
|
295
|
+
Returns the settings descriptor (`namespace`, `revision`, `value`, `base`,
|
|
296
|
+
`user`) plus a non-network `detection` report:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{
|
|
300
|
+
"ok": true,
|
|
301
|
+
"namespace": "dsh-multi-provider-usage",
|
|
302
|
+
"revision": 0,
|
|
303
|
+
"value": { "opencodeGoEnabled": true },
|
|
304
|
+
"detection": {
|
|
305
|
+
"providerOrder": ["opencode-go", "command-code", "glm"],
|
|
306
|
+
"enabled": ["opencode-go", "command-code", "glm"],
|
|
307
|
+
"providers": {
|
|
308
|
+
"opencode-go": { "configured": true, "entryFound": false, "fallback": { "...": "..." } },
|
|
309
|
+
"command-code": { "configured": true, "source": "auth-file" },
|
|
310
|
+
"glm": { "configured": true, "region": "auto" }
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`503` means the host has no settings service or the entry has no volatile
|
|
317
|
+
fields; the settings page explains how to fix it.
|
|
318
|
+
|
|
319
|
+
### `POST /provider-usage/settings`
|
|
320
|
+
|
|
321
|
+
Only the fields in the table above are accepted; anything else is ignored.
|
|
322
|
+
`expectedRevision` gets `409` on a concurrent change.
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"patch": { "commandCodeEnabled": false, "providerOrder": ["glm", "opencode-go", "command-code"] },
|
|
327
|
+
"expectedRevision": 0
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Cross-origin writes are refused with `403`. The usage route is read-only.
|
|
332
|
+
|
|
333
|
+
## How it is put together
|
|
334
|
+
|
|
335
|
+
| Half | File | Role |
|
|
336
|
+
|---|---|---|
|
|
337
|
+
| host (Node) | `lib/index.js` | Config schema, routes, command, settings/detection, cache, session fix |
|
|
338
|
+
| host adapters | `lib/providers.js` | Canonical window shape and the three vendor adapters |
|
|
339
|
+
| console balance | `lib/opencode-console.js` | OpenCode console token discovery + billing status |
|
|
340
|
+
| session fix | `lib/opencode-session.js` | `globalThis.fetch` wrapper + `llm/stream` ALS context |
|
|
341
|
+
| browser | `lib/client.js` | Hand-authored `window.__ModuleLoader__.load({ id, factory })` bundle: widget, settings page, locale dictionaries |
|
|
342
|
+
| bundle patch | `cordis.patch.yml` | Inserts the profile entry |
|
|
343
|
+
|
|
344
|
+
The browser bundle is composed by DSH's official client-modules scan from
|
|
345
|
+
`dsh.client` + `exports["./client"]`; it registers into
|
|
346
|
+
`sidebar.footer.action` and `plugins.bundle.config`, exactly like the reference
|
|
347
|
+
plugin.
|
|
348
|
+
|
|
349
|
+
The session fix wraps `globalThis.fetch` once and only adds
|
|
350
|
+
`x-opencode-session` when the request URL starts with the gateway base carried
|
|
351
|
+
by the current `llm/stream` call. The base comes from
|
|
352
|
+
`llm-pi-ai.providers.<route>.baseURL`, falling back to `opencodeGoBaseUrl`.
|
|
353
|
+
|
|
354
|
+
## Compatibility
|
|
355
|
+
|
|
356
|
+
- DSH `0.1.7` and above, including the `0.2.0` line; verified on `0.2.1-rc.5`.
|
|
357
|
+
- `@deepseek-ai/schemastery` `^3.18.4` (volatile fields / settings page).
|
|
358
|
+
- Node.js ≥ 18 (global `fetch`, `AbortSignal.timeout`).
|
|
359
|
+
- `dsh-opencode-go` is optional; its absence is a normal fallback.
|
|
360
|
+
- Command Code's CLI auth file is optional if `COMMANDCODE_API_KEY` is set.
|
|
361
|
+
- A DSH runtime that does not expose `plugins.bundle.config` loses only the
|
|
362
|
+
settings page; the sidebar widget and `/provider-usage` keep working. Set the
|
|
363
|
+
config fields in the profile patch or the auto-generated form instead.
|
|
364
|
+
|
|
365
|
+
## Development
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
npm install
|
|
369
|
+
npm test
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
The tests are hermetic: a fake `globalThis.fetch` and a stub Cordis context, no
|
|
373
|
+
network and no real keys. They cover the schema, all three adapters, OpenCode
|
|
374
|
+
GO service integration/fallback, aggregation and error isolation, caching and
|
|
375
|
+
forced refresh, settings conflicts/whitelisting, the client bundle contract,
|
|
376
|
+
top-up balance discovery (credential and local auth.json) and failure
|
|
377
|
+
isolation, and the session-header injection. 75 tests, all offline.
|
|
378
|
+
|
|
379
|
+
## License
|
|
380
|
+
|
|
381
|
+
MIT.
|