@goodandready/dsh-opencode-provider 0.5.3 → 0.5.4

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.4
4
+
5
+ - **Sidebar settings**: the quota-card switch moved out of Connection into its own **Sidebar** section in Settings → OpenCode and into the TUI; saving now refreshes the sidebar card immediately instead of waiting for the 60-second poll.
6
+ - 85 tests.
7
+
3
8
  ## 0.5.3
4
9
 
5
10
  - **Per-account runway**: the quota panel estimates time to exhaustion for each account from the 5-hour, weekly and monthly burn rate and flags the first window projected to run out before its reset. The estimate is intentionally approximate; only the current percentage and reset time are exposed by the usage endpoint.
package/README.md CHANGED
@@ -123,12 +123,12 @@ npm pack
123
123
  Install the **packed tarball** into a profile:
124
124
 
125
125
  ```bash
126
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.3.tgz
126
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.4.tgz
127
127
  # DSH Desktop:
128
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.3.tgz
128
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.4.tgz
129
129
 
130
130
  # Or install the published package:
131
- dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.3
131
+ dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.4
132
132
  ```
133
133
 
134
134
  Restart dsh (or DSH Desktop), then pick `OpenCode` or `OpenCode Go` in the model picker.
@@ -231,11 +231,12 @@ Open **Settings → OpenCode**:
231
231
  - **Quota** — 5-hour, weekly and monthly bars with reset dates, plus warning banners at 80% and 95%.
232
232
  - **Accounts** — add extra accounts by credential reference, set each account's key from the page, see whether it is configured, pin one, or return to automatic rotation. **Import all from auth.json** copies every OpenCode key into its own credential ref and adds an account for it; **Check all accounts** refreshes the per-account quota/health rows.
233
233
  - **Models** — search, refresh the live catalog, uncheck models to build an allowlist, override the wire transport per model, and see each model's input/output price plus its Go request estimate (`Go 34.7K/5h`). Hover the estimate for the full Go / Go Plus monthly allowance and 5h/week/month request counts; expand **Subscription limits** for a table of every model with both plans.
234
- - **Connection** — API bases, usage endpoint, timeouts, output cap, context override, sidebar-card switch. The OpenCode auth-entry field autocompletes from the keys present in `auth.json`.
234
+ - **Connection** — API bases, usage endpoint, timeouts, output cap, context override. The OpenCode auth-entry field autocompletes from the keys present in `auth.json`.
235
+ - **Sidebar** — show or hide the quota card. The card refreshes immediately after saving and otherwise every 60 seconds.
235
236
  - **Plugin update** — current/latest version, update badge, and **Update now**. It installs the exact registry version through the normal `dsh plugin add` path, blocks a second click while installing, and reports when DSH must be restarted.
236
237
  - **Routing** — sticky account per conversation and the free fallback model used after a Go quota error. The quota panel shows the last routing decisions with their reason.
237
238
 
238
- The sidebar quota card is **off by default**. Enable *Show the quota card in the sidebar* in the settings page; clicking the card opens the full quota panel, which also carries the **Usage by model** table (7/30/90-day window).
239
+ The sidebar quota card is **off by default**. Enable *Show the quota card in the sidebar* under **Sidebar**; the card appears immediately after saving, and clicking it opens the full quota panel with the **Usage by model** table (7/30/90-day window).
239
240
 
240
241
  The Host serves `/dsh-opencode/state`, `/health`, `/analytics`, `/config`, `/key`, `/key/clear`, `/auth-import`, `/refresh`, `/models/refresh`, `/test` and `/update`. Writes require JSON, are refused for cross-origin requests, and are restricted to loopback (`127.0.0.1` / `::1`); reads stay same-origin. This keeps key and configuration writes off a LAN-exposed web server until the host's own auth token can be integrated.
241
242
 
package/README.ru.md CHANGED
@@ -88,6 +88,7 @@ graph LR
88
88
  - Автоматический переход на следующий аккаунт при 401/403/429 или ответах о превышении квот (400/402/403/429).
89
89
  - Недействительный ключ охлаждается 24 часа, превышение квоты — 10 минут (или по заголовку `Retry-After`), rate limit — 60 секунд (или по `Retry-After`).
90
90
  - Если запрос уже начал стриминг, он не переключается посреди ответа; следующий запрос берёт следующий готовый ключ.
91
+ - **Sidebar-карточка квот**: отдельный переключатель в разделе **Sidebar** (и в TUI); после сохранения карточка появляется/исчезает сразу, без ожидания 60-секундного polling.
91
92
  - **Квоты по каждому аккаунту**: отображение индивидуального расхода за 5 часов, неделю и месяц; активный аккаунт управляет сайдбаром и панелью.
92
93
  - **Runway по каждому аккаунту**: оценка времени до исчерпания по среднему темпу каждого окна; отмечается первое окно, которое закончится раньше сброса. Оценка приблизительная: API отдаёт только текущий процент и время сброса.
93
94
  - **Онбординг аккаунтов**: **Import all from auth.json** переносит все ключи OpenCode в отдельные credential ref и добавляет аккаунты; **Check all accounts** обновляет квоты и health по каждому аккаунту.
@@ -120,12 +121,12 @@ npm pack
120
121
  Установка из **упакованного tarball**:
121
122
 
122
123
  ```bash
123
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.3.tgz
124
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.4.tgz
124
125
  # DSH Desktop:
125
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.3.tgz
126
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.4.tgz
126
127
 
127
128
  # Либо установка опубликованного пакета из npm:
128
- dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.3
129
+ dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.4
129
130
  ```
130
131
 
131
132
  После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode** или **OpenCode Go** в списке моделей.
package/README.zh.md CHANGED
@@ -89,6 +89,7 @@ graph LR
89
89
  - 在生成流开始前,遇到 401/403/429 或配额用尽响应(400/402/403/429)自动重试下一个账户。
90
90
  - 无效密钥冷却 24 小时,超额冷却 10 分钟(或遵循 `Retry-After`),速率限制冷却 60 秒。
91
91
  - 正在流式传输中的请求不会被打断,下一个请求会自动使用下一个可用密钥。
92
+ - **侧边栏配额卡片**:独立 **Sidebar** 开关(TUI 也可设置);保存后立即显示/隐藏,无需等待 60 秒轮询。
92
93
  - **多账户额度与健康监控**:每行独立展示 5 小时、每周与每月使用情况,活跃账户驱动侧边栏与配额面板。
93
94
  - **每账户预计可用时长**:按各窗口的平均消耗速度估算耗尽时间,并标记在重置前最先耗尽的窗口;API 仅提供当前百分比与重置时间,因此为近似值。
94
95
  - **模型 ➔ 账户路由规则**:支持 Glob 通配符(如 `*free*`、`deepseek-*`)将模型优先绑定至特定账户,其他账户保留为备用。
@@ -117,12 +118,12 @@ npm pack
117
118
  通过**打包文件 (.tgz)**安装:
118
119
 
119
120
  ```bash
120
- dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.3.tgz
121
+ dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.4.tgz
121
122
  # DSH 桌面端:
122
- dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.3.tgz
123
+ dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.4.tgz
123
124
 
124
125
  # 或从 npm 安装已发布版本:
125
- dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.3
126
+ dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.4
126
127
  ```
127
128
 
128
129
  安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode** 或 **OpenCode Go** 即可。
package/lib/client.js CHANGED
@@ -151,7 +151,9 @@ window.__ModuleLoader__.load({
151
151
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
152
152
  maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
153
153
  contextWindow: "Context override",
154
+ sidebar: "Sidebar",
154
155
  showSidebarQuota: "Show the quota card in the sidebar",
156
+ showSidebarQuotaHint: "The card updates immediately after saving and otherwise every 60 seconds.",
155
157
  routing: "Routing",
156
158
  routingHint: "Recent account/model choices. No prompts or keys are recorded.",
157
159
  routingEmpty: "No routing decisions yet.",
@@ -331,7 +333,9 @@ window.__ModuleLoader__.load({
331
333
  streamIdleTimeoutMs: "流空闲超时(毫秒)",
332
334
  maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
333
335
  contextWindow: "上下文覆盖",
336
+ sidebar: "侧边栏",
334
337
  showSidebarQuota: "在侧边栏显示配额卡片",
338
+ showSidebarQuotaHint: "保存后立即更新;其余时间每 60 秒更新一次。",
335
339
  routing: "路由",
336
340
  routingHint: "最近使用的账户/模型选择。不会记录提示词或密钥。",
337
341
  routingEmpty: "尚无路由决策。",
@@ -454,6 +458,9 @@ window.__ModuleLoader__.load({
454
458
  });
455
459
  }
456
460
  //#endregion
461
+ //#region src/client/events.ts
462
+ const CONFIG_CHANGED_EVENT = "dsh-opencode-config-changed";
463
+ //#endregion
457
464
  //#region src/client/heatmap.tsx
458
465
  const DAYS = [
459
466
  "Mon",
@@ -1014,9 +1021,14 @@ window.__ModuleLoader__.load({
1014
1021
  };
1015
1022
  load();
1016
1023
  const timer = setInterval(load, intervalMs);
1024
+ const onConfigChanged = () => {
1025
+ load();
1026
+ };
1027
+ window.addEventListener(CONFIG_CHANGED_EVENT, onConfigChanged);
1017
1028
  return () => {
1018
1029
  cancelled = true;
1019
1030
  clearInterval(timer);
1031
+ window.removeEventListener(CONFIG_CHANGED_EVENT, onConfigChanged);
1020
1032
  };
1021
1033
  }, [intervalMs]);
1022
1034
  return state;
@@ -1256,8 +1268,13 @@ window.__ModuleLoader__.load({
1256
1268
  load();
1257
1269
  loadAnalytics(days);
1258
1270
  const timer = setInterval(load, 6e4);
1271
+ const onConfigChanged = () => {
1272
+ load();
1273
+ };
1274
+ window.addEventListener(CONFIG_CHANGED_EVENT, onConfigChanged);
1259
1275
  return () => {
1260
1276
  clearInterval(timer);
1277
+ window.removeEventListener(CONFIG_CHANGED_EVENT, onConfigChanged);
1261
1278
  analyticsSeq.current += 1;
1262
1279
  };
1263
1280
  }, [days]);
@@ -1803,6 +1820,7 @@ window.__ModuleLoader__.load({
1803
1820
  setError(null);
1804
1821
  try {
1805
1822
  await saveConfig(patchOf(draft));
1823
+ window.dispatchEvent(new CustomEvent(CONFIG_CHANGED_EVENT));
1806
1824
  setNotice(t("saved"));
1807
1825
  setState(null);
1808
1826
  setDraft(null);
@@ -2657,78 +2675,84 @@ window.__ModuleLoader__.load({
2657
2675
  })
2658
2676
  ]
2659
2677
  }),
2678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2679
+ className: "oc-card",
2680
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2681
+ className: "oc-card-title",
2682
+ children: t("advanced")
2683
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2684
+ className: "oc-grid",
2685
+ children: [
2686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2687
+ label: t("apiKeyEnv"),
2688
+ hint: t("apiKeyEnvHint"),
2689
+ value: draft.apiKeyEnv,
2690
+ onChange: (value) => update({ apiKeyEnv: value })
2691
+ }),
2692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2693
+ label: t("openCodeAuthEntry"),
2694
+ hint: t("openCodeAuthEntryHint"),
2695
+ value: draft.openCodeAuthEntry,
2696
+ onChange: (value) => update({ openCodeAuthEntry: value }),
2697
+ list: "oc-auth-entries"
2698
+ }),
2699
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("datalist", {
2700
+ id: "oc-auth-entries",
2701
+ children: state.auth.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: entry }, entry))
2702
+ }),
2703
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2704
+ label: t("apiBase"),
2705
+ value: draft.apiBase,
2706
+ onChange: (value) => update({ apiBase: value })
2707
+ }),
2708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2709
+ label: t("goApiBase"),
2710
+ value: draft.goApiBase,
2711
+ onChange: (value) => update({ goApiBase: value })
2712
+ }),
2713
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2714
+ label: t("goZenBase"),
2715
+ value: draft.goZenBase,
2716
+ onChange: (value) => update({ goZenBase: value })
2717
+ }),
2718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2719
+ label: t("usageUrl"),
2720
+ value: draft.usageUrl,
2721
+ onChange: (value) => update({ usageUrl: value })
2722
+ }),
2723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2724
+ label: t("requestTimeoutMs"),
2725
+ type: "number",
2726
+ value: draft.requestTimeoutMs,
2727
+ onChange: (value) => update({ requestTimeoutMs: value })
2728
+ }),
2729
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2730
+ label: t("streamIdleTimeoutMs"),
2731
+ type: "number",
2732
+ value: draft.streamIdleTimeoutMs,
2733
+ onChange: (value) => update({ streamIdleTimeoutMs: value })
2734
+ }),
2735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2736
+ label: t("maxOutputTokens"),
2737
+ type: "number",
2738
+ value: draft.maxOutputTokens,
2739
+ onChange: (value) => update({ maxOutputTokens: value })
2740
+ }),
2741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2742
+ label: t("contextWindow"),
2743
+ type: "number",
2744
+ value: draft.contextWindow,
2745
+ onChange: (value) => update({ contextWindow: value })
2746
+ })
2747
+ ]
2748
+ })]
2749
+ }),
2660
2750
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2661
2751
  className: "oc-card",
2662
2752
  children: [
2663
2753
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2664
2754
  className: "oc-card-title",
2665
- children: t("advanced")
2666
- }),
2667
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2668
- className: "oc-grid",
2669
- children: [
2670
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2671
- label: t("apiKeyEnv"),
2672
- hint: t("apiKeyEnvHint"),
2673
- value: draft.apiKeyEnv,
2674
- onChange: (value) => update({ apiKeyEnv: value })
2675
- }),
2676
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2677
- label: t("openCodeAuthEntry"),
2678
- hint: t("openCodeAuthEntryHint"),
2679
- value: draft.openCodeAuthEntry,
2680
- onChange: (value) => update({ openCodeAuthEntry: value }),
2681
- list: "oc-auth-entries"
2682
- }),
2683
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("datalist", {
2684
- id: "oc-auth-entries",
2685
- children: state.auth.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: entry }, entry))
2686
- }),
2687
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2688
- label: t("apiBase"),
2689
- value: draft.apiBase,
2690
- onChange: (value) => update({ apiBase: value })
2691
- }),
2692
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2693
- label: t("goApiBase"),
2694
- value: draft.goApiBase,
2695
- onChange: (value) => update({ goApiBase: value })
2696
- }),
2697
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2698
- label: t("goZenBase"),
2699
- value: draft.goZenBase,
2700
- onChange: (value) => update({ goZenBase: value })
2701
- }),
2702
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2703
- label: t("usageUrl"),
2704
- value: draft.usageUrl,
2705
- onChange: (value) => update({ usageUrl: value })
2706
- }),
2707
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2708
- label: t("requestTimeoutMs"),
2709
- type: "number",
2710
- value: draft.requestTimeoutMs,
2711
- onChange: (value) => update({ requestTimeoutMs: value })
2712
- }),
2713
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2714
- label: t("streamIdleTimeoutMs"),
2715
- type: "number",
2716
- value: draft.streamIdleTimeoutMs,
2717
- onChange: (value) => update({ streamIdleTimeoutMs: value })
2718
- }),
2719
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2720
- label: t("maxOutputTokens"),
2721
- type: "number",
2722
- value: draft.maxOutputTokens,
2723
- onChange: (value) => update({ maxOutputTokens: value })
2724
- }),
2725
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2726
- label: t("contextWindow"),
2727
- type: "number",
2728
- value: draft.contextWindow,
2729
- onChange: (value) => update({ contextWindow: value })
2730
- })
2731
- ]
2755
+ children: t("sidebar")
2732
2756
  }),
2733
2757
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2734
2758
  className: "oc-row",
@@ -2737,6 +2761,10 @@ window.__ModuleLoader__.load({
2737
2761
  checked: draft.showSidebarQuota,
2738
2762
  onChange: (event) => update({ showSidebarQuota: event.target.checked })
2739
2763
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("showSidebarQuota") })]
2764
+ }),
2765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2766
+ className: "oc-hint",
2767
+ children: t("showSidebarQuotaHint")
2740
2768
  })
2741
2769
  ]
2742
2770
  }),
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.3";
20
+ const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.4";
21
21
  const DEFAULT_CONTEXT_WINDOW = 262144;
22
22
  const DEFAULT_MAX_TOKENS = 32768;
23
23
  const DEFAULT_GENERATE_MAX_TOKENS = 32768;
@@ -4871,6 +4871,10 @@ function buildOpenCodeTuiSection(deps) {
4871
4871
  {
4872
4872
  id: "advanced",
4873
4873
  title: "Advanced"
4874
+ },
4875
+ {
4876
+ id: "sidebar",
4877
+ title: "Sidebar"
4874
4878
  }
4875
4879
  ],
4876
4880
  fields: [
@@ -4957,6 +4961,18 @@ function buildOpenCodeTuiSection(deps) {
4957
4961
  value: text.trim() === "true"
4958
4962
  })
4959
4963
  },
4964
+ {
4965
+ path: ["showSidebarQuota"],
4966
+ group: "sidebar",
4967
+ kind: "boolean",
4968
+ label: "Show quota card in sidebar",
4969
+ hint: "Card visibility updates on the next sidebar refresh.",
4970
+ format: (value) => String(value === void 0 ? deps.showSidebarQuota() : value === true),
4971
+ parse: (text) => ({
4972
+ kind: "set",
4973
+ value: text.trim() === "true"
4974
+ })
4975
+ },
4960
4976
  {
4961
4977
  path: ["activeAccount"],
4962
4978
  group: "advanced",
@@ -5028,7 +5044,8 @@ function applyOpenCodeTuiSettings(ctx, deps) {
5028
5044
  activeAccount: deps.activeAccount(),
5029
5045
  filterModelsByPlan: deps.filterModelsByPlan(),
5030
5046
  quotaFallbackModel: deps.quotaFallbackModel(),
5031
- stickySession: deps.stickySession()
5047
+ stickySession: deps.stickySession(),
5048
+ showSidebarQuota: deps.showSidebarQuota()
5032
5049
  });
5033
5050
  let lastSignature = signature();
5034
5051
  const refresh = () => {
@@ -5209,7 +5226,8 @@ function apply(ctx, config) {
5209
5226
  activeAccount: () => current().activeAccount,
5210
5227
  filterModelsByPlan: () => current().filterModelsByPlan,
5211
5228
  quotaFallbackModel: () => current().quotaFallbackModel,
5212
- stickySession: () => current().stickySession
5229
+ stickySession: () => current().stickySession,
5230
+ showSidebarQuota: () => current().showSidebarQuota
5213
5231
  });
5214
5232
  });
5215
5233
  ctx.inject(["webServer"], (webCtx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-opencode-provider",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "OpenCode / OpenCode Go LLM provider plugin for DeepSeek Harness (dsh).",
5
5
  "license": "MIT",
6
6
  "type": "module",