@goodandready/dsh-opencode-provider 0.5.24 → 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 +12 -0
- package/README.md +3 -3
- package/README.ru.md +3 -3
- package/README.zh.md +3 -3
- package/lib/client.js +164 -46
- package/lib/index.d.ts +10 -1
- package/lib/index.js +82 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.5.25 — release 2: stale data
|
|
10
|
+
|
|
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.
|
|
12
|
+
- **#88 — stale analytics**: a failed analytics rescan keeps the previous report and heatmap visible with a stale notice and a retry button instead of blanking the panel; successful rescans clear the stale flag.
|
|
13
|
+
- 127 tests.
|
|
14
|
+
|
|
3
15
|
## 0.5.24 — release 1: account safety
|
|
4
16
|
|
|
5
17
|
- **#89 — managed credential cleanup**: keys saved through Settings or imported from `auth.json` are recorded in `~/.dsh/storages/dsh-opencode/managed-credentials.json` (refs only, never secrets). Removing an account asks for confirmation and deletes only plugin-managed credentials after the settings save succeeds; external environment/`auth.json` references are left intact. Cleanup failures are reported separately from the account removal.
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
@@ -96,6 +96,7 @@ window.__ModuleLoader__.load({
|
|
|
96
96
|
runwaySafe: "safe",
|
|
97
97
|
noUsage: "Quota is unavailable.",
|
|
98
98
|
usageError: "Quota error",
|
|
99
|
+
usageStale: "Showing the last successful quota reading; the latest refresh failed.",
|
|
99
100
|
refresh: "Refresh",
|
|
100
101
|
refreshed: "Refreshed",
|
|
101
102
|
loading: "Loading…",
|
|
@@ -144,6 +145,7 @@ window.__ModuleLoader__.load({
|
|
|
144
145
|
heatmapAllAccounts: "All accounts",
|
|
145
146
|
analyticsHint: "Aggregated from local session metadata by last activity. A session that switched models is counted under its last model; failures are not exposed by the projection. Only plugin routes are counted; legacy opencode sessions belong to the built-in catalog.",
|
|
146
147
|
analyticsPartial: "This report is partial: the projection scan hit its file limit or skipped unreadable files.",
|
|
148
|
+
analyticsStale: "Showing the previous successful scan; the latest refresh failed.",
|
|
147
149
|
analyticsEmpty: "No plugin sessions in this window. Legacy opencode sessions belong to the built-in catalog.",
|
|
148
150
|
showAll: "Show all",
|
|
149
151
|
modelSearch: "Search models…",
|
|
@@ -187,6 +189,11 @@ window.__ModuleLoader__.load({
|
|
|
187
189
|
sidebar: "Sidebar",
|
|
188
190
|
showSidebarQuota: "Show the quota card in the sidebar",
|
|
189
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.",
|
|
190
197
|
routing: "Routing",
|
|
191
198
|
routingHint: "Recent account/model choices. No prompts or keys are recorded.",
|
|
192
199
|
routingEmpty: "No routing decisions yet.",
|
|
@@ -197,6 +204,7 @@ window.__ModuleLoader__.load({
|
|
|
197
204
|
routingDefault: "Automatic",
|
|
198
205
|
routingRotation: "Rotation",
|
|
199
206
|
routingFallback: "Quota fallback",
|
|
207
|
+
accountSwitchNotice: "Account switched to {account} ({reason}).",
|
|
200
208
|
routingFailed: "Failed",
|
|
201
209
|
quotaFallbackModel: "Quota fallback model",
|
|
202
210
|
quotaFallbackModelHint: "If Go rejects with a quota error, retry once on this free model through Console/Zen.",
|
|
@@ -314,6 +322,7 @@ window.__ModuleLoader__.load({
|
|
|
314
322
|
runwaySafe: "安全",
|
|
315
323
|
noUsage: "配额不可用。",
|
|
316
324
|
usageError: "配额错误",
|
|
325
|
+
usageStale: "显示上次成功读取的配额;最新刷新失败。",
|
|
317
326
|
refresh: "刷新",
|
|
318
327
|
refreshed: "已刷新",
|
|
319
328
|
loading: "加载中…",
|
|
@@ -362,6 +371,7 @@ window.__ModuleLoader__.load({
|
|
|
362
371
|
heatmapAllAccounts: "全部账户",
|
|
363
372
|
analyticsHint: "按最后活动时间聚合本地会话元数据。切换过模型的会话计入最后使用的模型;本地投影不暴露失败。仅统计插件路由;旧 opencode 会话属于内置目录。",
|
|
364
373
|
analyticsPartial: "此报告不完整:投影扫描达到文件上限或跳过了不可读文件。",
|
|
374
|
+
analyticsStale: "显示上次成功扫描的结果;最新刷新失败。",
|
|
365
375
|
analyticsEmpty: "此窗口内没有插件会话。旧 opencode 会话属于内置目录。",
|
|
366
376
|
showAll: "显示全部",
|
|
367
377
|
modelSearch: "搜索模型…",
|
|
@@ -405,6 +415,11 @@ window.__ModuleLoader__.load({
|
|
|
405
415
|
sidebar: "侧边栏",
|
|
406
416
|
showSidebarQuota: "在侧边栏显示配额卡片",
|
|
407
417
|
showSidebarQuotaHint: "保存后立即更新;其余时间每 60 秒更新一次。",
|
|
418
|
+
usageDisplay: "配额显示",
|
|
419
|
+
usageDisplayAuto: "自动(有 Go 用量时)",
|
|
420
|
+
usageDisplayAlways: "常驻",
|
|
421
|
+
usageDisplayOff: "关闭",
|
|
422
|
+
usageDisplayHint: "自动:仅有 Go 用量报告时显示侧边栏卡片;常驻:始终显示;关闭:隐藏。",
|
|
408
423
|
routing: "路由",
|
|
409
424
|
routingHint: "最近使用的账户/模型选择。不会记录提示词或密钥。",
|
|
410
425
|
routingEmpty: "尚无路由决策。",
|
|
@@ -415,6 +430,7 @@ window.__ModuleLoader__.load({
|
|
|
415
430
|
routingDefault: "自动",
|
|
416
431
|
routingRotation: "轮换",
|
|
417
432
|
routingFallback: "配额回退",
|
|
433
|
+
accountSwitchNotice: "账户已切换为 {account}({reason})。",
|
|
418
434
|
routingFailed: "失败",
|
|
419
435
|
quotaFallbackModel: "配额回退模型",
|
|
420
436
|
quotaFallbackModelHint: "Go 返回配额错误时,通过 Console/Zen 用此免费模型重试一次。",
|
|
@@ -465,6 +481,9 @@ window.__ModuleLoader__.load({
|
|
|
465
481
|
function fetchState() {
|
|
466
482
|
return call("/dsh-opencode/state");
|
|
467
483
|
}
|
|
484
|
+
function fetchNotices() {
|
|
485
|
+
return call("/dsh-opencode/notices");
|
|
486
|
+
}
|
|
468
487
|
function refreshState() {
|
|
469
488
|
return call("/dsh-opencode/refresh", {
|
|
470
489
|
method: "POST",
|
|
@@ -1016,6 +1035,10 @@ window.__ModuleLoader__.load({
|
|
|
1016
1035
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1017
1036
|
className: "oc-bars",
|
|
1018
1037
|
children: [
|
|
1038
|
+
usage.stale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1039
|
+
className: "oc-hint",
|
|
1040
|
+
children: [t("usageStale"), usage.lastSuccessAt === void 0 ? "" : ` (${new Date(usage.lastSuccessAt).toLocaleTimeString()})`]
|
|
1041
|
+
}) : null,
|
|
1019
1042
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1020
1043
|
label: t("fiveHour"),
|
|
1021
1044
|
window: windows.fiveHour
|
|
@@ -1230,7 +1253,10 @@ window.__ModuleLoader__.load({
|
|
|
1230
1253
|
const { t } = props;
|
|
1231
1254
|
const state = useHostState(6e4);
|
|
1232
1255
|
const expanded = usePanelOpen();
|
|
1233
|
-
if (state === 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;
|
|
1234
1260
|
const windows = state.usage?.report?.windows;
|
|
1235
1261
|
const accountLabel = state.accounts.find((account) => account.active)?.label;
|
|
1236
1262
|
const rows = [
|
|
@@ -1344,6 +1370,7 @@ window.__ModuleLoader__.load({
|
|
|
1344
1370
|
const [stateError, setStateError] = (0, react.useState)(null);
|
|
1345
1371
|
const [analytics, setAnalytics] = (0, react.useState)(null);
|
|
1346
1372
|
const [analyticsError, setAnalyticsError] = (0, react.useState)(null);
|
|
1373
|
+
const [analyticsStale, setAnalyticsStale] = (0, react.useState)(false);
|
|
1347
1374
|
const [days, setDays] = (0, react.useState)(7);
|
|
1348
1375
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1349
1376
|
const analyticsSeq = (0, react.useRef)(0);
|
|
@@ -1359,14 +1386,16 @@ window.__ModuleLoader__.load({
|
|
|
1359
1386
|
const seq = analyticsSeq.current + 1;
|
|
1360
1387
|
analyticsSeq.current = seq;
|
|
1361
1388
|
setAnalyticsError(null);
|
|
1389
|
+
setAnalyticsStale(analytics !== null);
|
|
1362
1390
|
fetchAnalytics(windowDays).then((next) => {
|
|
1363
1391
|
if (seq !== analyticsSeq.current) return;
|
|
1364
1392
|
setAnalytics(next);
|
|
1365
1393
|
setAnalyticsError(null);
|
|
1394
|
+
setAnalyticsStale(false);
|
|
1366
1395
|
}).catch((caught) => {
|
|
1367
1396
|
if (seq !== analyticsSeq.current) return;
|
|
1368
|
-
setAnalytics(null);
|
|
1369
1397
|
setAnalyticsError(caught instanceof Error ? caught.message : "analytics failed");
|
|
1398
|
+
setAnalyticsStale(analytics !== null);
|
|
1370
1399
|
});
|
|
1371
1400
|
};
|
|
1372
1401
|
(0, react.useEffect)(() => {
|
|
@@ -1583,59 +1612,69 @@ window.__ModuleLoader__.load({
|
|
|
1583
1612
|
children: t("intro")
|
|
1584
1613
|
})]
|
|
1585
1614
|
}),
|
|
1586
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1615
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1587
1616
|
className: "oc-card",
|
|
1588
|
-
children:
|
|
1617
|
+
children: [
|
|
1589
1618
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1590
1619
|
className: "oc-card-title",
|
|
1591
1620
|
children: t("usageAnalytics")
|
|
1592
1621
|
}),
|
|
1593
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1622
|
+
analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1594
1623
|
className: "oc-hint",
|
|
1595
1624
|
children: [
|
|
1596
1625
|
t("failed"),
|
|
1597
1626
|
": ",
|
|
1598
1627
|
analyticsError
|
|
1599
1628
|
]
|
|
1600
|
-
}),
|
|
1601
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1629
|
+
}) : null,
|
|
1630
|
+
analyticsStale && analytics !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1631
|
+
className: "oc-hint",
|
|
1632
|
+
children: t("analyticsStale")
|
|
1633
|
+
}) : null,
|
|
1634
|
+
analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1602
1635
|
type: "button",
|
|
1603
1636
|
className: "oc-button",
|
|
1604
1637
|
onClick: () => loadAnalytics(days),
|
|
1605
1638
|
children: t("refresh")
|
|
1639
|
+
}) : null,
|
|
1640
|
+
analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1641
|
+
className: "oc-hint",
|
|
1642
|
+
children: t("loading")
|
|
1643
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageAnalyticsView, {
|
|
1644
|
+
analytics,
|
|
1645
|
+
t,
|
|
1646
|
+
days,
|
|
1647
|
+
onDays: setDays
|
|
1606
1648
|
})
|
|
1607
|
-
]
|
|
1608
|
-
className: "oc-card-title",
|
|
1609
|
-
children: t("usageAnalytics")
|
|
1610
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1611
|
-
className: "oc-hint",
|
|
1612
|
-
children: t("loading")
|
|
1613
|
-
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageAnalyticsView, {
|
|
1614
|
-
analytics,
|
|
1615
|
-
t,
|
|
1616
|
-
days,
|
|
1617
|
-
onDays: setDays
|
|
1618
|
-
})
|
|
1649
|
+
]
|
|
1619
1650
|
}),
|
|
1620
1651
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1621
1652
|
className: "oc-card",
|
|
1622
|
-
children: [
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1653
|
+
children: [
|
|
1654
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1655
|
+
className: "oc-card-title",
|
|
1656
|
+
children: t("requestHeatmap")
|
|
1657
|
+
}),
|
|
1658
|
+
analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1659
|
+
className: "oc-hint",
|
|
1660
|
+
children: [
|
|
1661
|
+
t("failed"),
|
|
1662
|
+
": ",
|
|
1663
|
+
analyticsError
|
|
1664
|
+
]
|
|
1665
|
+
}) : null,
|
|
1666
|
+
analyticsStale && analytics !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1667
|
+
className: "oc-hint",
|
|
1668
|
+
children: t("analyticsStale")
|
|
1669
|
+
}) : null,
|
|
1670
|
+
analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1671
|
+
className: "oc-hint",
|
|
1672
|
+
children: t("loading")
|
|
1673
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequestHeatmapView, {
|
|
1674
|
+
heatmap: analytics.heatmap,
|
|
1675
|
+
t
|
|
1676
|
+
})
|
|
1677
|
+
]
|
|
1639
1678
|
})
|
|
1640
1679
|
]
|
|
1641
1680
|
});
|
|
@@ -1660,6 +1699,7 @@ window.__ModuleLoader__.load({
|
|
|
1660
1699
|
maxOutputTokensOverrides: Object.fromEntries(Object.entries(state.config.maxOutputTokensOverrides).map(([id, value]) => [id, String(value)])),
|
|
1661
1700
|
showSidebarQuota: state.config.showSidebarQuota,
|
|
1662
1701
|
activeAccount: state.config.activeAccount,
|
|
1702
|
+
usageDisplay: state.config.usageDisplay,
|
|
1663
1703
|
accounts: state.config.accounts.map((account) => ({ ...account })),
|
|
1664
1704
|
modelAccountRules: state.config.modelAccountRules.map((rule) => ({ ...rule })),
|
|
1665
1705
|
quotaFallbackModel: state.config.quotaFallbackModel,
|
|
@@ -1706,6 +1746,7 @@ window.__ModuleLoader__.load({
|
|
|
1706
1746
|
maxOutputTokensOverrides: numberRecordOrNull(draft.maxOutputTokensOverrides),
|
|
1707
1747
|
showSidebarQuota: draft.showSidebarQuota,
|
|
1708
1748
|
activeAccount: draft.activeAccount.trim(),
|
|
1749
|
+
usageDisplay: draft.usageDisplay,
|
|
1709
1750
|
accounts: draft.accounts.map((account) => ({
|
|
1710
1751
|
label: account.label.trim(),
|
|
1711
1752
|
apiKeyEnv: account.apiKeyEnv.trim()
|
|
@@ -2334,6 +2375,10 @@ window.__ModuleLoader__.load({
|
|
|
2334
2375
|
]
|
|
2335
2376
|
});
|
|
2336
2377
|
})(),
|
|
2378
|
+
defaultAccount?.usage?.stale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2379
|
+
className: "oc-hint",
|
|
2380
|
+
children: t("usageStale")
|
|
2381
|
+
}) : null,
|
|
2337
2382
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2338
2383
|
className: "oc-row",
|
|
2339
2384
|
children: [
|
|
@@ -2435,8 +2480,8 @@ window.__ModuleLoader__.load({
|
|
|
2435
2480
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
|
|
2436
2481
|
className: "oc-card-title",
|
|
2437
2482
|
children: [t("serviceStatus"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2438
|
-
className: `oc-badge ${state.service.usageCheckedAt === void 0 ? "oc-badge-warn" : state.service.usageError === void 0 ? "oc-badge-ok" : "oc-badge-bad"}`,
|
|
2439
|
-
children: state.service.usageCheckedAt === void 0 ? t("notChecked") : state.service.usageError === void 0 ? t("serviceOk") : t("serviceDegraded")
|
|
2483
|
+
className: `oc-badge ${state.service.usageCheckedAt === void 0 ? "oc-badge-warn" : state.service.usageError === void 0 && state.service.usageStale !== true ? "oc-badge-ok" : "oc-badge-bad"}`,
|
|
2484
|
+
children: state.service.usageCheckedAt === void 0 ? t("notChecked") : state.service.usageError === void 0 && state.service.usageStale !== true ? t("serviceOk") : t("serviceDegraded")
|
|
2440
2485
|
})]
|
|
2441
2486
|
}),
|
|
2442
2487
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2447,7 +2492,7 @@ window.__ModuleLoader__.load({
|
|
|
2447
2492
|
children: t("usageEndpoint")
|
|
2448
2493
|
}),
|
|
2449
2494
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2450
|
-
className: `oc-badge ${state.service.usageCheckedAt === void 0 ? "oc-badge-warn" : state.service.usageError === void 0 ? "oc-badge-ok" : "oc-badge-bad"}`,
|
|
2495
|
+
className: `oc-badge ${state.service.usageCheckedAt === void 0 ? "oc-badge-warn" : state.service.usageError === void 0 && state.service.usageStale !== true ? "oc-badge-ok" : "oc-badge-bad"}`,
|
|
2451
2496
|
children: state.service.usageCheckedAt === void 0 ? t("notChecked") : t("checkedAt").replace("{time}", new Date(state.service.usageCheckedAt).toLocaleTimeString())
|
|
2452
2497
|
}),
|
|
2453
2498
|
state.service.usageLatencyMs === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2460,6 +2505,10 @@ window.__ModuleLoader__.load({
|
|
|
2460
2505
|
className: "oc-hint",
|
|
2461
2506
|
children: state.service.usageError
|
|
2462
2507
|
}),
|
|
2508
|
+
state.service.usageStale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2509
|
+
className: "oc-hint",
|
|
2510
|
+
children: t("usageStale")
|
|
2511
|
+
}) : null,
|
|
2463
2512
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2464
2513
|
className: "oc-row",
|
|
2465
2514
|
children: [
|
|
@@ -2641,6 +2690,10 @@ window.__ModuleLoader__.load({
|
|
|
2641
2690
|
known.usage.error
|
|
2642
2691
|
]
|
|
2643
2692
|
}),
|
|
2693
|
+
known?.usage?.stale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2694
|
+
className: "oc-hint",
|
|
2695
|
+
children: t("usageStale")
|
|
2696
|
+
}) : null,
|
|
2644
2697
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2645
2698
|
className: "oc-row",
|
|
2646
2699
|
children: [
|
|
@@ -3115,16 +3168,33 @@ window.__ModuleLoader__.load({
|
|
|
3115
3168
|
children: t("sidebar")
|
|
3116
3169
|
}),
|
|
3117
3170
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3118
|
-
className: "oc-
|
|
3119
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
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
|
+
})]
|
|
3124
3194
|
}),
|
|
3125
3195
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3126
3196
|
className: "oc-hint",
|
|
3127
|
-
children: t("
|
|
3197
|
+
children: t("usageDisplayHint")
|
|
3128
3198
|
})
|
|
3129
3199
|
]
|
|
3130
3200
|
}),
|
|
@@ -3257,6 +3327,7 @@ window.__ModuleLoader__.load({
|
|
|
3257
3327
|
.oc-drag { cursor: grab; color: var(--oc-text-tertiary); user-select: none; font-size: 13px; line-height: 1; }
|
|
3258
3328
|
.oc-drag:active { cursor: grabbing; }
|
|
3259
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; }
|
|
3260
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); }
|
|
3261
3332
|
[class*="_footerActions"]:has(.oc-foot), [class*="_footerActions"]:has(.oc-railButton) { flex-direction: column; }
|
|
3262
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; }
|
|
@@ -3288,6 +3359,47 @@ window.__ModuleLoader__.load({
|
|
|
3288
3359
|
.oc-table th, .oc-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--oc-border-soft); vertical-align: top; }
|
|
3289
3360
|
.oc-table th { font-weight: 600; opacity: 0.8; }
|
|
3290
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
|
+
}
|
|
3291
3403
|
// src/client/index.tsx
|
|
3292
3404
|
function injectCss() {
|
|
3293
3405
|
if (typeof document === "undefined") return () => {};
|
|
@@ -3323,6 +3435,12 @@ window.__ModuleLoader__.load({
|
|
|
3323
3435
|
locale: NS,
|
|
3324
3436
|
inject: () => ({ close: () => ctx.get("layout")?.selectPanel(null) })
|
|
3325
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));
|
|
3326
3444
|
ctx.inject(["layout"], (layoutCtx) => {
|
|
3327
3445
|
const layoutSlots = layoutCtx.slots;
|
|
3328
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
|
-
/**
|
|
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;
|
|
@@ -263,6 +269,9 @@ interface UsageResult {
|
|
|
263
269
|
readonly keyStatus?: UsageKeyStatus;
|
|
264
270
|
readonly latencyMs?: number;
|
|
265
271
|
readonly checkedAt: number;
|
|
272
|
+
/** True when `report` is the last successful reading after a failed refresh. */
|
|
273
|
+
readonly stale?: boolean;
|
|
274
|
+
readonly lastSuccessAt?: number;
|
|
266
275
|
}
|
|
267
276
|
interface UsageWindowPayload {
|
|
268
277
|
status?: string;
|
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.
|
|
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;
|
|
@@ -5033,7 +5048,8 @@ async function buildState(ctx, deps) {
|
|
|
5033
5048
|
service: {
|
|
5034
5049
|
...usage?.checkedAt === void 0 ? {} : { usageCheckedAt: usage.checkedAt },
|
|
5035
5050
|
...usage?.latencyMs === void 0 ? {} : { usageLatencyMs: usage.latencyMs },
|
|
5036
|
-
...usage?.error === void 0 ? {} : { usageError: usage.error }
|
|
5051
|
+
...usage?.error === void 0 ? {} : { usageError: usage.error },
|
|
5052
|
+
...usage?.stale === true ? { usageStale: true } : {}
|
|
5037
5053
|
},
|
|
5038
5054
|
routing: deps.adapter.routingTrace(),
|
|
5039
5055
|
auth: {
|
|
@@ -5073,6 +5089,21 @@ function applyRoutes(ctx, web, deps) {
|
|
|
5073
5089
|
json(res, 500, { error: error instanceof Error ? error.message : "state failed" });
|
|
5074
5090
|
}
|
|
5075
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
|
+
});
|
|
5076
5107
|
route("/dsh-opencode/health", async (req, res) => {
|
|
5077
5108
|
if (req.method !== "GET") {
|
|
5078
5109
|
json(res, 405, { error: "GET required" });
|
|
@@ -5476,6 +5507,32 @@ function buildOpenCodeTuiSection(deps) {
|
|
|
5476
5507
|
value: text.trim() === "true"
|
|
5477
5508
|
})
|
|
5478
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
|
+
},
|
|
5479
5536
|
{
|
|
5480
5537
|
path: ["showSidebarQuota"],
|
|
5481
5538
|
group: "sidebar",
|
|
@@ -5618,6 +5675,8 @@ function fingerprint(key) {
|
|
|
5618
5675
|
/** Cached reader for the (undocumented) Go usage endpoint. Fail-open by design. */
|
|
5619
5676
|
function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
5620
5677
|
const cache = /* @__PURE__ */ new Map();
|
|
5678
|
+
/** Last successful reading per key, kept across explicit cache clears. */
|
|
5679
|
+
const lastSuccess = /* @__PURE__ */ new Map();
|
|
5621
5680
|
const read = async (provider, explicitKey) => {
|
|
5622
5681
|
if (provider !== "opencode-go-direct" && provider !== "opencode-go") return void 0;
|
|
5623
5682
|
const current = config();
|
|
@@ -5635,7 +5694,7 @@ function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
|
5635
5694
|
const cacheKey = `${provider}:${fingerprint(key)}`;
|
|
5636
5695
|
const cached = cache.get(cacheKey);
|
|
5637
5696
|
if (cached !== void 0) {
|
|
5638
|
-
const ttl = cached.report === void 0 ? FAILURE_TTL_MS : SUCCESS_TTL_MS;
|
|
5697
|
+
const ttl = cached.report === void 0 || cached.stale === true ? FAILURE_TTL_MS : SUCCESS_TTL_MS;
|
|
5639
5698
|
if (Date.now() - cached.checkedAt < ttl) return cached;
|
|
5640
5699
|
}
|
|
5641
5700
|
const result = async () => {
|
|
@@ -5693,8 +5752,25 @@ function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
|
5693
5752
|
}
|
|
5694
5753
|
};
|
|
5695
5754
|
const fresh = await result();
|
|
5696
|
-
|
|
5697
|
-
|
|
5755
|
+
const previous = cached?.report !== void 0 ? cached : lastSuccess.get(cacheKey);
|
|
5756
|
+
if (fresh.report === void 0 && previous?.report !== void 0) {
|
|
5757
|
+
const stale = {
|
|
5758
|
+
...fresh,
|
|
5759
|
+
report: previous.report,
|
|
5760
|
+
stale: true,
|
|
5761
|
+
lastSuccessAt: previous.lastSuccessAt ?? previous.checkedAt
|
|
5762
|
+
};
|
|
5763
|
+
cache.set(cacheKey, stale);
|
|
5764
|
+
return stale;
|
|
5765
|
+
}
|
|
5766
|
+
const next = fresh.report === void 0 ? fresh : {
|
|
5767
|
+
...fresh,
|
|
5768
|
+
stale: false,
|
|
5769
|
+
lastSuccessAt: fresh.checkedAt
|
|
5770
|
+
};
|
|
5771
|
+
if (next.report !== void 0) lastSuccess.set(cacheKey, next);
|
|
5772
|
+
cache.set(cacheKey, next);
|
|
5773
|
+
return next;
|
|
5698
5774
|
};
|
|
5699
5775
|
read.clear = () => {
|
|
5700
5776
|
cache.clear();
|
|
@@ -5773,6 +5849,7 @@ function apply(ctx, config) {
|
|
|
5773
5849
|
quotaFallbackModel: () => current().quotaFallbackModel,
|
|
5774
5850
|
autoSwitch: () => current().autoSwitch,
|
|
5775
5851
|
stickySession: () => current().stickySession,
|
|
5852
|
+
usageDisplay: () => current().usageDisplay,
|
|
5776
5853
|
showSidebarQuota: () => current().showSidebarQuota
|
|
5777
5854
|
});
|
|
5778
5855
|
});
|