@goodandready/dsh-opencode-provider 0.5.25 → 0.5.27
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 +102 -8
- package/lib/index.d.ts +7 -1
- package/lib/index.js +88 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.27 — release 4: catalog freshness
|
|
4
|
+
|
|
5
|
+
- **#86 — new-model badge**: the live catalog now keeps `release_date` (or `created`) metadata; Settings marks models released within the last seven UTC days with a NEW badge and date tooltip. Dates are dropped safely when the gateway omits or malforms them.
|
|
6
|
+
- **#85 — output ceiling**: the default generated-output cap is 65,536 tokens (was 32,768), matching the reference plugin while still respecting each model's own `maxTokens` and explicit caller limits.
|
|
7
|
+
- 133 tests.
|
|
8
|
+
|
|
9
|
+
## 0.5.26 — release 3: quota display and switch notices
|
|
10
|
+
|
|
11
|
+
- **#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.
|
|
12
|
+
- **#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.
|
|
13
|
+
- 130 tests.
|
|
14
|
+
|
|
3
15
|
## 0.5.25 — release 2: stale data
|
|
4
16
|
|
|
5
17
|
- **#87 — stale usage**: a failed usage refresh now returns the last successful report with `stale: true` and `lastSuccessAt` instead of discarding it. The quota sidebar/panel and Settings label the stale snapshot; an explicit refresh no longer loses the previous reading.
|
package/README.md
CHANGED
|
@@ -132,12 +132,12 @@ npm pack
|
|
|
132
132
|
Install the **packed tarball** into a profile:
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
135
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.27.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.27.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.27
|
|
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.27.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.27.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.27
|
|
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.27.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.27.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.27
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
|
package/lib/client.js
CHANGED
|
@@ -153,6 +153,8 @@ window.__ModuleLoader__.load({
|
|
|
153
153
|
transportAuto: "Auto",
|
|
154
154
|
free: "FREE",
|
|
155
155
|
vision: "Image",
|
|
156
|
+
newModel: "NEW",
|
|
157
|
+
newModelHint: "Released within the last seven UTC days.",
|
|
156
158
|
sectionFree: "Free models",
|
|
157
159
|
sectionFreeHint: "No charge on any plan.",
|
|
158
160
|
sectionGo: "Go subscription",
|
|
@@ -189,6 +191,11 @@ window.__ModuleLoader__.load({
|
|
|
189
191
|
sidebar: "Sidebar",
|
|
190
192
|
showSidebarQuota: "Show the quota card in the sidebar",
|
|
191
193
|
showSidebarQuotaHint: "The card updates immediately after saving and otherwise every 60 seconds.",
|
|
194
|
+
usageDisplay: "Quota display",
|
|
195
|
+
usageDisplayAuto: "Auto (when Go usage is available)",
|
|
196
|
+
usageDisplayAlways: "Always",
|
|
197
|
+
usageDisplayOff: "Off",
|
|
198
|
+
usageDisplayHint: "Auto shows the sidebar card only when a Go usage report is available; Always keeps it visible; Off stops polling-hidden display.",
|
|
192
199
|
routing: "Routing",
|
|
193
200
|
routingHint: "Recent account/model choices. No prompts or keys are recorded.",
|
|
194
201
|
routingEmpty: "No routing decisions yet.",
|
|
@@ -199,6 +206,7 @@ window.__ModuleLoader__.load({
|
|
|
199
206
|
routingDefault: "Automatic",
|
|
200
207
|
routingRotation: "Rotation",
|
|
201
208
|
routingFallback: "Quota fallback",
|
|
209
|
+
accountSwitchNotice: "Account switched to {account} ({reason}).",
|
|
202
210
|
routingFailed: "Failed",
|
|
203
211
|
quotaFallbackModel: "Quota fallback model",
|
|
204
212
|
quotaFallbackModelHint: "If Go rejects with a quota error, retry once on this free model through Console/Zen.",
|
|
@@ -373,6 +381,8 @@ window.__ModuleLoader__.load({
|
|
|
373
381
|
transportAuto: "自动",
|
|
374
382
|
free: "免费",
|
|
375
383
|
vision: "图片",
|
|
384
|
+
newModel: "NEW",
|
|
385
|
+
newModelHint: "最近七个 UTC 日内发布。",
|
|
376
386
|
sectionFree: "免费模型",
|
|
377
387
|
sectionFreeHint: "任何计划均不收费。",
|
|
378
388
|
sectionGo: "Go 订阅",
|
|
@@ -409,6 +419,11 @@ window.__ModuleLoader__.load({
|
|
|
409
419
|
sidebar: "侧边栏",
|
|
410
420
|
showSidebarQuota: "在侧边栏显示配额卡片",
|
|
411
421
|
showSidebarQuotaHint: "保存后立即更新;其余时间每 60 秒更新一次。",
|
|
422
|
+
usageDisplay: "配额显示",
|
|
423
|
+
usageDisplayAuto: "自动(有 Go 用量时)",
|
|
424
|
+
usageDisplayAlways: "常驻",
|
|
425
|
+
usageDisplayOff: "关闭",
|
|
426
|
+
usageDisplayHint: "自动:仅有 Go 用量报告时显示侧边栏卡片;常驻:始终显示;关闭:隐藏。",
|
|
412
427
|
routing: "路由",
|
|
413
428
|
routingHint: "最近使用的账户/模型选择。不会记录提示词或密钥。",
|
|
414
429
|
routingEmpty: "尚无路由决策。",
|
|
@@ -419,6 +434,7 @@ window.__ModuleLoader__.load({
|
|
|
419
434
|
routingDefault: "自动",
|
|
420
435
|
routingRotation: "轮换",
|
|
421
436
|
routingFallback: "配额回退",
|
|
437
|
+
accountSwitchNotice: "账户已切换为 {account}({reason})。",
|
|
422
438
|
routingFailed: "失败",
|
|
423
439
|
quotaFallbackModel: "配额回退模型",
|
|
424
440
|
quotaFallbackModelHint: "Go 返回配额错误时,通过 Console/Zen 用此免费模型重试一次。",
|
|
@@ -469,6 +485,9 @@ window.__ModuleLoader__.load({
|
|
|
469
485
|
function fetchState() {
|
|
470
486
|
return call("/dsh-opencode/state");
|
|
471
487
|
}
|
|
488
|
+
function fetchNotices() {
|
|
489
|
+
return call("/dsh-opencode/notices");
|
|
490
|
+
}
|
|
472
491
|
function refreshState() {
|
|
473
492
|
return call("/dsh-opencode/refresh", {
|
|
474
493
|
method: "POST",
|
|
@@ -1238,7 +1257,10 @@ window.__ModuleLoader__.load({
|
|
|
1238
1257
|
const { t } = props;
|
|
1239
1258
|
const state = useHostState(6e4);
|
|
1240
1259
|
const expanded = usePanelOpen();
|
|
1241
|
-
if (state === null
|
|
1260
|
+
if (state === null) return null;
|
|
1261
|
+
const mode = state.config.usageDisplay ?? (state.config.showSidebarQuota === true ? "always" : "auto");
|
|
1262
|
+
if (mode === "off") return null;
|
|
1263
|
+
if (mode === "auto" && state.usage === null) return null;
|
|
1242
1264
|
const windows = state.usage?.report?.windows;
|
|
1243
1265
|
const accountLabel = state.accounts.find((account) => account.active)?.label;
|
|
1244
1266
|
const rows = [
|
|
@@ -1681,6 +1703,7 @@ window.__ModuleLoader__.load({
|
|
|
1681
1703
|
maxOutputTokensOverrides: Object.fromEntries(Object.entries(state.config.maxOutputTokensOverrides).map(([id, value]) => [id, String(value)])),
|
|
1682
1704
|
showSidebarQuota: state.config.showSidebarQuota,
|
|
1683
1705
|
activeAccount: state.config.activeAccount,
|
|
1706
|
+
usageDisplay: state.config.usageDisplay,
|
|
1684
1707
|
accounts: state.config.accounts.map((account) => ({ ...account })),
|
|
1685
1708
|
modelAccountRules: state.config.modelAccountRules.map((rule) => ({ ...rule })),
|
|
1686
1709
|
quotaFallbackModel: state.config.quotaFallbackModel,
|
|
@@ -1727,6 +1750,7 @@ window.__ModuleLoader__.load({
|
|
|
1727
1750
|
maxOutputTokensOverrides: numberRecordOrNull(draft.maxOutputTokensOverrides),
|
|
1728
1751
|
showSidebarQuota: draft.showSidebarQuota,
|
|
1729
1752
|
activeAccount: draft.activeAccount.trim(),
|
|
1753
|
+
usageDisplay: draft.usageDisplay,
|
|
1730
1754
|
accounts: draft.accounts.map((account) => ({
|
|
1731
1755
|
label: account.label.trim(),
|
|
1732
1756
|
apiKeyEnv: account.apiKeyEnv.trim()
|
|
@@ -2138,6 +2162,11 @@ window.__ModuleLoader__.load({
|
|
|
2138
2162
|
title: t("legendFree"),
|
|
2139
2163
|
children: [" ", t("free")]
|
|
2140
2164
|
}) : null,
|
|
2165
|
+
model.isNew === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2166
|
+
className: "oc-badge oc-badge-ok",
|
|
2167
|
+
title: `${t("newModelHint")} ${model.releaseDate ?? ""}`,
|
|
2168
|
+
children: t("newModel")
|
|
2169
|
+
}) : null,
|
|
2141
2170
|
!model.free && hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2142
2171
|
className: "oc-badge",
|
|
2143
2172
|
title: t("legendPrice"),
|
|
@@ -3148,16 +3177,33 @@ window.__ModuleLoader__.load({
|
|
|
3148
3177
|
children: t("sidebar")
|
|
3149
3178
|
}),
|
|
3150
3179
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3151
|
-
className: "oc-
|
|
3152
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3180
|
+
className: "oc-field",
|
|
3181
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3182
|
+
className: "oc-label",
|
|
3183
|
+
children: t("usageDisplay")
|
|
3184
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
3185
|
+
className: "oc-select",
|
|
3186
|
+
value: draft.usageDisplay,
|
|
3187
|
+
onChange: (event) => update({ usageDisplay: event.target.value }),
|
|
3188
|
+
children: [
|
|
3189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3190
|
+
value: "auto",
|
|
3191
|
+
children: t("usageDisplayAuto")
|
|
3192
|
+
}),
|
|
3193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3194
|
+
value: "always",
|
|
3195
|
+
children: t("usageDisplayAlways")
|
|
3196
|
+
}),
|
|
3197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3198
|
+
value: "off",
|
|
3199
|
+
children: t("usageDisplayOff")
|
|
3200
|
+
})
|
|
3201
|
+
]
|
|
3202
|
+
})]
|
|
3157
3203
|
}),
|
|
3158
3204
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3159
3205
|
className: "oc-hint",
|
|
3160
|
-
children: t("
|
|
3206
|
+
children: t("usageDisplayHint")
|
|
3161
3207
|
})
|
|
3162
3208
|
]
|
|
3163
3209
|
}),
|
|
@@ -3290,6 +3336,7 @@ window.__ModuleLoader__.load({
|
|
|
3290
3336
|
.oc-drag { cursor: grab; color: var(--oc-text-tertiary); user-select: none; font-size: 13px; line-height: 1; }
|
|
3291
3337
|
.oc-drag:active { cursor: grabbing; }
|
|
3292
3338
|
.oc-notice { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-accent-soft); border: 1px solid var(--oc-accent-border); }
|
|
3339
|
+
.oc-switch-notice { margin: 4px auto; padding: 4px 10px; border: 1px solid var(--oc-border); border-radius: 999px; background: var(--oc-fill-soft); color: var(--oc-text-secondary); font-size: 11px; line-height: 1.4; text-align: center; }
|
|
3293
3340
|
.oc-error { font-size: 12px; padding: 7px 10px; border-radius: 7px; background: var(--oc-danger-soft); border: 1px solid var(--oc-danger-border); color: var(--oc-danger); }
|
|
3294
3341
|
[class*="_footerActions"]:has(.oc-foot), [class*="_footerActions"]:has(.oc-railButton) { flex-direction: column; }
|
|
3295
3342
|
.oc-foot { box-sizing: border-box; flex: 1 1 auto; width: 100%; min-width: 0; display: flex; flex-direction: column; gap: 6px; margin: 0 0 4px; padding: 8px; border: 0.5px solid transparent; border-radius: var(--dsw-radius-md, 8px); background: transparent; color: var(--oc-text-secondary); font: inherit; text-align: left; cursor: pointer; }
|
|
@@ -3321,6 +3368,47 @@ window.__ModuleLoader__.load({
|
|
|
3321
3368
|
.oc-table th, .oc-table td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--oc-border-soft); vertical-align: top; }
|
|
3322
3369
|
.oc-table th { font-weight: 600; opacity: 0.8; }
|
|
3323
3370
|
`;
|
|
3371
|
+
// src/client/switch-notice.tsx
|
|
3372
|
+
/** Ambient notice under the composer when this conversation switched accounts. */
|
|
3373
|
+
function SwitchNotice({ sessionId, t }) {
|
|
3374
|
+
const [notice, setNotice] = (0, react.useState)(null);
|
|
3375
|
+
const seenAt = (0, react.useRef)(Date.now() - 5e3);
|
|
3376
|
+
(0, react.useEffect)(() => {
|
|
3377
|
+
if (sessionId === void 0 || String(sessionId) === "") return;
|
|
3378
|
+
let alive = true;
|
|
3379
|
+
const poll = () => {
|
|
3380
|
+
fetchNotices().then(({ events }) => {
|
|
3381
|
+
if (!alive) return;
|
|
3382
|
+
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];
|
|
3383
|
+
if (event === void 0) return;
|
|
3384
|
+
seenAt.current = event.at;
|
|
3385
|
+
setNotice({
|
|
3386
|
+
account: event.account,
|
|
3387
|
+
reason: event.reason,
|
|
3388
|
+
at: event.at
|
|
3389
|
+
});
|
|
3390
|
+
}).catch(() => {});
|
|
3391
|
+
};
|
|
3392
|
+
poll();
|
|
3393
|
+
const timer = setInterval(poll, 5e3);
|
|
3394
|
+
return () => {
|
|
3395
|
+
alive = false;
|
|
3396
|
+
clearInterval(timer);
|
|
3397
|
+
};
|
|
3398
|
+
}, [sessionId]);
|
|
3399
|
+
(0, react.useEffect)(() => {
|
|
3400
|
+
if (notice === null) return;
|
|
3401
|
+
const timer = setTimeout(() => setNotice(null), 6e4);
|
|
3402
|
+
return () => clearTimeout(timer);
|
|
3403
|
+
}, [notice]);
|
|
3404
|
+
if (notice === null) return null;
|
|
3405
|
+
const reason = notice.reason === "quota-fallback" ? t("routingFallback") : t("routingRotation");
|
|
3406
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3407
|
+
className: "oc-switch-notice",
|
|
3408
|
+
role: "status",
|
|
3409
|
+
children: t("accountSwitchNotice").replace("{account}", notice.account).replace("{reason}", reason)
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3324
3412
|
// src/client/index.tsx
|
|
3325
3413
|
function injectCss() {
|
|
3326
3414
|
if (typeof document === "undefined") return () => {};
|
|
@@ -3356,6 +3444,12 @@ window.__ModuleLoader__.load({
|
|
|
3356
3444
|
locale: NS,
|
|
3357
3445
|
inject: () => ({ close: () => ctx.get("layout")?.selectPanel(null) })
|
|
3358
3446
|
}, QuotaPanel));
|
|
3447
|
+
ctx.slots.inject("conversation.composer.dock", () => slots.register({
|
|
3448
|
+
name: "conversation.composer.dock",
|
|
3449
|
+
id: "opencode-switch-notice",
|
|
3450
|
+
order: 80,
|
|
3451
|
+
locale: NS
|
|
3452
|
+
}, SwitchNotice));
|
|
3359
3453
|
ctx.inject(["layout"], (layoutCtx) => {
|
|
3360
3454
|
const layoutSlots = layoutCtx.slots;
|
|
3361
3455
|
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;
|
package/lib/index.js
CHANGED
|
@@ -81,10 +81,10 @@ 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.27";
|
|
85
85
|
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
86
86
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
87
|
-
const DEFAULT_GENERATE_MAX_TOKENS =
|
|
87
|
+
const DEFAULT_GENERATE_MAX_TOKENS = 65536;
|
|
88
88
|
function markVolatile(schema) {
|
|
89
89
|
const mark = schema.volatile;
|
|
90
90
|
return typeof mark === "function" ? mark.call(schema) : schema;
|
|
@@ -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
|
};
|
|
@@ -1102,6 +1108,24 @@ function modelInGoPlan(id, config) {
|
|
|
1102
1108
|
if (GENERATED_MODELS[id] === void 0) return true;
|
|
1103
1109
|
return effectiveModel(id, config).free || limitsFor(id) !== void 0;
|
|
1104
1110
|
}
|
|
1111
|
+
function releaseDateOf(entry) {
|
|
1112
|
+
const raw = entry.release_date;
|
|
1113
|
+
if (typeof raw === "string" && raw !== "") {
|
|
1114
|
+
const parsed = Date.parse(raw.length === 10 ? `${raw}T00:00:00Z` : raw);
|
|
1115
|
+
if (Number.isFinite(parsed)) return new Date(parsed).toISOString().slice(0, 10);
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof entry.created === "number" && Number.isFinite(entry.created)) return (/* @__PURE__ */ new Date(entry.created * 1e3)).toISOString().slice(0, 10);
|
|
1118
|
+
}
|
|
1119
|
+
/** Seven UTC calendar days, used by the picker's NEW badge. */
|
|
1120
|
+
function isNewRelease(date, now = Date.now()) {
|
|
1121
|
+
if (date === void 0) return false;
|
|
1122
|
+
const parsed = Date.parse(date.length === 10 ? `${date}T00:00:00Z` : date);
|
|
1123
|
+
return Number.isFinite(parsed) && parsed <= now && now - parsed < 6048e5;
|
|
1124
|
+
}
|
|
1125
|
+
/** Release date learned from the live catalog, if the gateway published it. */
|
|
1126
|
+
function catalogReleaseDate(provider, id, config) {
|
|
1127
|
+
return catalogCache.get(`${provider}|${modelsUrlFor(provider, config)}`)?.releaseDates[id];
|
|
1128
|
+
}
|
|
1105
1129
|
async function fetchLiveIds(provider, config, apiKey, fetchImpl) {
|
|
1106
1130
|
const url = modelsUrlFor(provider, config);
|
|
1107
1131
|
const cacheKey = `${provider}|${url}`;
|
|
@@ -1121,9 +1145,16 @@ async function fetchLiveIds(provider, config, apiKey, fetchImpl) {
|
|
|
1121
1145
|
const payload = await response.json();
|
|
1122
1146
|
const ids = Array.isArray(payload.data) ? payload.data.map((entry) => entry.id).filter((id) => typeof id === "string" && id !== "") : Object.keys(payload.models ?? {});
|
|
1123
1147
|
if (ids.length === 0) return void 0;
|
|
1148
|
+
const releaseDates = {};
|
|
1149
|
+
if (Array.isArray(payload.data)) for (const entry of payload.data) {
|
|
1150
|
+
if (typeof entry.id !== "string" || entry.id === "") continue;
|
|
1151
|
+
const date = releaseDateOf(entry);
|
|
1152
|
+
if (date !== void 0) releaseDates[entry.id] = date;
|
|
1153
|
+
}
|
|
1124
1154
|
catalogCache.set(cacheKey, {
|
|
1125
1155
|
at: Date.now(),
|
|
1126
|
-
ids
|
|
1156
|
+
ids,
|
|
1157
|
+
releaseDates
|
|
1127
1158
|
});
|
|
1128
1159
|
return ids;
|
|
1129
1160
|
} catch {
|
|
@@ -3305,6 +3336,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3305
3336
|
const runStartedAt = Date.now();
|
|
3306
3337
|
const model = effectiveModel(options.model, config);
|
|
3307
3338
|
const sessionId = openCodeSessionId(options);
|
|
3339
|
+
const traceSession = options.sessionId === void 0 ? "" : String(options.sessionId);
|
|
3308
3340
|
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
3341
|
const rule = matchModelAccount(options.model, config.modelAccountRules);
|
|
3310
3342
|
let keys = rawKeys;
|
|
@@ -3404,6 +3436,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3404
3436
|
provider: options.provider,
|
|
3405
3437
|
model: options.model,
|
|
3406
3438
|
account: entry.label ?? "default",
|
|
3439
|
+
...traceSession === "" ? {} : { session: traceSession },
|
|
3407
3440
|
reason: attemptReason,
|
|
3408
3441
|
fallback,
|
|
3409
3442
|
ok: true
|
|
@@ -3418,6 +3451,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3418
3451
|
provider: options.provider,
|
|
3419
3452
|
model: options.model,
|
|
3420
3453
|
account: entry.label ?? "default",
|
|
3454
|
+
...traceSession === "" ? {} : { session: traceSession },
|
|
3421
3455
|
reason: attemptReason,
|
|
3422
3456
|
fallback,
|
|
3423
3457
|
ok: false,
|
|
@@ -3446,6 +3480,7 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3446
3480
|
provider: options.provider,
|
|
3447
3481
|
model: options.model,
|
|
3448
3482
|
account: lastAccount,
|
|
3483
|
+
...traceSession === "" ? {} : { session: traceSession },
|
|
3449
3484
|
reason,
|
|
3450
3485
|
fallback,
|
|
3451
3486
|
ok: false,
|
|
@@ -4734,6 +4769,7 @@ function publicConfig(config) {
|
|
|
4734
4769
|
autoSwitch: config.autoSwitch,
|
|
4735
4770
|
stickySession: config.stickySession,
|
|
4736
4771
|
activeAccount: config.activeAccount,
|
|
4772
|
+
usageDisplay: config.usageDisplay,
|
|
4737
4773
|
showSidebarQuota: config.showSidebarQuota,
|
|
4738
4774
|
userAgent: config.userAgent
|
|
4739
4775
|
};
|
|
@@ -4923,6 +4959,10 @@ function configOps(patch) {
|
|
|
4923
4959
|
if ("autoSwitch" in patch) set("autoSwitch", patch.autoSwitch === true);
|
|
4924
4960
|
if ("stickySession" in patch) set("stickySession", patch.stickySession === true);
|
|
4925
4961
|
if ("activeAccount" in patch) set("activeAccount", patch.activeAccount);
|
|
4962
|
+
if ("usageDisplay" in patch) {
|
|
4963
|
+
const value = patch.usageDisplay;
|
|
4964
|
+
if (value === "auto" || value === "always" || value === "off") set("usageDisplay", value);
|
|
4965
|
+
}
|
|
4926
4966
|
if ("showSidebarQuota" in patch) set("showSidebarQuota", patch.showSidebarQuota === true);
|
|
4927
4967
|
if ("userAgent" in patch) set("userAgent", patch.userAgent);
|
|
4928
4968
|
return ops;
|
|
@@ -5015,7 +5055,9 @@ async function buildState(ctx, deps) {
|
|
|
5015
5055
|
limits: limitsFor(model.id) ?? null,
|
|
5016
5056
|
input: meta.input,
|
|
5017
5057
|
reasoning: meta.reasoning,
|
|
5018
|
-
visible: modelVisible(model.id, config)
|
|
5058
|
+
visible: modelVisible(model.id, config),
|
|
5059
|
+
...catalogReleaseDate("opencode-go-direct", model.id, config) === void 0 ? {} : { releaseDate: catalogReleaseDate(GO_PROVIDER, model.id, config) },
|
|
5060
|
+
isNew: isNewRelease(catalogReleaseDate(GO_PROVIDER, model.id, config))
|
|
5019
5061
|
};
|
|
5020
5062
|
});
|
|
5021
5063
|
} catch {
|
|
@@ -5074,6 +5116,21 @@ function applyRoutes(ctx, web, deps) {
|
|
|
5074
5116
|
json(res, 500, { error: error instanceof Error ? error.message : "state failed" });
|
|
5075
5117
|
}
|
|
5076
5118
|
});
|
|
5119
|
+
route("/dsh-opencode/notices", async (req, res) => {
|
|
5120
|
+
if (req.method !== "GET") {
|
|
5121
|
+
json(res, 405, { error: "GET required" });
|
|
5122
|
+
return;
|
|
5123
|
+
}
|
|
5124
|
+
if (!trustedHost(req)) {
|
|
5125
|
+
json(res, 403, { error: "non-local Host refused" });
|
|
5126
|
+
return;
|
|
5127
|
+
}
|
|
5128
|
+
if (!allowedOrigin(req)) {
|
|
5129
|
+
json(res, 403, { error: "cross-origin request refused" });
|
|
5130
|
+
return;
|
|
5131
|
+
}
|
|
5132
|
+
json(res, 200, { events: deps.adapter.routingTrace() });
|
|
5133
|
+
});
|
|
5077
5134
|
route("/dsh-opencode/health", async (req, res) => {
|
|
5078
5135
|
if (req.method !== "GET") {
|
|
5079
5136
|
json(res, 405, { error: "GET required" });
|
|
@@ -5477,6 +5534,32 @@ function buildOpenCodeTuiSection(deps) {
|
|
|
5477
5534
|
value: text.trim() === "true"
|
|
5478
5535
|
})
|
|
5479
5536
|
},
|
|
5537
|
+
{
|
|
5538
|
+
path: ["usageDisplay"],
|
|
5539
|
+
group: "sidebar",
|
|
5540
|
+
kind: "select",
|
|
5541
|
+
label: "Quota display",
|
|
5542
|
+
hint: "Auto, Always, or Off for the sidebar quota card.",
|
|
5543
|
+
options: [
|
|
5544
|
+
{
|
|
5545
|
+
value: "auto",
|
|
5546
|
+
label: "Auto"
|
|
5547
|
+
},
|
|
5548
|
+
{
|
|
5549
|
+
value: "always",
|
|
5550
|
+
label: "Always"
|
|
5551
|
+
},
|
|
5552
|
+
{
|
|
5553
|
+
value: "off",
|
|
5554
|
+
label: "Off"
|
|
5555
|
+
}
|
|
5556
|
+
],
|
|
5557
|
+
format: (value) => typeof value === "string" ? value : deps.usageDisplay(),
|
|
5558
|
+
parse: (text) => text === "auto" || text === "always" || text === "off" ? {
|
|
5559
|
+
kind: "set",
|
|
5560
|
+
value: text
|
|
5561
|
+
} : void 0
|
|
5562
|
+
},
|
|
5480
5563
|
{
|
|
5481
5564
|
path: ["showSidebarQuota"],
|
|
5482
5565
|
group: "sidebar",
|
|
@@ -5793,6 +5876,7 @@ function apply(ctx, config) {
|
|
|
5793
5876
|
quotaFallbackModel: () => current().quotaFallbackModel,
|
|
5794
5877
|
autoSwitch: () => current().autoSwitch,
|
|
5795
5878
|
stickySession: () => current().stickySession,
|
|
5879
|
+
usageDisplay: () => current().usageDisplay,
|
|
5796
5880
|
showSidebarQuota: () => current().showSidebarQuota
|
|
5797
5881
|
});
|
|
5798
5882
|
});
|