@goodandready/dsh-opencode-provider 0.5.24 → 0.5.25
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 +6 -0
- package/README.md +3 -3
- package/README.ru.md +3 -3
- package/README.zh.md +3 -3
- package/lib/client.js +71 -38
- package/lib/index.d.ts +3 -0
- package/lib/index.js +25 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.25 — release 2: stale data
|
|
4
|
+
|
|
5
|
+
- **#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.
|
|
6
|
+
- **#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.
|
|
7
|
+
- 127 tests.
|
|
8
|
+
|
|
3
9
|
## 0.5.24 — release 1: account safety
|
|
4
10
|
|
|
5
11
|
- **#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.25.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.25.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.25
|
|
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.25.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.25.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.25
|
|
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.25.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.25.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.25
|
|
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…",
|
|
@@ -314,6 +316,7 @@ window.__ModuleLoader__.load({
|
|
|
314
316
|
runwaySafe: "安全",
|
|
315
317
|
noUsage: "配额不可用。",
|
|
316
318
|
usageError: "配额错误",
|
|
319
|
+
usageStale: "显示上次成功读取的配额;最新刷新失败。",
|
|
317
320
|
refresh: "刷新",
|
|
318
321
|
refreshed: "已刷新",
|
|
319
322
|
loading: "加载中…",
|
|
@@ -362,6 +365,7 @@ window.__ModuleLoader__.load({
|
|
|
362
365
|
heatmapAllAccounts: "全部账户",
|
|
363
366
|
analyticsHint: "按最后活动时间聚合本地会话元数据。切换过模型的会话计入最后使用的模型;本地投影不暴露失败。仅统计插件路由;旧 opencode 会话属于内置目录。",
|
|
364
367
|
analyticsPartial: "此报告不完整:投影扫描达到文件上限或跳过了不可读文件。",
|
|
368
|
+
analyticsStale: "显示上次成功扫描的结果;最新刷新失败。",
|
|
365
369
|
analyticsEmpty: "此窗口内没有插件会话。旧 opencode 会话属于内置目录。",
|
|
366
370
|
showAll: "显示全部",
|
|
367
371
|
modelSearch: "搜索模型…",
|
|
@@ -1016,6 +1020,10 @@ window.__ModuleLoader__.load({
|
|
|
1016
1020
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1017
1021
|
className: "oc-bars",
|
|
1018
1022
|
children: [
|
|
1023
|
+
usage.stale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1024
|
+
className: "oc-hint",
|
|
1025
|
+
children: [t("usageStale"), usage.lastSuccessAt === void 0 ? "" : ` (${new Date(usage.lastSuccessAt).toLocaleTimeString()})`]
|
|
1026
|
+
}) : null,
|
|
1019
1027
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Row, {
|
|
1020
1028
|
label: t("fiveHour"),
|
|
1021
1029
|
window: windows.fiveHour
|
|
@@ -1344,6 +1352,7 @@ window.__ModuleLoader__.load({
|
|
|
1344
1352
|
const [stateError, setStateError] = (0, react.useState)(null);
|
|
1345
1353
|
const [analytics, setAnalytics] = (0, react.useState)(null);
|
|
1346
1354
|
const [analyticsError, setAnalyticsError] = (0, react.useState)(null);
|
|
1355
|
+
const [analyticsStale, setAnalyticsStale] = (0, react.useState)(false);
|
|
1347
1356
|
const [days, setDays] = (0, react.useState)(7);
|
|
1348
1357
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1349
1358
|
const analyticsSeq = (0, react.useRef)(0);
|
|
@@ -1359,14 +1368,16 @@ window.__ModuleLoader__.load({
|
|
|
1359
1368
|
const seq = analyticsSeq.current + 1;
|
|
1360
1369
|
analyticsSeq.current = seq;
|
|
1361
1370
|
setAnalyticsError(null);
|
|
1371
|
+
setAnalyticsStale(analytics !== null);
|
|
1362
1372
|
fetchAnalytics(windowDays).then((next) => {
|
|
1363
1373
|
if (seq !== analyticsSeq.current) return;
|
|
1364
1374
|
setAnalytics(next);
|
|
1365
1375
|
setAnalyticsError(null);
|
|
1376
|
+
setAnalyticsStale(false);
|
|
1366
1377
|
}).catch((caught) => {
|
|
1367
1378
|
if (seq !== analyticsSeq.current) return;
|
|
1368
|
-
setAnalytics(null);
|
|
1369
1379
|
setAnalyticsError(caught instanceof Error ? caught.message : "analytics failed");
|
|
1380
|
+
setAnalyticsStale(analytics !== null);
|
|
1370
1381
|
});
|
|
1371
1382
|
};
|
|
1372
1383
|
(0, react.useEffect)(() => {
|
|
@@ -1583,59 +1594,69 @@ window.__ModuleLoader__.load({
|
|
|
1583
1594
|
children: t("intro")
|
|
1584
1595
|
})]
|
|
1585
1596
|
}),
|
|
1586
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1597
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1587
1598
|
className: "oc-card",
|
|
1588
|
-
children:
|
|
1599
|
+
children: [
|
|
1589
1600
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1590
1601
|
className: "oc-card-title",
|
|
1591
1602
|
children: t("usageAnalytics")
|
|
1592
1603
|
}),
|
|
1593
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1604
|
+
analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1594
1605
|
className: "oc-hint",
|
|
1595
1606
|
children: [
|
|
1596
1607
|
t("failed"),
|
|
1597
1608
|
": ",
|
|
1598
1609
|
analyticsError
|
|
1599
1610
|
]
|
|
1600
|
-
}),
|
|
1601
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
1611
|
+
}) : null,
|
|
1612
|
+
analyticsStale && analytics !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1613
|
+
className: "oc-hint",
|
|
1614
|
+
children: t("analyticsStale")
|
|
1615
|
+
}) : null,
|
|
1616
|
+
analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1602
1617
|
type: "button",
|
|
1603
1618
|
className: "oc-button",
|
|
1604
1619
|
onClick: () => loadAnalytics(days),
|
|
1605
1620
|
children: t("refresh")
|
|
1621
|
+
}) : null,
|
|
1622
|
+
analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1623
|
+
className: "oc-hint",
|
|
1624
|
+
children: t("loading")
|
|
1625
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageAnalyticsView, {
|
|
1626
|
+
analytics,
|
|
1627
|
+
t,
|
|
1628
|
+
days,
|
|
1629
|
+
onDays: setDays
|
|
1606
1630
|
})
|
|
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
|
-
})
|
|
1631
|
+
]
|
|
1619
1632
|
}),
|
|
1620
1633
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1621
1634
|
className: "oc-card",
|
|
1622
|
-
children: [
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1635
|
+
children: [
|
|
1636
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1637
|
+
className: "oc-card-title",
|
|
1638
|
+
children: t("requestHeatmap")
|
|
1639
|
+
}),
|
|
1640
|
+
analyticsError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1641
|
+
className: "oc-hint",
|
|
1642
|
+
children: [
|
|
1643
|
+
t("failed"),
|
|
1644
|
+
": ",
|
|
1645
|
+
analyticsError
|
|
1646
|
+
]
|
|
1647
|
+
}) : null,
|
|
1648
|
+
analyticsStale && analytics !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1649
|
+
className: "oc-hint",
|
|
1650
|
+
children: t("analyticsStale")
|
|
1651
|
+
}) : null,
|
|
1652
|
+
analytics === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1653
|
+
className: "oc-hint",
|
|
1654
|
+
children: t("loading")
|
|
1655
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequestHeatmapView, {
|
|
1656
|
+
heatmap: analytics.heatmap,
|
|
1657
|
+
t
|
|
1658
|
+
})
|
|
1659
|
+
]
|
|
1639
1660
|
})
|
|
1640
1661
|
]
|
|
1641
1662
|
});
|
|
@@ -2334,6 +2355,10 @@ window.__ModuleLoader__.load({
|
|
|
2334
2355
|
]
|
|
2335
2356
|
});
|
|
2336
2357
|
})(),
|
|
2358
|
+
defaultAccount?.usage?.stale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2359
|
+
className: "oc-hint",
|
|
2360
|
+
children: t("usageStale")
|
|
2361
|
+
}) : null,
|
|
2337
2362
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2338
2363
|
className: "oc-row",
|
|
2339
2364
|
children: [
|
|
@@ -2435,8 +2460,8 @@ window.__ModuleLoader__.load({
|
|
|
2435
2460
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
|
|
2436
2461
|
className: "oc-card-title",
|
|
2437
2462
|
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")
|
|
2463
|
+
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"}`,
|
|
2464
|
+
children: state.service.usageCheckedAt === void 0 ? t("notChecked") : state.service.usageError === void 0 && state.service.usageStale !== true ? t("serviceOk") : t("serviceDegraded")
|
|
2440
2465
|
})]
|
|
2441
2466
|
}),
|
|
2442
2467
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2447,7 +2472,7 @@ window.__ModuleLoader__.load({
|
|
|
2447
2472
|
children: t("usageEndpoint")
|
|
2448
2473
|
}),
|
|
2449
2474
|
/* @__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"}`,
|
|
2475
|
+
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
2476
|
children: state.service.usageCheckedAt === void 0 ? t("notChecked") : t("checkedAt").replace("{time}", new Date(state.service.usageCheckedAt).toLocaleTimeString())
|
|
2452
2477
|
}),
|
|
2453
2478
|
state.service.usageLatencyMs === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -2460,6 +2485,10 @@ window.__ModuleLoader__.load({
|
|
|
2460
2485
|
className: "oc-hint",
|
|
2461
2486
|
children: state.service.usageError
|
|
2462
2487
|
}),
|
|
2488
|
+
state.service.usageStale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2489
|
+
className: "oc-hint",
|
|
2490
|
+
children: t("usageStale")
|
|
2491
|
+
}) : null,
|
|
2463
2492
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2464
2493
|
className: "oc-row",
|
|
2465
2494
|
children: [
|
|
@@ -2641,6 +2670,10 @@ window.__ModuleLoader__.load({
|
|
|
2641
2670
|
known.usage.error
|
|
2642
2671
|
]
|
|
2643
2672
|
}),
|
|
2673
|
+
known?.usage?.stale === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2674
|
+
className: "oc-hint",
|
|
2675
|
+
children: t("usageStale")
|
|
2676
|
+
}) : null,
|
|
2644
2677
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2645
2678
|
className: "oc-row",
|
|
2646
2679
|
children: [
|
package/lib/index.d.ts
CHANGED
|
@@ -263,6 +263,9 @@ interface UsageResult {
|
|
|
263
263
|
readonly keyStatus?: UsageKeyStatus;
|
|
264
264
|
readonly latencyMs?: number;
|
|
265
265
|
readonly checkedAt: number;
|
|
266
|
+
/** True when `report` is the last successful reading after a failed refresh. */
|
|
267
|
+
readonly stale?: boolean;
|
|
268
|
+
readonly lastSuccessAt?: number;
|
|
266
269
|
}
|
|
267
270
|
interface UsageWindowPayload {
|
|
268
271
|
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.25";
|
|
85
85
|
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
86
86
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
87
87
|
const DEFAULT_GENERATE_MAX_TOKENS = 32768;
|
|
@@ -5033,7 +5033,8 @@ async function buildState(ctx, deps) {
|
|
|
5033
5033
|
service: {
|
|
5034
5034
|
...usage?.checkedAt === void 0 ? {} : { usageCheckedAt: usage.checkedAt },
|
|
5035
5035
|
...usage?.latencyMs === void 0 ? {} : { usageLatencyMs: usage.latencyMs },
|
|
5036
|
-
...usage?.error === void 0 ? {} : { usageError: usage.error }
|
|
5036
|
+
...usage?.error === void 0 ? {} : { usageError: usage.error },
|
|
5037
|
+
...usage?.stale === true ? { usageStale: true } : {}
|
|
5037
5038
|
},
|
|
5038
5039
|
routing: deps.adapter.routingTrace(),
|
|
5039
5040
|
auth: {
|
|
@@ -5618,6 +5619,8 @@ function fingerprint(key) {
|
|
|
5618
5619
|
/** Cached reader for the (undocumented) Go usage endpoint. Fail-open by design. */
|
|
5619
5620
|
function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
5620
5621
|
const cache = /* @__PURE__ */ new Map();
|
|
5622
|
+
/** Last successful reading per key, kept across explicit cache clears. */
|
|
5623
|
+
const lastSuccess = /* @__PURE__ */ new Map();
|
|
5621
5624
|
const read = async (provider, explicitKey) => {
|
|
5622
5625
|
if (provider !== "opencode-go-direct" && provider !== "opencode-go") return void 0;
|
|
5623
5626
|
const current = config();
|
|
@@ -5635,7 +5638,7 @@ function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
|
5635
5638
|
const cacheKey = `${provider}:${fingerprint(key)}`;
|
|
5636
5639
|
const cached = cache.get(cacheKey);
|
|
5637
5640
|
if (cached !== void 0) {
|
|
5638
|
-
const ttl = cached.report === void 0 ? FAILURE_TTL_MS : SUCCESS_TTL_MS;
|
|
5641
|
+
const ttl = cached.report === void 0 || cached.stale === true ? FAILURE_TTL_MS : SUCCESS_TTL_MS;
|
|
5639
5642
|
if (Date.now() - cached.checkedAt < ttl) return cached;
|
|
5640
5643
|
}
|
|
5641
5644
|
const result = async () => {
|
|
@@ -5693,8 +5696,25 @@ function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
|
5693
5696
|
}
|
|
5694
5697
|
};
|
|
5695
5698
|
const fresh = await result();
|
|
5696
|
-
|
|
5697
|
-
|
|
5699
|
+
const previous = cached?.report !== void 0 ? cached : lastSuccess.get(cacheKey);
|
|
5700
|
+
if (fresh.report === void 0 && previous?.report !== void 0) {
|
|
5701
|
+
const stale = {
|
|
5702
|
+
...fresh,
|
|
5703
|
+
report: previous.report,
|
|
5704
|
+
stale: true,
|
|
5705
|
+
lastSuccessAt: previous.lastSuccessAt ?? previous.checkedAt
|
|
5706
|
+
};
|
|
5707
|
+
cache.set(cacheKey, stale);
|
|
5708
|
+
return stale;
|
|
5709
|
+
}
|
|
5710
|
+
const next = fresh.report === void 0 ? fresh : {
|
|
5711
|
+
...fresh,
|
|
5712
|
+
stale: false,
|
|
5713
|
+
lastSuccessAt: fresh.checkedAt
|
|
5714
|
+
};
|
|
5715
|
+
if (next.report !== void 0) lastSuccess.set(cacheKey, next);
|
|
5716
|
+
cache.set(cacheKey, next);
|
|
5717
|
+
return next;
|
|
5698
5718
|
};
|
|
5699
5719
|
read.clear = () => {
|
|
5700
5720
|
cache.clear();
|