@goodandready/dsh-opencode-provider 0.5.18 → 0.5.19
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 +85 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +74 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.19
|
|
4
|
+
|
|
5
|
+
- **Per-model soft cooldown**: quota/free-tier/rate-limit rejections now cool only that key+model (`quota` 5m, `rate-limit` 60s); other models on the same account remain usable. Invalid credentials still cool the account. If every key is cooling, the request degrades to the normal candidate list instead of failing before dispatch.
|
|
6
|
+
- **Service status**: `/state` exposes the usage check time, latency and error, plus catalog source (`live`/`snapshot`), count and fetch time. Settings shows a compact Service status card.
|
|
7
|
+
- 105 tests; engine matrix/install matrix remain green.
|
|
8
|
+
|
|
3
9
|
## 0.5.18
|
|
4
10
|
|
|
5
11
|
- **Compatibility range**: `engines.dsh`, DSH peer ranges and `dsh.compatibility.dsh` now use `>=0.2.1-rc.2 <0.2.2-0`; `dshReleases` keeps the tested rc.2/rc.3/rc.4 records. Future `0.2.1` RCs are admitted automatically; the next minor remains excluded.
|
package/README.md
CHANGED
|
@@ -130,12 +130,12 @@ npm pack
|
|
|
130
130
|
Install the **packed tarball** into a profile:
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
133
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.19.tgz
|
|
134
134
|
# DSH Desktop:
|
|
135
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
135
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.19.tgz
|
|
136
136
|
|
|
137
137
|
# Or install the published package:
|
|
138
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
138
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.19
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (plugin)** in the model picker.
|
package/README.ru.md
CHANGED
|
@@ -129,12 +129,12 @@ npm pack
|
|
|
129
129
|
Установка из **упакованного tarball**:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
132
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.19.tgz
|
|
133
133
|
# DSH Desktop:
|
|
134
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
134
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.19.tgz
|
|
135
135
|
|
|
136
136
|
# Либо установка опубликованного пакета из npm:
|
|
137
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
137
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.19
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode (plugin)** или **OpenCode Go (plugin)** в списке моделей.
|
package/README.zh.md
CHANGED
|
@@ -124,12 +124,12 @@ npm pack
|
|
|
124
124
|
通过**打包文件 (.tgz)**安装:
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
127
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.19.tgz
|
|
128
128
|
# DSH 桌面端:
|
|
129
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
129
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.19.tgz
|
|
130
130
|
|
|
131
131
|
# 或从 npm 安装已发布版本:
|
|
132
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
132
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.19
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
|
package/lib/client.js
CHANGED
|
@@ -25,6 +25,17 @@ window.__ModuleLoader__.load({
|
|
|
25
25
|
keyStatusUnchecked: "Key unchecked",
|
|
26
26
|
checkKey: "Check key",
|
|
27
27
|
keyStatusHint: "A key check reads the usage endpoint; it does not send a model request.",
|
|
28
|
+
serviceStatus: "Service status",
|
|
29
|
+
serviceOk: "Reachable",
|
|
30
|
+
serviceDegraded: "Degraded",
|
|
31
|
+
usageEndpoint: "Usage endpoint",
|
|
32
|
+
catalogStatus: "Model catalog",
|
|
33
|
+
catalogLive: "live",
|
|
34
|
+
catalogSnapshot: "snapshot",
|
|
35
|
+
catalogAge: "updated {age}",
|
|
36
|
+
checkedAt: "checked {time}",
|
|
37
|
+
latencyValue: "{ms} ms",
|
|
38
|
+
notChecked: "not checked",
|
|
28
39
|
keySource: "Source",
|
|
29
40
|
keyWritable: "Writable",
|
|
30
41
|
pasteKey: "Paste a key",
|
|
@@ -218,6 +229,17 @@ window.__ModuleLoader__.load({
|
|
|
218
229
|
keyStatusUnchecked: "未检查",
|
|
219
230
|
checkKey: "检查密钥",
|
|
220
231
|
keyStatusHint: "检查仅读取用量端点,不会发送模型请求。",
|
|
232
|
+
serviceStatus: "服务状态",
|
|
233
|
+
serviceOk: "可访问",
|
|
234
|
+
serviceDegraded: "异常",
|
|
235
|
+
usageEndpoint: "用量端点",
|
|
236
|
+
catalogStatus: "模型目录",
|
|
237
|
+
catalogLive: "实时",
|
|
238
|
+
catalogSnapshot: "快照",
|
|
239
|
+
catalogAge: "更新于 {age}",
|
|
240
|
+
checkedAt: "检查于 {time}",
|
|
241
|
+
latencyValue: "{ms} ms",
|
|
242
|
+
notChecked: "未检查",
|
|
221
243
|
keySource: "来源",
|
|
222
244
|
keyWritable: "可写",
|
|
223
245
|
pasteKey: "粘贴密钥",
|
|
@@ -1854,6 +1876,13 @@ window.__ModuleLoader__.load({
|
|
|
1854
1876
|
if (status === "expired" || status === "invalid") return "oc-badge-bad";
|
|
1855
1877
|
return "oc-badge-warn";
|
|
1856
1878
|
}
|
|
1879
|
+
function ageText(at) {
|
|
1880
|
+
if (at === void 0) return "";
|
|
1881
|
+
const minutes = Math.max(0, Math.round((Date.now() - at) / 6e4));
|
|
1882
|
+
if (minutes < 1) return "now";
|
|
1883
|
+
if (minutes < 60) return `${minutes}m`;
|
|
1884
|
+
return `${Math.round(minutes / 60)}h`;
|
|
1885
|
+
}
|
|
1857
1886
|
function OpenCodeSettingsPage(props) {
|
|
1858
1887
|
const { t } = props;
|
|
1859
1888
|
const [state, setState] = (0, react.useState)(null);
|
|
@@ -2300,6 +2329,62 @@ window.__ModuleLoader__.load({
|
|
|
2300
2329
|
})
|
|
2301
2330
|
]
|
|
2302
2331
|
}),
|
|
2332
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2333
|
+
className: "oc-card",
|
|
2334
|
+
children: [
|
|
2335
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
|
|
2336
|
+
className: "oc-card-title",
|
|
2337
|
+
children: [t("serviceStatus"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2338
|
+
className: `oc-badge ${state.service.usageError === void 0 ? "oc-badge-ok" : "oc-badge-warn"}`,
|
|
2339
|
+
children: state.service.usageError === void 0 ? t("serviceOk") : t("serviceDegraded")
|
|
2340
|
+
})]
|
|
2341
|
+
}),
|
|
2342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2343
|
+
className: "oc-row",
|
|
2344
|
+
children: [
|
|
2345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2346
|
+
className: "oc-hint",
|
|
2347
|
+
children: t("usageEndpoint")
|
|
2348
|
+
}),
|
|
2349
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2350
|
+
className: `oc-badge ${state.service.usageError === void 0 ? "oc-badge-ok" : "oc-badge-bad"}`,
|
|
2351
|
+
children: state.service.usageCheckedAt === void 0 ? t("notChecked") : t("checkedAt").replace("{time}", new Date(state.service.usageCheckedAt).toLocaleTimeString())
|
|
2352
|
+
}),
|
|
2353
|
+
state.service.usageLatencyMs === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2354
|
+
className: "oc-badge",
|
|
2355
|
+
children: t("latencyValue").replace("{ms}", String(state.service.usageLatencyMs))
|
|
2356
|
+
})
|
|
2357
|
+
]
|
|
2358
|
+
}),
|
|
2359
|
+
state.service.usageError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2360
|
+
className: "oc-hint",
|
|
2361
|
+
children: state.service.usageError
|
|
2362
|
+
}),
|
|
2363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2364
|
+
className: "oc-row",
|
|
2365
|
+
children: [
|
|
2366
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2367
|
+
className: "oc-hint",
|
|
2368
|
+
children: t("catalogStatus")
|
|
2369
|
+
}),
|
|
2370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2371
|
+
className: `oc-badge ${state.catalog.source === "live" ? "oc-badge-ok" : "oc-badge-warn"}`,
|
|
2372
|
+
children: [
|
|
2373
|
+
state.catalog.source === "live" ? t("catalogLive") : t("catalogSnapshot"),
|
|
2374
|
+
" · ",
|
|
2375
|
+
state.catalog.count,
|
|
2376
|
+
" ",
|
|
2377
|
+
t("modelCount")
|
|
2378
|
+
]
|
|
2379
|
+
}),
|
|
2380
|
+
state.catalog.fetchedAt === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2381
|
+
className: "oc-badge",
|
|
2382
|
+
children: t("catalogAge").replace("{age}", ageText(state.catalog.fetchedAt))
|
|
2383
|
+
})
|
|
2384
|
+
]
|
|
2385
|
+
})
|
|
2386
|
+
]
|
|
2387
|
+
}),
|
|
2303
2388
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2304
2389
|
className: "oc-card",
|
|
2305
2390
|
children: [
|
package/lib/index.d.ts
CHANGED
|
@@ -190,6 +190,8 @@ declare class OpenCodeAdapter extends LlmAdapter {
|
|
|
190
190
|
private stickyWrites;
|
|
191
191
|
/** key fingerprint → cooldown deadline and the rejection that caused it. */
|
|
192
192
|
private readonly cooldowns;
|
|
193
|
+
/** key + model → short soft rejection; other models stay usable. */
|
|
194
|
+
private readonly modelCooldowns;
|
|
193
195
|
private readonly sticky;
|
|
194
196
|
private readonly routing;
|
|
195
197
|
private warnedTools;
|
|
@@ -215,6 +217,11 @@ declare class OpenCodeAdapter extends LlmAdapter {
|
|
|
215
217
|
private send;
|
|
216
218
|
private keyId;
|
|
217
219
|
private coolingDown;
|
|
220
|
+
/** Current model-scoped soft cooldown for one key, if any. */
|
|
221
|
+
modelHealth(key: string, model: string): {
|
|
222
|
+
coolingUntil: number;
|
|
223
|
+
reason: string;
|
|
224
|
+
} | undefined;
|
|
218
225
|
/** Current cooldown for one resolved key, if any. */
|
|
219
226
|
accountHealth(key: string): {
|
|
220
227
|
coolingUntil: number;
|
|
@@ -225,6 +232,9 @@ declare class OpenCodeAdapter extends LlmAdapter {
|
|
|
225
232
|
private loadCooldowns;
|
|
226
233
|
private pruneCooldowns;
|
|
227
234
|
private persistCooldowns;
|
|
235
|
+
private modelCoolingDown;
|
|
236
|
+
private markRejection;
|
|
237
|
+
private markModelCooldown;
|
|
228
238
|
private markCooldown;
|
|
229
239
|
private isTransient;
|
|
230
240
|
}
|
|
@@ -247,6 +257,7 @@ interface UsageResult {
|
|
|
247
257
|
readonly report?: UsageReport;
|
|
248
258
|
readonly error?: string;
|
|
249
259
|
readonly keyStatus?: UsageKeyStatus;
|
|
260
|
+
readonly latencyMs?: number;
|
|
250
261
|
readonly checkedAt: number;
|
|
251
262
|
}
|
|
252
263
|
interface UsageWindowPayload {
|
package/lib/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const DEFAULT_GO_API_BASE = "https://opencode.ai/inference/go";
|
|
|
18
18
|
const DEFAULT_GO_ZEN_BASE = "https://opencode.ai/zen/go/v1";
|
|
19
19
|
const DEFAULT_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
20
20
|
const DEFAULT_AUTH_ENTRY = "opencode-go";
|
|
21
|
-
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.
|
|
21
|
+
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.19";
|
|
22
22
|
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
23
23
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
24
24
|
const DEFAULT_GENERATE_MAX_TOKENS = 32768;
|
|
@@ -1090,6 +1090,17 @@ function describeModel(meta, provider) {
|
|
|
1090
1090
|
}
|
|
1091
1091
|
return parts.join(" · ");
|
|
1092
1092
|
}
|
|
1093
|
+
function catalogStatus(provider, config, count) {
|
|
1094
|
+
const cached = catalogCache.get(`${provider}|${modelsUrlFor(provider, config)}`);
|
|
1095
|
+
return cached === void 0 ? {
|
|
1096
|
+
source: "snapshot",
|
|
1097
|
+
count
|
|
1098
|
+
} : {
|
|
1099
|
+
source: "live",
|
|
1100
|
+
count,
|
|
1101
|
+
fetchedAt: cached.at
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1093
1104
|
function clearCatalogCache() {
|
|
1094
1105
|
catalogCache.clear();
|
|
1095
1106
|
}
|
|
@@ -3100,6 +3111,8 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3100
3111
|
stickyWrites = Promise.resolve();
|
|
3101
3112
|
/** key fingerprint → cooldown deadline and the rejection that caused it. */
|
|
3102
3113
|
cooldowns = /* @__PURE__ */ new Map();
|
|
3114
|
+
/** key + model → short soft rejection; other models stay usable. */
|
|
3115
|
+
modelCooldowns = /* @__PURE__ */ new Map();
|
|
3103
3116
|
sticky = /* @__PURE__ */ new Map();
|
|
3104
3117
|
routing = [];
|
|
3105
3118
|
warnedTools = false;
|
|
@@ -3202,7 +3215,10 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3202
3215
|
}
|
|
3203
3216
|
}
|
|
3204
3217
|
}
|
|
3205
|
-
const
|
|
3218
|
+
const eligible = fallback ? keys : keys.filter((entry) => !this.coolingDown(entry.key) && !this.modelCoolingDown(entry.key, options.model));
|
|
3219
|
+
const ordered = eligible.length > 0 ? eligible : keys;
|
|
3220
|
+
const forced = !fallback && eligible.length === 0;
|
|
3221
|
+
const initial = ordered[0];
|
|
3206
3222
|
const url = endpointFor(options.provider, model.transport, options.model, config);
|
|
3207
3223
|
let images;
|
|
3208
3224
|
if (hasImages(options)) {
|
|
@@ -3226,8 +3242,8 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3226
3242
|
let lastError;
|
|
3227
3243
|
let lastAccount = initial?.label ?? "default";
|
|
3228
3244
|
let loggedAny = false;
|
|
3229
|
-
for (const [index, entry] of
|
|
3230
|
-
if (!fallback && this.coolingDown(entry.key)) continue;
|
|
3245
|
+
for (const [index, entry] of ordered.entries()) {
|
|
3246
|
+
if (!fallback && !forced && (this.coolingDown(entry.key) || this.modelCoolingDown(entry.key, options.model))) continue;
|
|
3231
3247
|
lastAccount = entry.label ?? "default";
|
|
3232
3248
|
const attemptReason = index === 0 ? reason : "rotation";
|
|
3233
3249
|
const attemptStartedAt = Date.now();
|
|
@@ -3254,9 +3270,9 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3254
3270
|
const error = classifyResponseError(response.status, text, response.headers);
|
|
3255
3271
|
lastError = error;
|
|
3256
3272
|
logAttempt(false, error.code);
|
|
3257
|
-
const canRotate = index <
|
|
3273
|
+
const canRotate = index < ordered.length - 1;
|
|
3258
3274
|
if (error.code === "INVALID_CREDENTIAL" || error.code === "RATE_LIMIT" || error.code === "QUOTA") {
|
|
3259
|
-
if (!fallback) this.
|
|
3275
|
+
if (!fallback) this.markRejection(entry.key, options.model, response.status, response.headers, error.code);
|
|
3260
3276
|
responseFailure = true;
|
|
3261
3277
|
if (canRotate) continue;
|
|
3262
3278
|
}
|
|
@@ -3297,8 +3313,8 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3297
3313
|
}
|
|
3298
3314
|
lastError = classified;
|
|
3299
3315
|
const transient = this.isTransient(classified);
|
|
3300
|
-
if (transient && !responseFailure && !fallback) this.
|
|
3301
|
-
if (!(index <
|
|
3316
|
+
if (transient && !responseFailure && !fallback) this.markRejection(entry.key, options.model, 0, void 0, classified.code);
|
|
3317
|
+
if (!(index < ordered.length - 1 && transient)) {
|
|
3302
3318
|
if (!fallback && config.quotaFallbackModel !== "" && isGoProvider(options.provider) && classified.code === "QUOTA") break;
|
|
3303
3319
|
throw classified;
|
|
3304
3320
|
}
|
|
@@ -3453,6 +3469,20 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3453
3469
|
}
|
|
3454
3470
|
return true;
|
|
3455
3471
|
}
|
|
3472
|
+
/** Current model-scoped soft cooldown for one key, if any. */
|
|
3473
|
+
modelHealth(key, model) {
|
|
3474
|
+
const id = `${this.keyId(key)}:${model}`;
|
|
3475
|
+
const entry = this.modelCooldowns.get(id);
|
|
3476
|
+
if (entry === void 0) return void 0;
|
|
3477
|
+
if (entry.until <= Date.now()) {
|
|
3478
|
+
this.modelCooldowns.delete(id);
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
return {
|
|
3482
|
+
coolingUntil: entry.until,
|
|
3483
|
+
reason: entry.reason
|
|
3484
|
+
};
|
|
3485
|
+
}
|
|
3456
3486
|
/** Current cooldown for one resolved key, if any. */
|
|
3457
3487
|
accountHealth(key) {
|
|
3458
3488
|
const id = this.keyId(key);
|
|
@@ -3505,6 +3535,30 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3505
3535
|
await writeFile(this.cooldownPath, payload, "utf8");
|
|
3506
3536
|
}).catch(() => {});
|
|
3507
3537
|
}
|
|
3538
|
+
modelCoolingDown(key, model) {
|
|
3539
|
+
const id = `${this.keyId(key)}:${model}`;
|
|
3540
|
+
const entry = this.modelCooldowns.get(id);
|
|
3541
|
+
if (entry === void 0) return false;
|
|
3542
|
+
if (entry.until <= Date.now()) {
|
|
3543
|
+
this.modelCooldowns.delete(id);
|
|
3544
|
+
return false;
|
|
3545
|
+
}
|
|
3546
|
+
return true;
|
|
3547
|
+
}
|
|
3548
|
+
markRejection(key, model, status, headers, code) {
|
|
3549
|
+
if (code === "QUOTA" || code === "RATE_LIMIT") {
|
|
3550
|
+
this.markModelCooldown(key, model, code);
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
this.markCooldown(key, status, headers, code);
|
|
3554
|
+
}
|
|
3555
|
+
markModelCooldown(key, model, code) {
|
|
3556
|
+
const delayMs = code === "QUOTA" ? 3e5 : 6e4;
|
|
3557
|
+
this.modelCooldowns.set(`${this.keyId(key)}:${model}`, {
|
|
3558
|
+
until: Date.now() + delayMs,
|
|
3559
|
+
reason: code === "QUOTA" ? "quota" : "rate-limit"
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3508
3562
|
markCooldown(key, status, headers, code) {
|
|
3509
3563
|
let delayMs = 3e4;
|
|
3510
3564
|
let reason = "transport";
|
|
@@ -4779,7 +4833,13 @@ async function buildState(ctx, deps) {
|
|
|
4779
4833
|
key: keyInfo,
|
|
4780
4834
|
accounts,
|
|
4781
4835
|
models,
|
|
4836
|
+
catalog: catalogStatus(GO_PROVIDER, config, models.length),
|
|
4782
4837
|
usage: usage ?? null,
|
|
4838
|
+
service: {
|
|
4839
|
+
...usage?.checkedAt === void 0 ? {} : { usageCheckedAt: usage.checkedAt },
|
|
4840
|
+
...usage?.latencyMs === void 0 ? {} : { usageLatencyMs: usage.latencyMs },
|
|
4841
|
+
...usage?.error === void 0 ? {} : { usageError: usage.error }
|
|
4842
|
+
},
|
|
4783
4843
|
routing: deps.adapter.routingTrace(),
|
|
4784
4844
|
auth: {
|
|
4785
4845
|
entry: config.openCodeAuthEntry,
|
|
@@ -5360,7 +5420,9 @@ function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
|
5360
5420
|
if (Date.now() - cached.checkedAt < ttl) return cached;
|
|
5361
5421
|
}
|
|
5362
5422
|
const result = async () => {
|
|
5423
|
+
const startedAt = Date.now();
|
|
5363
5424
|
const signal = AbortSignal.timeout(USAGE_TIMEOUT_MS);
|
|
5425
|
+
const latency = () => Math.max(0, Date.now() - startedAt);
|
|
5364
5426
|
try {
|
|
5365
5427
|
let response;
|
|
5366
5428
|
let lastNetworkError;
|
|
@@ -5386,23 +5448,27 @@ function createUsageReader(config, resolveKey, fetchImpl = fetch) {
|
|
|
5386
5448
|
return {
|
|
5387
5449
|
error: "usage endpoint rejected the key (inference may still work)",
|
|
5388
5450
|
keyStatus: /expired/i.test(body) ? "expired" : "invalid",
|
|
5451
|
+
latencyMs: latency(),
|
|
5389
5452
|
checkedAt: Date.now()
|
|
5390
5453
|
};
|
|
5391
5454
|
}
|
|
5392
5455
|
if (!response.ok) return {
|
|
5393
5456
|
error: `usage endpoint returned HTTP ${response.status}`,
|
|
5394
5457
|
keyStatus: "unavailable",
|
|
5458
|
+
latencyMs: latency(),
|
|
5395
5459
|
checkedAt: Date.now()
|
|
5396
5460
|
};
|
|
5397
5461
|
return {
|
|
5398
5462
|
report: normalizeUsage(await response.json()),
|
|
5399
5463
|
keyStatus: "ok",
|
|
5464
|
+
latencyMs: latency(),
|
|
5400
5465
|
checkedAt: Date.now()
|
|
5401
5466
|
};
|
|
5402
5467
|
} catch (error) {
|
|
5403
5468
|
return {
|
|
5404
5469
|
error: error instanceof Error ? error.message : "usage request failed",
|
|
5405
5470
|
keyStatus: "unavailable",
|
|
5471
|
+
latencyMs: latency(),
|
|
5406
5472
|
checkedAt: Date.now()
|
|
5407
5473
|
};
|
|
5408
5474
|
}
|