@goodandready/dsh-opencode-provider 0.5.25 → 0.5.26

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.26 — release 3: quota display and switch notices
4
+
5
+ - **#75 — quota display modes**: new `usageDisplay` setting with `auto`/`always`/`off` (legacy `showSidebarQuota` maps to `always`); `auto` shows the sidebar card when a Go usage report exists. Exposed in Settings and TUI.
6
+ - **#79 — account-switch notice**: routing trace entries now carry the opaque DSH session id; a `conversation.composer.dock` notice shows a non-blocking switch message for the current conversation after rotation or quota fallback. No prompt content or keys are exposed.
7
+ - 130 tests.
8
+
3
9
  ## 0.5.25 — release 2: stale data
4
10
 
5
11
  - **#87 — stale usage**: a failed usage refresh now returns the last successful report with `stale: true` and `lastSuccessAt` instead of discarding it. The quota sidebar/panel and Settings label the stale snapshot; an explicit refresh no longer loses the previous reading.
package/README.md CHANGED
@@ -132,12 +132,12 @@ npm pack
132
132
  Install the **packed tarball** into a profile:
133
133
 
134
134
  ```bash
135
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.25.tgz
135
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.26.tgz
136
136
  # DSH Desktop:
137
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.25.tgz
137
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.26.tgz
138
138
 
139
139
  # Or install the published package:
140
- dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.25
140
+ dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.26
141
141
  ```
142
142
 
143
143
  Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (plugin)** in the model picker.
package/README.ru.md CHANGED
@@ -131,12 +131,12 @@ npm pack
131
131
  Установка из **упакованного tarball**:
132
132
 
133
133
  ```bash
134
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.25.tgz
134
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.26.tgz
135
135
  # DSH Desktop:
136
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.25.tgz
136
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.26.tgz
137
137
 
138
138
  # Либо установка опубликованного пакета из npm:
139
- dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.25
139
+ dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.26
140
140
  ```
141
141
 
142
142
  После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode (plugin)** или **OpenCode Go (plugin)** в списке моделей.
package/README.zh.md CHANGED
@@ -126,12 +126,12 @@ npm pack
126
126
  通过**打包文件 (.tgz)**安装:
127
127
 
128
128
  ```bash
129
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.25.tgz
129
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.26.tgz
130
130
  # DSH 桌面端:
131
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.25.tgz
131
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.26.tgz
132
132
 
133
133
  # 或从 npm 安装已发布版本:
134
- dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.25
134
+ dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.26
135
135
  ```
136
136
 
137
137
  安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
package/lib/client.js CHANGED
@@ -189,6 +189,11 @@ window.__ModuleLoader__.load({
189
189
  sidebar: "Sidebar",
190
190
  showSidebarQuota: "Show the quota card in the sidebar",
191
191
  showSidebarQuotaHint: "The card updates immediately after saving and otherwise every 60 seconds.",
192
+ usageDisplay: "Quota display",
193
+ usageDisplayAuto: "Auto (when Go usage is available)",
194
+ usageDisplayAlways: "Always",
195
+ usageDisplayOff: "Off",
196
+ usageDisplayHint: "Auto shows the sidebar card only when a Go usage report is available; Always keeps it visible; Off stops polling-hidden display.",
192
197
  routing: "Routing",
193
198
  routingHint: "Recent account/model choices. No prompts or keys are recorded.",
194
199
  routingEmpty: "No routing decisions yet.",
@@ -199,6 +204,7 @@ window.__ModuleLoader__.load({
199
204
  routingDefault: "Automatic",
200
205
  routingRotation: "Rotation",
201
206
  routingFallback: "Quota fallback",
207
+ accountSwitchNotice: "Account switched to {account} ({reason}).",
202
208
  routingFailed: "Failed",
203
209
  quotaFallbackModel: "Quota fallback model",
204
210
  quotaFallbackModelHint: "If Go rejects with a quota error, retry once on this free model through Console/Zen.",
@@ -409,6 +415,11 @@ window.__ModuleLoader__.load({
409
415
  sidebar: "侧边栏",
410
416
  showSidebarQuota: "在侧边栏显示配额卡片",
411
417
  showSidebarQuotaHint: "保存后立即更新;其余时间每 60 秒更新一次。",
418
+ usageDisplay: "配额显示",
419
+ usageDisplayAuto: "自动(有 Go 用量时)",
420
+ usageDisplayAlways: "常驻",
421
+ usageDisplayOff: "关闭",
422
+ usageDisplayHint: "自动:仅有 Go 用量报告时显示侧边栏卡片;常驻:始终显示;关闭:隐藏。",
412
423
  routing: "路由",
413
424
  routingHint: "最近使用的账户/模型选择。不会记录提示词或密钥。",
414
425
  routingEmpty: "尚无路由决策。",
@@ -419,6 +430,7 @@ window.__ModuleLoader__.load({
419
430
  routingDefault: "自动",
420
431
  routingRotation: "轮换",
421
432
  routingFallback: "配额回退",
433
+ accountSwitchNotice: "账户已切换为 {account}({reason})。",
422
434
  routingFailed: "失败",
423
435
  quotaFallbackModel: "配额回退模型",
424
436
  quotaFallbackModelHint: "Go 返回配额错误时,通过 Console/Zen 用此免费模型重试一次。",
@@ -469,6 +481,9 @@ window.__ModuleLoader__.load({
469
481
  function fetchState() {
470
482
  return call("/dsh-opencode/state");
471
483
  }
484
+ function fetchNotices() {
485
+ return call("/dsh-opencode/notices");
486
+ }
472
487
  function refreshState() {
473
488
  return call("/dsh-opencode/refresh", {
474
489
  method: "POST",
@@ -1238,7 +1253,10 @@ window.__ModuleLoader__.load({
1238
1253
  const { t } = props;
1239
1254
  const state = useHostState(6e4);
1240
1255
  const expanded = usePanelOpen();
1241
- if (state === null || state.config.showSidebarQuota !== true) return null;
1256
+ if (state === null) return null;
1257
+ const mode = state.config.usageDisplay ?? (state.config.showSidebarQuota === true ? "always" : "auto");
1258
+ if (mode === "off") return null;
1259
+ if (mode === "auto" && state.usage === null) return null;
1242
1260
  const windows = state.usage?.report?.windows;
1243
1261
  const accountLabel = state.accounts.find((account) => account.active)?.label;
1244
1262
  const rows = [
@@ -1681,6 +1699,7 @@ window.__ModuleLoader__.load({
1681
1699
  maxOutputTokensOverrides: Object.fromEntries(Object.entries(state.config.maxOutputTokensOverrides).map(([id, value]) => [id, String(value)])),
1682
1700
  showSidebarQuota: state.config.showSidebarQuota,
1683
1701
  activeAccount: state.config.activeAccount,
1702
+ usageDisplay: state.config.usageDisplay,
1684
1703
  accounts: state.config.accounts.map((account) => ({ ...account })),
1685
1704
  modelAccountRules: state.config.modelAccountRules.map((rule) => ({ ...rule })),
1686
1705
  quotaFallbackModel: state.config.quotaFallbackModel,
@@ -1727,6 +1746,7 @@ window.__ModuleLoader__.load({
1727
1746
  maxOutputTokensOverrides: numberRecordOrNull(draft.maxOutputTokensOverrides),
1728
1747
  showSidebarQuota: draft.showSidebarQuota,
1729
1748
  activeAccount: draft.activeAccount.trim(),
1749
+ usageDisplay: draft.usageDisplay,
1730
1750
  accounts: draft.accounts.map((account) => ({
1731
1751
  label: account.label.trim(),
1732
1752
  apiKeyEnv: account.apiKeyEnv.trim()
@@ -3148,16 +3168,33 @@ window.__ModuleLoader__.load({
3148
3168
  children: t("sidebar")
3149
3169
  }),
3150
3170
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3151
- className: "oc-row",
3152
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3153
- type: "checkbox",
3154
- checked: draft.showSidebarQuota,
3155
- onChange: (event) => update({ showSidebarQuota: event.target.checked })
3156
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("showSidebarQuota") })]
3171
+ className: "oc-field",
3172
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3173
+ className: "oc-label",
3174
+ children: t("usageDisplay")
3175
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3176
+ className: "oc-select",
3177
+ value: draft.usageDisplay,
3178
+ onChange: (event) => update({ usageDisplay: event.target.value }),
3179
+ children: [
3180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3181
+ value: "auto",
3182
+ children: t("usageDisplayAuto")
3183
+ }),
3184
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3185
+ value: "always",
3186
+ children: t("usageDisplayAlways")
3187
+ }),
3188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3189
+ value: "off",
3190
+ children: t("usageDisplayOff")
3191
+ })
3192
+ ]
3193
+ })]
3157
3194
  }),
3158
3195
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3159
3196
  className: "oc-hint",
3160
- children: t("showSidebarQuotaHint")
3197
+ children: t("usageDisplayHint")
3161
3198
  })
3162
3199
  ]
3163
3200
  }),
@@ -3290,6 +3327,7 @@ window.__ModuleLoader__.load({
3290
3327
  .oc-drag { cursor: grab; color: var(--oc-text-tertiary); user-select: none; font-size: 13px; line-height: 1; }
3291
3328
  .oc-drag:active { cursor: grabbing; }
3292
3329
  .oc-notice { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-accent-soft); border: 1px solid var(--oc-accent-border); }
3330
+ .oc-switch-notice { margin: 4px auto; padding: 4px 10px; border: 1px solid var(--oc-border); border-radius: 999px; background: var(--oc-fill-soft); color: var(--oc-text-secondary); font-size: 11px; line-height: 1.4; text-align: center; }
3293
3331
  .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); }
3294
3332
  [class*="_footerActions"]:has(.oc-foot), [class*="_footerActions"]:has(.oc-railButton) { flex-direction: column; }
3295
3333
  .oc-foot { box-sizing: border-box; flex: 1 1 auto; width: 100%; min-width: 0; display: flex; flex-direction: column; gap: 6px; margin: 0 0 4px; padding: 8px; border: 0.5px solid transparent; border-radius: var(--dsw-radius-md, 8px); background: transparent; color: var(--oc-text-secondary); font: inherit; text-align: left; cursor: pointer; }
@@ -3321,6 +3359,47 @@ window.__ModuleLoader__.load({
3321
3359
  .oc-table th, .oc-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--oc-border-soft); vertical-align: top; }
3322
3360
  .oc-table th { font-weight: 600; opacity: 0.8; }
3323
3361
  `;
3362
+ // src/client/switch-notice.tsx
3363
+ /** Ambient notice under the composer when this conversation switched accounts. */
3364
+ function SwitchNotice({ sessionId, t }) {
3365
+ const [notice, setNotice] = (0, react.useState)(null);
3366
+ const seenAt = (0, react.useRef)(Date.now() - 5e3);
3367
+ (0, react.useEffect)(() => {
3368
+ if (sessionId === void 0 || String(sessionId) === "") return;
3369
+ let alive = true;
3370
+ const poll = () => {
3371
+ fetchNotices().then(({ events }) => {
3372
+ if (!alive) return;
3373
+ const event = events.filter((entry) => entry.session === String(sessionId) && entry.at > seenAt.current && entry.ok && (entry.reason === "rotation" || entry.reason === "quota-fallback")).sort((left, right) => right.at - left.at)[0];
3374
+ if (event === void 0) return;
3375
+ seenAt.current = event.at;
3376
+ setNotice({
3377
+ account: event.account,
3378
+ reason: event.reason,
3379
+ at: event.at
3380
+ });
3381
+ }).catch(() => {});
3382
+ };
3383
+ poll();
3384
+ const timer = setInterval(poll, 5e3);
3385
+ return () => {
3386
+ alive = false;
3387
+ clearInterval(timer);
3388
+ };
3389
+ }, [sessionId]);
3390
+ (0, react.useEffect)(() => {
3391
+ if (notice === null) return;
3392
+ const timer = setTimeout(() => setNotice(null), 6e4);
3393
+ return () => clearTimeout(timer);
3394
+ }, [notice]);
3395
+ if (notice === null) return null;
3396
+ const reason = notice.reason === "quota-fallback" ? t("routingFallback") : t("routingRotation");
3397
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3398
+ className: "oc-switch-notice",
3399
+ role: "status",
3400
+ children: t("accountSwitchNotice").replace("{account}", notice.account).replace("{reason}", reason)
3401
+ });
3402
+ }
3324
3403
  // src/client/index.tsx
3325
3404
  function injectCss() {
3326
3405
  if (typeof document === "undefined") return () => {};
@@ -3356,6 +3435,12 @@ window.__ModuleLoader__.load({
3356
3435
  locale: NS,
3357
3436
  inject: () => ({ close: () => ctx.get("layout")?.selectPanel(null) })
3358
3437
  }, QuotaPanel));
3438
+ ctx.slots.inject("conversation.composer.dock", () => slots.register({
3439
+ name: "conversation.composer.dock",
3440
+ id: "opencode-switch-notice",
3441
+ order: 80,
3442
+ locale: NS
3443
+ }, SwitchNotice));
3359
3444
  ctx.inject(["layout"], (layoutCtx) => {
3360
3445
  const layoutSlots = layoutCtx.slots;
3361
3446
  layoutCtx.slots.inject("sidebar.footer.action", () => layoutSlots.register({
package/lib/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Context } from "@deepseek-ai/cordis";
4
4
  import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
5
5
  import { WebServer } from "@deepseek-ai/dsh-host-webserver";
6
6
  type TransportId = 'openai-chat' | 'openai-responses' | 'anthropic' | 'google';
7
+ type UsageDisplay = 'auto' | 'always' | 'off';
7
8
  interface AccountConfig {
8
9
  label?: string;
9
10
  apiKeyEnv?: string;
@@ -66,7 +67,9 @@ interface Config {
66
67
  stickySession?: boolean;
67
68
  /** Pinned account id; empty means automatic. */
68
69
  activeAccount?: string;
69
- /** Show the quota card in the sidebar. */
70
+ /** Sidebar quota visibility; legacy booleans map to always/auto. */
71
+ usageDisplay?: UsageDisplay;
72
+ /** Legacy boolean; used only when `usageDisplay` is absent. */
70
73
  showSidebarQuota?: boolean;
71
74
  /** Product user agent sent to OpenCode. */
72
75
  userAgent?: string;
@@ -97,6 +100,7 @@ interface ResolvedConfig {
97
100
  autoSwitch: boolean;
98
101
  stickySession: boolean;
99
102
  activeAccount: string;
103
+ usageDisplay: UsageDisplay;
100
104
  showSidebarQuota: boolean;
101
105
  userAgent: string;
102
106
  }
@@ -163,6 +167,8 @@ interface RoutingTraceEntry {
163
167
  readonly provider: string;
164
168
  readonly model: string;
165
169
  readonly account: string;
170
+ /** DSH session id; never prompt content. */
171
+ readonly session?: string;
166
172
  readonly reason: RoutingReason;
167
173
  readonly fallback: boolean;
168
174
  readonly ok: boolean;
package/lib/index.js CHANGED
@@ -81,7 +81,7 @@ const DEFAULT_GO_API_BASE = "https://opencode.ai/inference/go";
81
81
  const DEFAULT_GO_ZEN_BASE = "https://opencode.ai/zen/go/v1";
82
82
  const DEFAULT_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
83
83
  const DEFAULT_AUTH_ENTRY = "opencode-go";
84
- const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.25";
84
+ const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.26";
85
85
  const DEFAULT_CONTEXT_WINDOW = 262144;
86
86
  const DEFAULT_MAX_TOKENS = 32768;
87
87
  const DEFAULT_GENERATE_MAX_TOKENS = 32768;
@@ -126,6 +126,11 @@ const Config = z.object({
126
126
  autoSwitch: markVolatile(z.boolean().default(true)),
127
127
  stickySession: markVolatile(z.boolean().default(true)),
128
128
  activeAccount: markVolatile(z.string().default("")),
129
+ usageDisplay: markVolatile(z.union([
130
+ "auto",
131
+ "always",
132
+ "off"
133
+ ])),
129
134
  showSidebarQuota: markVolatile(z.boolean().default(false)),
130
135
  userAgent: markVolatile(z.string().default(DEFAULT_USER_AGENT))
131
136
  });
@@ -222,6 +227,7 @@ function resolveConfig(config) {
222
227
  autoSwitch: config.autoSwitch !== false,
223
228
  stickySession: config.stickySession !== false,
224
229
  activeAccount: typeof config.activeAccount === "string" ? config.activeAccount : "",
230
+ usageDisplay: config.usageDisplay === "always" || config.usageDisplay === "off" ? config.usageDisplay : config.usageDisplay === "auto" ? "auto" : config.showSidebarQuota === true ? "always" : "auto",
225
231
  showSidebarQuota: config.showSidebarQuota === true,
226
232
  userAgent: typeof config.userAgent === "string" && config.userAgent !== "" ? config.userAgent : DEFAULT_USER_AGENT
227
233
  };
@@ -3305,6 +3311,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
3305
3311
  const runStartedAt = Date.now();
3306
3312
  const model = effectiveModel(options.model, config);
3307
3313
  const sessionId = openCodeSessionId(options);
3314
+ const traceSession = options.sessionId === void 0 ? "" : String(options.sessionId);
3308
3315
  const rawKeys = orderKeysForModel(this.deps.resolveKeys !== void 0 ? await this.deps.resolveKeys(options.provider) : [await this.deps.resolveKey(options.provider)], options.model, config.modelAccountRules);
3309
3316
  const rule = matchModelAccount(options.model, config.modelAccountRules);
3310
3317
  let keys = rawKeys;
@@ -3404,6 +3411,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
3404
3411
  provider: options.provider,
3405
3412
  model: options.model,
3406
3413
  account: entry.label ?? "default",
3414
+ ...traceSession === "" ? {} : { session: traceSession },
3407
3415
  reason: attemptReason,
3408
3416
  fallback,
3409
3417
  ok: true
@@ -3418,6 +3426,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
3418
3426
  provider: options.provider,
3419
3427
  model: options.model,
3420
3428
  account: entry.label ?? "default",
3429
+ ...traceSession === "" ? {} : { session: traceSession },
3421
3430
  reason: attemptReason,
3422
3431
  fallback,
3423
3432
  ok: false,
@@ -3446,6 +3455,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
3446
3455
  provider: options.provider,
3447
3456
  model: options.model,
3448
3457
  account: lastAccount,
3458
+ ...traceSession === "" ? {} : { session: traceSession },
3449
3459
  reason,
3450
3460
  fallback,
3451
3461
  ok: false,
@@ -4734,6 +4744,7 @@ function publicConfig(config) {
4734
4744
  autoSwitch: config.autoSwitch,
4735
4745
  stickySession: config.stickySession,
4736
4746
  activeAccount: config.activeAccount,
4747
+ usageDisplay: config.usageDisplay,
4737
4748
  showSidebarQuota: config.showSidebarQuota,
4738
4749
  userAgent: config.userAgent
4739
4750
  };
@@ -4923,6 +4934,10 @@ function configOps(patch) {
4923
4934
  if ("autoSwitch" in patch) set("autoSwitch", patch.autoSwitch === true);
4924
4935
  if ("stickySession" in patch) set("stickySession", patch.stickySession === true);
4925
4936
  if ("activeAccount" in patch) set("activeAccount", patch.activeAccount);
4937
+ if ("usageDisplay" in patch) {
4938
+ const value = patch.usageDisplay;
4939
+ if (value === "auto" || value === "always" || value === "off") set("usageDisplay", value);
4940
+ }
4926
4941
  if ("showSidebarQuota" in patch) set("showSidebarQuota", patch.showSidebarQuota === true);
4927
4942
  if ("userAgent" in patch) set("userAgent", patch.userAgent);
4928
4943
  return ops;
@@ -5074,6 +5089,21 @@ function applyRoutes(ctx, web, deps) {
5074
5089
  json(res, 500, { error: error instanceof Error ? error.message : "state failed" });
5075
5090
  }
5076
5091
  });
5092
+ route("/dsh-opencode/notices", async (req, res) => {
5093
+ if (req.method !== "GET") {
5094
+ json(res, 405, { error: "GET required" });
5095
+ return;
5096
+ }
5097
+ if (!trustedHost(req)) {
5098
+ json(res, 403, { error: "non-local Host refused" });
5099
+ return;
5100
+ }
5101
+ if (!allowedOrigin(req)) {
5102
+ json(res, 403, { error: "cross-origin request refused" });
5103
+ return;
5104
+ }
5105
+ json(res, 200, { events: deps.adapter.routingTrace() });
5106
+ });
5077
5107
  route("/dsh-opencode/health", async (req, res) => {
5078
5108
  if (req.method !== "GET") {
5079
5109
  json(res, 405, { error: "GET required" });
@@ -5477,6 +5507,32 @@ function buildOpenCodeTuiSection(deps) {
5477
5507
  value: text.trim() === "true"
5478
5508
  })
5479
5509
  },
5510
+ {
5511
+ path: ["usageDisplay"],
5512
+ group: "sidebar",
5513
+ kind: "select",
5514
+ label: "Quota display",
5515
+ hint: "Auto, Always, or Off for the sidebar quota card.",
5516
+ options: [
5517
+ {
5518
+ value: "auto",
5519
+ label: "Auto"
5520
+ },
5521
+ {
5522
+ value: "always",
5523
+ label: "Always"
5524
+ },
5525
+ {
5526
+ value: "off",
5527
+ label: "Off"
5528
+ }
5529
+ ],
5530
+ format: (value) => typeof value === "string" ? value : deps.usageDisplay(),
5531
+ parse: (text) => text === "auto" || text === "always" || text === "off" ? {
5532
+ kind: "set",
5533
+ value: text
5534
+ } : void 0
5535
+ },
5480
5536
  {
5481
5537
  path: ["showSidebarQuota"],
5482
5538
  group: "sidebar",
@@ -5793,6 +5849,7 @@ function apply(ctx, config) {
5793
5849
  quotaFallbackModel: () => current().quotaFallbackModel,
5794
5850
  autoSwitch: () => current().autoSwitch,
5795
5851
  stickySession: () => current().stickySession,
5852
+ usageDisplay: () => current().usageDisplay,
5796
5853
  showSidebarQuota: () => current().showSidebarQuota
5797
5854
  });
5798
5855
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-opencode-provider",
3
- "version": "0.5.25",
3
+ "version": "0.5.26",
4
4
  "description": "OpenCode / OpenCode Go LLM provider plugin for DeepSeek Harness (dsh).",
5
5
  "license": "MIT",
6
6
  "type": "module",