@goodandready/dsh-opencode-provider 0.5.29 → 0.5.31
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 +5 -3
- package/README.ru.md +5 -3
- package/README.zh.md +5 -3
- package/lib/client.js +31 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +282 -39
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.31 — release 8: command localization
|
|
4
|
+
|
|
5
|
+
- **#48 — `/opencode` localization**: new `commandLocale` setting (`en` default, `zh`) localizes status, usage, accounts, models, rules and errors. Exposed in Settings → OpenCode.
|
|
6
|
+
- **#72 — thinking budgets**: DSH exposes selectable `reasoningEffort` ids only; there is no numeric per-model token-budget seam, and the Anthropic transport uses `output_config.effort`. No synthetic mapping is added; limitation recorded in DESIGN and the issue is closed with evidence.
|
|
7
|
+
- 140 tests.
|
|
8
|
+
|
|
9
|
+
## 0.5.30 — release 7: pricing accuracy
|
|
10
|
+
|
|
11
|
+
- **#74 — peak/off-peak multipliers**: analytics now reads live gateway `cost.multipliers` and evaluates UTC weekday/hour/date conditions at the session timestamp.
|
|
12
|
+
- **#84 — tiered pricing**: live gateway context overrides are normalized into tiers and selected from billed input (`input + cacheRead`) before multipliers. Malformed pricing metadata fails open to the generated flat cost.
|
|
13
|
+
- 139 tests.
|
|
14
|
+
|
|
3
15
|
## 0.5.29 — release 6: catalog refresh and Free-lane decision
|
|
4
16
|
|
|
5
17
|
- **#77 — catalog refresh**: `catalogRefreshMinutes` (1–10,080, default 10) controls the live `/models` cache; explicit `/models/refresh` still bypasses it. Exposed in Settings.
|
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.31.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.31.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.31
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (plugin)** in the model picker.
|
|
@@ -278,6 +278,8 @@ Captured from the exact 0.5.0 release candidate in DSH Desktop in the dark theme
|
|
|
278
278
|
/opencode test [model] # minimal real request
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
+
Set **Settings → OpenCode → `/opencode` command language** to `中文` to localize this output. The default is English.
|
|
282
|
+
|
|
281
283
|
## Verified API facts
|
|
282
284
|
|
|
283
285
|
- `GET /inference/go/v1/models` is public and returns the live Go roster.
|
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.31.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.31.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.31
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode (plugin)** или **OpenCode Go (plugin)** в списке моделей.
|
|
@@ -208,6 +208,8 @@ modelAccountRules:
|
|
|
208
208
|
/opencode test [model] # проверочный запрос к модели
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
Язык вывода задаётся в **Настройки → OpenCode → `/opencode` command language** (`中文` включает китайский). По умолчанию английский.
|
|
212
|
+
|
|
211
213
|
## Безопасность и надежность
|
|
212
214
|
|
|
213
215
|
- Запросы на изменение настроек и ключей разрешены только с loopback (`127.0.0.1` / `::1`) с обязательной проверкой same-origin для защиты от DNS-rebinding атак.
|
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.31.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.31.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.31
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
|
|
@@ -183,6 +183,8 @@ modelAccountRules:
|
|
|
183
183
|
/opencode test [model] # 发送最小化测试请求
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
+
在 **设置 → OpenCode → `/opencode` 命令语言** 中选择 `中文` 即可切换输出语言,默认英语。
|
|
187
|
+
|
|
186
188
|
## 安全规范
|
|
187
189
|
|
|
188
190
|
- 写入与配置路由均要求 Loopback(`127.0.0.1` / `::1`)与同源检查,防御 DNS 重新绑定。
|
package/lib/client.js
CHANGED
|
@@ -187,6 +187,8 @@ window.__ModuleLoader__.load({
|
|
|
187
187
|
requestImagePixelBudget: "Image pixel budget",
|
|
188
188
|
requestImageMaxBytes: "Per-image max bytes",
|
|
189
189
|
catalogRefreshMinutes: "Catalog refresh (minutes)",
|
|
190
|
+
commandLocale: "/opencode command language",
|
|
191
|
+
commandLocaleHint: "Language for /opencode status, accounts, usage, models and rules.",
|
|
190
192
|
proxyUrl: "Proxy URL",
|
|
191
193
|
proxyUrlHint: "HTTP, HTTPS or SOCKS5 proxy for inference, catalog and usage. Empty disables it. Credentials are not supported.",
|
|
192
194
|
maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
|
|
@@ -421,6 +423,8 @@ window.__ModuleLoader__.load({
|
|
|
421
423
|
requestImagePixelBudget: "图片像素预算",
|
|
422
424
|
requestImageMaxBytes: "单图最大字节数",
|
|
423
425
|
catalogRefreshMinutes: "目录刷新间隔(分钟)",
|
|
426
|
+
commandLocale: "/opencode 命令语言",
|
|
427
|
+
commandLocaleHint: "用于 /opencode 的状态、账户、用量、模型和规则输出。",
|
|
424
428
|
proxyUrl: "代理地址",
|
|
425
429
|
proxyUrlHint: "用于推理、目录和用量的 HTTP、HTTPS 或 SOCKS5 代理。留空则禁用。不支持代理认证。",
|
|
426
430
|
maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
|
|
@@ -1715,6 +1719,7 @@ window.__ModuleLoader__.load({
|
|
|
1715
1719
|
requestImageMaxBytes: String(state.config.requestImageMaxBytes),
|
|
1716
1720
|
proxyUrl: state.config.proxyUrl,
|
|
1717
1721
|
catalogRefreshMinutes: String(state.config.catalogRefreshMinutes),
|
|
1722
|
+
commandLocale: state.config.commandLocale,
|
|
1718
1723
|
maxOutputTokens: state.config.maxOutputTokens === null ? "" : String(state.config.maxOutputTokens),
|
|
1719
1724
|
contextWindow: state.config.contextWindow === null ? "" : String(state.config.contextWindow),
|
|
1720
1725
|
contextWindowOverrides: Object.fromEntries(Object.entries(state.config.contextWindowOverrides).map(([id, value]) => [id, String(value)])),
|
|
@@ -1768,6 +1773,7 @@ window.__ModuleLoader__.load({
|
|
|
1768
1773
|
requestImageMaxBytes: numberOrNull(draft.requestImageMaxBytes) ?? 1048576,
|
|
1769
1774
|
proxyUrl: draft.proxyUrl.trim(),
|
|
1770
1775
|
catalogRefreshMinutes: numberOrNull(draft.catalogRefreshMinutes) ?? 10,
|
|
1776
|
+
commandLocale: draft.commandLocale,
|
|
1771
1777
|
maxOutputTokens: numberOrNull(draft.maxOutputTokens),
|
|
1772
1778
|
contextWindow: numberOrNull(draft.contextWindow),
|
|
1773
1779
|
contextWindowOverrides: numberRecordOrNull(draft.contextWindowOverrides),
|
|
@@ -3208,6 +3214,31 @@ window.__ModuleLoader__.load({
|
|
|
3208
3214
|
value: draft.catalogRefreshMinutes,
|
|
3209
3215
|
onChange: (value) => update({ catalogRefreshMinutes: value })
|
|
3210
3216
|
}),
|
|
3217
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3218
|
+
className: "oc-field",
|
|
3219
|
+
children: [
|
|
3220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3221
|
+
className: "oc-label",
|
|
3222
|
+
children: t("commandLocale")
|
|
3223
|
+
}),
|
|
3224
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
3225
|
+
className: "oc-select",
|
|
3226
|
+
value: draft.commandLocale,
|
|
3227
|
+
onChange: (event) => update({ commandLocale: event.target.value }),
|
|
3228
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3229
|
+
value: "en",
|
|
3230
|
+
children: "English"
|
|
3231
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
3232
|
+
value: "zh",
|
|
3233
|
+
children: "中文"
|
|
3234
|
+
})]
|
|
3235
|
+
}),
|
|
3236
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3237
|
+
className: "oc-hint",
|
|
3238
|
+
children: t("commandLocaleHint")
|
|
3239
|
+
})
|
|
3240
|
+
]
|
|
3241
|
+
}),
|
|
3211
3242
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3212
3243
|
label: t("proxyUrl"),
|
|
3213
3244
|
hint: t("proxyUrlHint"),
|
package/lib/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ 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
7
|
type UsageDisplay = 'auto' | 'always' | 'off';
|
|
8
|
+
type CommandLocale = 'en' | 'zh';
|
|
8
9
|
interface AccountConfig {
|
|
9
10
|
label?: string;
|
|
10
11
|
apiKeyEnv?: string;
|
|
@@ -51,6 +52,8 @@ interface Config {
|
|
|
51
52
|
proxyUrl?: string;
|
|
52
53
|
/** Live catalog cache lifetime in minutes; defaults to 10. */
|
|
53
54
|
catalogRefreshMinutes?: number;
|
|
55
|
+
/** Language for `/opencode` output; defaults to English. */
|
|
56
|
+
commandLocale?: CommandLocale;
|
|
54
57
|
/** Visible-model allowlist; an empty list means "show everything". */
|
|
55
58
|
visibleModels?: string[];
|
|
56
59
|
/** Per-model visibility overrides; wins over `visibleModels`. */
|
|
@@ -104,6 +107,7 @@ interface ResolvedConfig {
|
|
|
104
107
|
requestImageMaxBytes: number;
|
|
105
108
|
proxyUrl: string;
|
|
106
109
|
catalogRefreshMinutes: number;
|
|
110
|
+
commandLocale: CommandLocale;
|
|
107
111
|
visibleModels: readonly string[];
|
|
108
112
|
modelVisibility: Readonly<Record<string, boolean>>;
|
|
109
113
|
filterModelsByPlan: boolean;
|
|
@@ -316,6 +320,7 @@ interface CommandDeps {
|
|
|
316
320
|
readonly pool: KeyPool;
|
|
317
321
|
readonly usage: (provider: string, key?: string) => Promise<UsageResult | undefined>;
|
|
318
322
|
readonly adapter: OpenCodeAdapter;
|
|
323
|
+
readonly locale: () => 'en' | 'zh';
|
|
319
324
|
}
|
|
320
325
|
declare function applyCommands(ctx: Context, deps: CommandDeps): void;
|
|
321
326
|
interface ModelUsageRow {
|
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.31";
|
|
85
85
|
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
86
86
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
87
87
|
const DEFAULT_GENERATE_MAX_TOKENS = 65536;
|
|
@@ -110,6 +110,7 @@ const Config = z.object({
|
|
|
110
110
|
requestImageMaxBytes: markVolatile(z.number().min(1).max(1e8).step(1).default(DEFAULT_REQUEST_IMAGE_MAX_BYTES)),
|
|
111
111
|
proxyUrl: markVolatile(z.string().default("")),
|
|
112
112
|
catalogRefreshMinutes: markVolatile(z.number().min(1).max(10080).step(1).default(10)),
|
|
113
|
+
commandLocale: markVolatile(z.union(["en", "zh"]).default("en")),
|
|
113
114
|
visibleModels: markVolatile(z.array(z.string()).default([])),
|
|
114
115
|
modelVisibility: markVolatile(z.dict(z.boolean()).default({})),
|
|
115
116
|
filterModelsByPlan: markVolatile(z.boolean().default(true)),
|
|
@@ -229,6 +230,7 @@ function resolveConfig(config) {
|
|
|
229
230
|
requestImageMaxBytes: positiveInt(config.requestImageMaxBytes, DEFAULT_REQUEST_IMAGE_MAX_BYTES),
|
|
230
231
|
proxyUrl: normalizeProxyUrl(config.proxyUrl),
|
|
231
232
|
catalogRefreshMinutes: positiveInt(config.catalogRefreshMinutes, 10),
|
|
233
|
+
commandLocale: config.commandLocale === "zh" ? "zh" : "en",
|
|
232
234
|
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : [],
|
|
233
235
|
modelVisibility: booleanRecord(config.modelVisibility),
|
|
234
236
|
filterModelsByPlan: config.filterModelsByPlan !== false,
|
|
@@ -1067,6 +1069,182 @@ function formatRequests(value) {
|
|
|
1067
1069
|
function describeLimitsShort(limits) {
|
|
1068
1070
|
return `Go ${formatRequests(limits.go.fiveHour)}/5h · ${formatRequests(limits.go.weekly)}/wk`;
|
|
1069
1071
|
}
|
|
1072
|
+
// src/pricing.ts
|
|
1073
|
+
const DAYS = [
|
|
1074
|
+
"sun",
|
|
1075
|
+
"mon",
|
|
1076
|
+
"tue",
|
|
1077
|
+
"wed",
|
|
1078
|
+
"thu",
|
|
1079
|
+
"fri",
|
|
1080
|
+
"sat"
|
|
1081
|
+
];
|
|
1082
|
+
const HOURS = /^(?:[01]\d|2[0-3]):[0-5]\d$|^24:00$/;
|
|
1083
|
+
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
1084
|
+
function record(value) {
|
|
1085
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1086
|
+
}
|
|
1087
|
+
function rate(value) {
|
|
1088
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1089
|
+
}
|
|
1090
|
+
function comparison(value, validate) {
|
|
1091
|
+
const raw = record(value);
|
|
1092
|
+
if (raw === void 0) return void 0;
|
|
1093
|
+
const out = {};
|
|
1094
|
+
for (const [key, entry] of Object.entries(raw)) if (key === "in") {
|
|
1095
|
+
if (!Array.isArray(entry) || entry.length === 0 || !entry.every((item) => typeof item === "string" && validate(item))) return void 0;
|
|
1096
|
+
out.in = entry;
|
|
1097
|
+
} else {
|
|
1098
|
+
if (typeof entry !== "string" || !validate(entry)) return void 0;
|
|
1099
|
+
out[key] = entry;
|
|
1100
|
+
}
|
|
1101
|
+
return out;
|
|
1102
|
+
}
|
|
1103
|
+
function conditionOf(value, depth = 0) {
|
|
1104
|
+
if (depth > 12) return void 0;
|
|
1105
|
+
const raw = record(value);
|
|
1106
|
+
if (raw === void 0) return void 0;
|
|
1107
|
+
const out = {};
|
|
1108
|
+
for (const [key, entry] of Object.entries(raw)) if (key === "all" || key === "any") {
|
|
1109
|
+
if (!Array.isArray(entry) || entry.length === 0 || entry.length > 64) return void 0;
|
|
1110
|
+
const nested = entry.map((item) => conditionOf(item, depth + 1));
|
|
1111
|
+
if (nested.some((item) => item === void 0)) return void 0;
|
|
1112
|
+
out[key] = nested;
|
|
1113
|
+
} else if (key === "utc_days") {
|
|
1114
|
+
const value = comparison(entry, (text) => DAYS.includes(text));
|
|
1115
|
+
if (value === void 0) return void 0;
|
|
1116
|
+
out[key] = value;
|
|
1117
|
+
} else if (key === "utc_hours") {
|
|
1118
|
+
const value = comparison(entry, (text) => HOURS.test(text));
|
|
1119
|
+
if (value === void 0) return void 0;
|
|
1120
|
+
out[key] = value;
|
|
1121
|
+
} else if (key === "date") {
|
|
1122
|
+
const value = comparison(entry, (text) => DATE.test(text));
|
|
1123
|
+
if (value === void 0) return void 0;
|
|
1124
|
+
out[key] = value;
|
|
1125
|
+
} else if (key === "context") {
|
|
1126
|
+
const context = record(entry);
|
|
1127
|
+
if (context === void 0) return void 0;
|
|
1128
|
+
const bounds = {};
|
|
1129
|
+
for (const [op, size] of Object.entries(context)) {
|
|
1130
|
+
if (![
|
|
1131
|
+
"gt",
|
|
1132
|
+
"gte",
|
|
1133
|
+
"lt",
|
|
1134
|
+
"lte"
|
|
1135
|
+
].includes(op) || typeof size !== "number" || !Number.isFinite(size)) return void 0;
|
|
1136
|
+
bounds[op] = size;
|
|
1137
|
+
}
|
|
1138
|
+
out[key] = bounds;
|
|
1139
|
+
} else return;
|
|
1140
|
+
return Object.keys(out).length === 0 ? void 0 : out;
|
|
1141
|
+
}
|
|
1142
|
+
function compare(actual, rule) {
|
|
1143
|
+
return Object.entries(rule).every(([op, expected]) => {
|
|
1144
|
+
if (op === "in") return Array.isArray(expected) && expected.includes(actual);
|
|
1145
|
+
if (op === "eq") return actual === expected;
|
|
1146
|
+
if (op === "gt") return actual > String(expected);
|
|
1147
|
+
if (op === "gte") return actual >= String(expected);
|
|
1148
|
+
if (op === "lt") return actual < String(expected);
|
|
1149
|
+
if (op === "lte") return actual <= String(expected);
|
|
1150
|
+
return false;
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
function matches(condition, at, context) {
|
|
1154
|
+
if (condition.all !== void 0 && !condition.all.every((entry) => matches(entry, at, context))) return false;
|
|
1155
|
+
if (condition.any !== void 0 && !condition.any.some((entry) => matches(entry, at, context))) return false;
|
|
1156
|
+
const date = new Date(at);
|
|
1157
|
+
if (condition.utc_days !== void 0 && !compare(DAYS[date.getUTCDay()], condition.utc_days)) return false;
|
|
1158
|
+
if (condition.utc_hours !== void 0) {
|
|
1159
|
+
if (!compare(`${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(2, "0")}`, condition.utc_hours)) return false;
|
|
1160
|
+
}
|
|
1161
|
+
if (condition.date !== void 0 && !compare(date.toISOString().slice(0, 10), condition.date)) return false;
|
|
1162
|
+
if (condition.context !== void 0) {
|
|
1163
|
+
if (condition.context.gt !== void 0 && !(context > condition.context.gt)) return false;
|
|
1164
|
+
if (condition.context.gte !== void 0 && !(context >= condition.context.gte)) return false;
|
|
1165
|
+
if (condition.context.lt !== void 0 && !(context < condition.context.lt)) return false;
|
|
1166
|
+
if (condition.context.lte !== void 0 && !(context <= condition.context.lte)) return false;
|
|
1167
|
+
}
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
function billingContext(tokens) {
|
|
1171
|
+
return tokens.input + tokens.cacheRead;
|
|
1172
|
+
}
|
|
1173
|
+
/** Estimate USD from gateway cost metadata, including context tiers and UTC multipliers. */
|
|
1174
|
+
function priceUsage(pricing, tokens, at) {
|
|
1175
|
+
const context = billingContext(tokens);
|
|
1176
|
+
let rates = pricing.base;
|
|
1177
|
+
for (const tier of pricing.tiers) if (tier.at <= context) rates = tier.rates;
|
|
1178
|
+
else break;
|
|
1179
|
+
const factor = pricing.multipliers.reduce((value, entry) => matches(entry.when, at, context) ? value * entry.factor : value, 1);
|
|
1180
|
+
return (tokens.input * rates.input + tokens.output * rates.output + tokens.cacheRead * rates.cacheRead) / 1e6 * factor;
|
|
1181
|
+
}
|
|
1182
|
+
function overridesOf(raw, field) {
|
|
1183
|
+
if (!Array.isArray(raw)) return [];
|
|
1184
|
+
const out = [];
|
|
1185
|
+
for (const entry of raw) {
|
|
1186
|
+
const value = record(entry);
|
|
1187
|
+
const context = record(record(value?.when)?.context);
|
|
1188
|
+
const at = rate(context?.gt) ?? rate(context?.gte);
|
|
1189
|
+
const price = rate(value?.price);
|
|
1190
|
+
const cache = record(value?.cache);
|
|
1191
|
+
const cacheRead = field === "input" ? rate(cache?.read) : void 0;
|
|
1192
|
+
if (at === void 0 || price === void 0 && cacheRead === void 0) continue;
|
|
1193
|
+
out.push({
|
|
1194
|
+
at,
|
|
1195
|
+
...price === void 0 ? {} : { [field]: price },
|
|
1196
|
+
...cacheRead === void 0 ? {} : { cacheRead }
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
return out.sort((left, right) => left.at - right.at);
|
|
1200
|
+
}
|
|
1201
|
+
/** Normalize the gateway's `cost` object; malformed metadata is ignored. */
|
|
1202
|
+
function parseGatewayPricing(raw) {
|
|
1203
|
+
const cost = record(raw);
|
|
1204
|
+
const text = record(cost?.text);
|
|
1205
|
+
const input = record(text?.input);
|
|
1206
|
+
const output = record(text?.output);
|
|
1207
|
+
const cache = record(input?.cache);
|
|
1208
|
+
const baseInput = rate(input?.price);
|
|
1209
|
+
const baseOutput = rate(output?.price);
|
|
1210
|
+
if (baseInput === void 0 || baseOutput === void 0) return void 0;
|
|
1211
|
+
const base = {
|
|
1212
|
+
input: baseInput,
|
|
1213
|
+
output: baseOutput,
|
|
1214
|
+
cacheRead: rate(cache?.read) ?? 0
|
|
1215
|
+
};
|
|
1216
|
+
const inputOverrides = overridesOf(input?.overrides, "input");
|
|
1217
|
+
const outputOverrides = overridesOf(output?.overrides, "output");
|
|
1218
|
+
const thresholds = [.../* @__PURE__ */ new Set([...inputOverrides.map((entry) => entry.at), ...outputOverrides.map((entry) => entry.at)])].sort((left, right) => left - right);
|
|
1219
|
+
const tiers = [];
|
|
1220
|
+
for (const at of thresholds) {
|
|
1221
|
+
const inputOverride = inputOverrides.filter((entry) => entry.at <= at).at(-1);
|
|
1222
|
+
const outputOverride = outputOverrides.filter((entry) => entry.at <= at).at(-1);
|
|
1223
|
+
tiers.push({
|
|
1224
|
+
at,
|
|
1225
|
+
rates: {
|
|
1226
|
+
input: inputOverride?.input ?? base.input,
|
|
1227
|
+
output: outputOverride?.output ?? base.output,
|
|
1228
|
+
cacheRead: inputOverride?.cacheRead ?? base.cacheRead
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
const multipliers = [];
|
|
1233
|
+
if (Array.isArray(cost?.multipliers)) for (const entry of cost.multipliers) {
|
|
1234
|
+
const value = record(entry);
|
|
1235
|
+
const factor = rate(value?.factor);
|
|
1236
|
+
const when = conditionOf(value?.when);
|
|
1237
|
+
if (factor !== void 0 && factor > 0 && when !== void 0) multipliers.push({
|
|
1238
|
+
when,
|
|
1239
|
+
factor
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
return {
|
|
1243
|
+
base,
|
|
1244
|
+
tiers,
|
|
1245
|
+
multipliers
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1070
1248
|
// src/catalog.ts
|
|
1071
1249
|
const CATALOG_TIMEOUT_MS = 15e3;
|
|
1072
1250
|
const catalogCache = /* @__PURE__ */ new Map();
|
|
@@ -1141,6 +1319,14 @@ function isNewRelease(date, now = Date.now()) {
|
|
|
1141
1319
|
function catalogReleaseDate(provider, id, config) {
|
|
1142
1320
|
return catalogCache.get(`${provider}|${modelsUrlFor(provider, config)}`)?.releaseDates[id];
|
|
1143
1321
|
}
|
|
1322
|
+
/** Gateway pricing metadata for a live model, if the catalog published it. */
|
|
1323
|
+
function livePricingFor(provider, id) {
|
|
1324
|
+
for (const [key, entry] of catalogCache) {
|
|
1325
|
+
if (!key.startsWith(`${provider}|`)) continue;
|
|
1326
|
+
const pricing = entry.pricing[id];
|
|
1327
|
+
if (pricing !== void 0) return pricing;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1144
1330
|
async function fetchLiveIds(provider, config, apiKey, fetchImpl) {
|
|
1145
1331
|
const url = modelsUrlFor(provider, config);
|
|
1146
1332
|
const cacheKey = `${provider}|${url}`;
|
|
@@ -1161,15 +1347,19 @@ async function fetchLiveIds(provider, config, apiKey, fetchImpl) {
|
|
|
1161
1347
|
const ids = Array.isArray(payload.data) ? payload.data.map((entry) => entry.id).filter((id) => typeof id === "string" && id !== "") : Object.keys(payload.models ?? {});
|
|
1162
1348
|
if (ids.length === 0) return void 0;
|
|
1163
1349
|
const releaseDates = {};
|
|
1350
|
+
const pricing = {};
|
|
1164
1351
|
if (Array.isArray(payload.data)) for (const entry of payload.data) {
|
|
1165
1352
|
if (typeof entry.id !== "string" || entry.id === "") continue;
|
|
1166
1353
|
const date = releaseDateOf(entry);
|
|
1167
1354
|
if (date !== void 0) releaseDates[entry.id] = date;
|
|
1355
|
+
const parsed = parseGatewayPricing(entry.cost);
|
|
1356
|
+
if (parsed !== void 0) pricing[entry.id] = parsed;
|
|
1168
1357
|
}
|
|
1169
1358
|
catalogCache.set(cacheKey, {
|
|
1170
1359
|
at: Date.now(),
|
|
1171
1360
|
ids,
|
|
1172
|
-
releaseDates
|
|
1361
|
+
releaseDates,
|
|
1362
|
+
pricing
|
|
1173
1363
|
});
|
|
1174
1364
|
return ids;
|
|
1175
1365
|
} catch {
|
|
@@ -3790,22 +3980,66 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3790
3980
|
return error.code === "RATE_LIMIT" || error.code === "QUOTA" || error.code === "SERVER" || error.code === "TIMEOUT" || error.code === "TRANSPORT" || error.code === "EMPTY_RESPONSE";
|
|
3791
3981
|
}
|
|
3792
3982
|
};
|
|
3983
|
+
// src/command-copy.ts
|
|
3984
|
+
const en = {
|
|
3985
|
+
provider: "OpenCode provider",
|
|
3986
|
+
noCredential: "no credential",
|
|
3987
|
+
active: "active",
|
|
3988
|
+
cooling: "cooling",
|
|
3989
|
+
rules: "Rules",
|
|
3990
|
+
auth: "Auth",
|
|
3991
|
+
none: "none",
|
|
3992
|
+
noRules: "No model → account rules.",
|
|
3993
|
+
noModels: "No models.",
|
|
3994
|
+
modelsUnavailable: "Models unavailable",
|
|
3995
|
+
unknown: "Unknown /opencode argument",
|
|
3996
|
+
quotaUnavailable: "Quota unavailable",
|
|
3997
|
+
noWindows: "No quota windows returned.",
|
|
3998
|
+
testFailed: "failed",
|
|
3999
|
+
resets: "resets",
|
|
4000
|
+
fiveHour: "5h",
|
|
4001
|
+
weekly: "weekly",
|
|
4002
|
+
monthly: "monthly"
|
|
4003
|
+
};
|
|
4004
|
+
const zh = {
|
|
4005
|
+
provider: "OpenCode 提供商",
|
|
4006
|
+
noCredential: "未配置凭据",
|
|
4007
|
+
active: "活动",
|
|
4008
|
+
cooling: "冷却中",
|
|
4009
|
+
rules: "规则",
|
|
4010
|
+
auth: "认证",
|
|
4011
|
+
none: "无",
|
|
4012
|
+
noRules: "没有模型 → 账户规则。",
|
|
4013
|
+
noModels: "没有模型。",
|
|
4014
|
+
modelsUnavailable: "模型不可用",
|
|
4015
|
+
unknown: "未知的 /opencode 参数",
|
|
4016
|
+
quotaUnavailable: "配额不可用",
|
|
4017
|
+
noWindows: "未返回配额窗口。",
|
|
4018
|
+
testFailed: "失败",
|
|
4019
|
+
resets: "重置",
|
|
4020
|
+
fiveHour: "5小时",
|
|
4021
|
+
weekly: "每周",
|
|
4022
|
+
monthly: "每月"
|
|
4023
|
+
};
|
|
4024
|
+
function commandCopy(locale) {
|
|
4025
|
+
return locale === "zh" ? zh : en;
|
|
4026
|
+
}
|
|
3793
4027
|
// src/commands.ts
|
|
3794
4028
|
function bar(percent, width = 10) {
|
|
3795
4029
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
3796
4030
|
const filled = Math.round(clamped / 100 * width);
|
|
3797
4031
|
return `[${"█".repeat(filled)}${"░".repeat(width - filled)}] ${clamped.toFixed(0)}%`;
|
|
3798
4032
|
}
|
|
3799
|
-
function reset(iso) {
|
|
4033
|
+
function reset(iso, copy) {
|
|
3800
4034
|
if (iso === void 0) return "";
|
|
3801
4035
|
const date = new Date(iso);
|
|
3802
4036
|
if (Number.isNaN(date.getTime())) return "";
|
|
3803
|
-
return ` · resets ${date.toISOString().replace("T", " ").slice(0, 16)}Z`;
|
|
4037
|
+
return ` · ${copy.resets} ${date.toISOString().replace("T", " ").slice(0, 16)}Z`;
|
|
3804
4038
|
}
|
|
3805
|
-
function windowLine(label, window) {
|
|
4039
|
+
function windowLine(label, window, copy) {
|
|
3806
4040
|
if (window === void 0) return void 0;
|
|
3807
4041
|
const status = window.status === void 0 || window.status === "ok" ? "" : ` (${window.status})`;
|
|
3808
|
-
return `${label}: ${bar(window.percentUsed)}${status}${reset(window.resetsAt)}`;
|
|
4042
|
+
return `${label}: ${bar(window.percentUsed)}${status}${reset(window.resetsAt, copy)}`;
|
|
3809
4043
|
}
|
|
3810
4044
|
function percent(window) {
|
|
3811
4045
|
if (window === void 0) return "—";
|
|
@@ -3827,18 +4061,18 @@ function relative(until) {
|
|
|
3827
4061
|
if (delta >= 36e5) return `${Math.round(delta / 36e5)}h`;
|
|
3828
4062
|
return `${Math.max(1, Math.round(delta / 6e4))}m`;
|
|
3829
4063
|
}
|
|
3830
|
-
async function accountsText(deps) {
|
|
4064
|
+
async function accountsText(deps, copy) {
|
|
3831
4065
|
const lines = [];
|
|
3832
4066
|
for (const provider of [GO_PROVIDER, CONSOLE_PROVIDER]) {
|
|
3833
4067
|
let keys;
|
|
3834
4068
|
try {
|
|
3835
4069
|
keys = await deps.pool.resolveAll(provider);
|
|
3836
4070
|
} catch {
|
|
3837
|
-
lines.push(`${provider}:
|
|
4071
|
+
lines.push(`${provider}: ${copy.noCredential}`);
|
|
3838
4072
|
continue;
|
|
3839
4073
|
}
|
|
3840
4074
|
if (keys.length === 0) {
|
|
3841
|
-
lines.push(`${provider}:
|
|
4075
|
+
lines.push(`${provider}: ${copy.noCredential}`);
|
|
3842
4076
|
continue;
|
|
3843
4077
|
}
|
|
3844
4078
|
const active = keys.find((entry) => deps.adapter.accountHealth(entry.key) === void 0);
|
|
@@ -3848,8 +4082,8 @@ async function accountsText(deps) {
|
|
|
3848
4082
|
const usage = provider === "opencode-go-direct" ? await deps.usage(provider, entry.key) : void 0;
|
|
3849
4083
|
const tags = [
|
|
3850
4084
|
entry.source,
|
|
3851
|
-
entry === active ?
|
|
3852
|
-
health === void 0 ? "" :
|
|
4085
|
+
entry === active ? copy.active : "",
|
|
4086
|
+
health === void 0 ? "" : `${copy.cooling} ${health.reason} ${relative(health.coolingUntil)}`,
|
|
3853
4087
|
usage?.report === void 0 ? usage?.error ?? "" : summary(usage.report)
|
|
3854
4088
|
].filter((tag) => tag !== "");
|
|
3855
4089
|
lines.push(` ${index + 1}. ${entry.label ?? "default"}: ${tags.join(" · ")}`);
|
|
@@ -3857,27 +4091,27 @@ async function accountsText(deps) {
|
|
|
3857
4091
|
}
|
|
3858
4092
|
return lines.join("\n");
|
|
3859
4093
|
}
|
|
3860
|
-
function rulesText(deps) {
|
|
4094
|
+
function rulesText(deps, copy) {
|
|
3861
4095
|
const rules = deps.config().modelAccountRules;
|
|
3862
|
-
if (rules.length === 0) return
|
|
4096
|
+
if (rules.length === 0) return copy.noRules;
|
|
3863
4097
|
return rules.map((rule) => `${rule.pattern} → ${rule.account}`).join("\n");
|
|
3864
4098
|
}
|
|
3865
|
-
async function modelsText(deps) {
|
|
4099
|
+
async function modelsText(deps, copy) {
|
|
3866
4100
|
const config = deps.config();
|
|
3867
4101
|
try {
|
|
3868
4102
|
const models = await deps.adapter.listModels(GO_PROVIDER, { unfiltered: true });
|
|
3869
|
-
if (models.length === 0) return
|
|
4103
|
+
if (models.length === 0) return copy.noModels;
|
|
3870
4104
|
return models.map((model) => {
|
|
3871
4105
|
const meta = effectiveModel(model.id, config);
|
|
3872
4106
|
const limits = limitsFor(model.id);
|
|
3873
|
-
const tags = [meta.free ? "FREE" : limits === void 0 ? "Console" : "Go", limits === void 0 ? "" :
|
|
4107
|
+
const tags = [meta.free ? "FREE" : limits === void 0 ? "Console" : "Go", limits === void 0 ? "" : `${copy.fiveHour} ${formatRequests(limits.go.fiveHour)} / ${copy.weekly} ${formatRequests(limits.go.weekly)} / ${copy.monthly} ${formatRequests(limits.go.monthly)}`].filter((tag) => tag !== "");
|
|
3874
4108
|
return `- ${model.id}${tags.length === 0 ? "" : ` · ${tags.join(" · ")}`}`;
|
|
3875
4109
|
}).join("\n");
|
|
3876
4110
|
} catch (error) {
|
|
3877
|
-
return
|
|
4111
|
+
return `${copy.modelsUnavailable}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3878
4112
|
}
|
|
3879
4113
|
}
|
|
3880
|
-
async function testText(deps, model) {
|
|
4114
|
+
async function testText(deps, model, copy) {
|
|
3881
4115
|
const started = Date.now();
|
|
3882
4116
|
let text = "";
|
|
3883
4117
|
try {
|
|
@@ -3900,18 +4134,18 @@ async function testText(deps, model) {
|
|
|
3900
4134
|
} catch (error) {
|
|
3901
4135
|
return {
|
|
3902
4136
|
ok: false,
|
|
3903
|
-
text: `${model}:
|
|
4137
|
+
text: `${model}: ${copy.testFailed} — ${error instanceof Error ? error.message : String(error)}`
|
|
3904
4138
|
};
|
|
3905
4139
|
}
|
|
3906
4140
|
}
|
|
3907
|
-
async function statusText(deps) {
|
|
4141
|
+
async function statusText(deps, copy) {
|
|
3908
4142
|
const config = deps.config();
|
|
3909
|
-
const rules = config.modelAccountRules.length === 0 ?
|
|
4143
|
+
const rules = config.modelAccountRules.length === 0 ? copy.none : config.modelAccountRules.map((rule) => `${rule.pattern} → ${rule.account}`).join(", ");
|
|
3910
4144
|
return [
|
|
3911
|
-
|
|
3912
|
-
await accountsText(deps),
|
|
3913
|
-
|
|
3914
|
-
|
|
4145
|
+
copy.provider,
|
|
4146
|
+
await accountsText(deps, copy),
|
|
4147
|
+
`${copy.rules}: ${rules}`,
|
|
4148
|
+
`${copy.auth}: ${config.openCodeAuthEntry} · ${config.apiKeyEnv}`
|
|
3915
4149
|
].filter((line) => line !== "").join("\n");
|
|
3916
4150
|
}
|
|
3917
4151
|
function applyCommands(ctx, deps) {
|
|
@@ -3920,6 +4154,7 @@ function applyCommands(ctx, deps) {
|
|
|
3920
4154
|
description: "OpenCode / OpenCode Go status, accounts, quotas, models and rules",
|
|
3921
4155
|
input: { hint: "status | usage | accounts | models | rules | test [model]" },
|
|
3922
4156
|
handler: async (invocation) => {
|
|
4157
|
+
const copy = commandCopy(deps.locale());
|
|
3923
4158
|
const raw = invocation.rawInput.trim();
|
|
3924
4159
|
const parts = raw === "" ? ["status"] : raw.split(/\s+/);
|
|
3925
4160
|
const verb = parts[0] ?? "status";
|
|
@@ -3927,39 +4162,39 @@ function applyCommands(ctx, deps) {
|
|
|
3927
4162
|
switch (verb.toLowerCase()) {
|
|
3928
4163
|
case "status": return {
|
|
3929
4164
|
kind: "success",
|
|
3930
|
-
text: await statusText(deps)
|
|
4165
|
+
text: await statusText(deps, copy)
|
|
3931
4166
|
};
|
|
3932
4167
|
case "usage": {
|
|
3933
4168
|
const usage = await deps.usage(GO_PROVIDER);
|
|
3934
4169
|
if (usage?.report === void 0) return {
|
|
3935
4170
|
kind: "error",
|
|
3936
|
-
text:
|
|
4171
|
+
text: `${copy.quotaUnavailable}: ${usage?.error ?? "no report"}`
|
|
3937
4172
|
};
|
|
3938
4173
|
const windows = usage.report.windows;
|
|
3939
4174
|
const text = [
|
|
3940
|
-
windowLine(
|
|
3941
|
-
windowLine(
|
|
3942
|
-
windowLine(
|
|
4175
|
+
windowLine(copy.fiveHour, windows.fiveHour, copy),
|
|
4176
|
+
windowLine(copy.weekly, windows.weekly, copy),
|
|
4177
|
+
windowLine(copy.monthly, windows.monthly, copy)
|
|
3943
4178
|
].filter((line) => line !== void 0).join("\n");
|
|
3944
4179
|
return {
|
|
3945
4180
|
kind: "success",
|
|
3946
|
-
text: text === "" ?
|
|
4181
|
+
text: text === "" ? copy.noWindows : text
|
|
3947
4182
|
};
|
|
3948
4183
|
}
|
|
3949
4184
|
case "accounts": return {
|
|
3950
4185
|
kind: "success",
|
|
3951
|
-
text: await accountsText(deps)
|
|
4186
|
+
text: await accountsText(deps, copy)
|
|
3952
4187
|
};
|
|
3953
4188
|
case "models": return {
|
|
3954
4189
|
kind: "success",
|
|
3955
|
-
text: await modelsText(deps)
|
|
4190
|
+
text: await modelsText(deps, copy)
|
|
3956
4191
|
};
|
|
3957
4192
|
case "rules": return {
|
|
3958
4193
|
kind: "success",
|
|
3959
|
-
text: rulesText(deps)
|
|
4194
|
+
text: rulesText(deps, copy)
|
|
3960
4195
|
};
|
|
3961
4196
|
case "test": {
|
|
3962
|
-
const result = await testText(deps, arg === "" ? "deepseek-v4.1-flash" : arg);
|
|
4197
|
+
const result = await testText(deps, arg === "" ? "deepseek-v4.1-flash" : arg, copy);
|
|
3963
4198
|
return result.ok ? {
|
|
3964
4199
|
kind: "success",
|
|
3965
4200
|
text: result.text
|
|
@@ -3970,7 +4205,7 @@ function applyCommands(ctx, deps) {
|
|
|
3970
4205
|
}
|
|
3971
4206
|
default: return {
|
|
3972
4207
|
kind: "error",
|
|
3973
|
-
text:
|
|
4208
|
+
text: `${copy.unknown} "${verb}". ${copy.rules}: status | usage | accounts | models | rules | test [model]`
|
|
3974
4209
|
};
|
|
3975
4210
|
}
|
|
3976
4211
|
}
|
|
@@ -4540,10 +4775,15 @@ function aggregateSessions(sessions, days, now = Date.now()) {
|
|
|
4540
4775
|
if (at < since) continue;
|
|
4541
4776
|
matched += 1;
|
|
4542
4777
|
const meta = GENERATED_MODELS[model];
|
|
4543
|
-
const
|
|
4778
|
+
const livePricing = livePricingFor(provider, model);
|
|
4779
|
+
const priced = meta !== void 0 || livePricing !== void 0;
|
|
4544
4780
|
const free = meta?.free === true;
|
|
4545
4781
|
const partial = cacheWrite > 0 || !priced;
|
|
4546
|
-
const cost =
|
|
4782
|
+
const cost = !free && priced ? livePricing !== void 0 ? priceUsage(livePricing, {
|
|
4783
|
+
input,
|
|
4784
|
+
output,
|
|
4785
|
+
cacheRead
|
|
4786
|
+
}, at) : meta !== void 0 ? (input * meta.cost.input + output * meta.cost.output + cacheRead * meta.cost.cacheRead) / 1e6 : 0 : 0;
|
|
4547
4787
|
const key = `${provider}/${model}`;
|
|
4548
4788
|
let row = models.get(key);
|
|
4549
4789
|
if (row === void 0) {
|
|
@@ -4796,6 +5036,7 @@ function publicConfig(config) {
|
|
|
4796
5036
|
requestImageMaxBytes: config.requestImageMaxBytes,
|
|
4797
5037
|
proxyUrl: config.proxyUrl,
|
|
4798
5038
|
catalogRefreshMinutes: config.catalogRefreshMinutes,
|
|
5039
|
+
commandLocale: config.commandLocale,
|
|
4799
5040
|
visibleModels: config.visibleModels,
|
|
4800
5041
|
modelVisibility: config.modelVisibility,
|
|
4801
5042
|
filterModelsByPlan: config.filterModelsByPlan,
|
|
@@ -4998,6 +5239,7 @@ function configOps(patch) {
|
|
|
4998
5239
|
if ("requestImageMaxBytes" in patch) set("requestImageMaxBytes", patch.requestImageMaxBytes);
|
|
4999
5240
|
if ("proxyUrl" in patch) set("proxyUrl", normalizeProxyUrl(typeof patch.proxyUrl === "string" ? patch.proxyUrl : ""));
|
|
5000
5241
|
if ("catalogRefreshMinutes" in patch) set("catalogRefreshMinutes", patch.catalogRefreshMinutes);
|
|
5242
|
+
if ("commandLocale" in patch) set("commandLocale", patch.commandLocale === "zh" ? "zh" : "en");
|
|
5001
5243
|
if ("visibleModels" in patch) set("visibleModels", cleanStringArray(patch.visibleModels));
|
|
5002
5244
|
if ("modelVisibility" in patch) set("modelVisibility", cleanBooleanRecord(patch.modelVisibility));
|
|
5003
5245
|
if ("filterModelsByPlan" in patch) set("filterModelsByPlan", patch.filterModelsByPlan === true);
|
|
@@ -5906,7 +6148,8 @@ function apply(ctx, config) {
|
|
|
5906
6148
|
config: current,
|
|
5907
6149
|
pool: keys,
|
|
5908
6150
|
usage,
|
|
5909
|
-
adapter
|
|
6151
|
+
adapter,
|
|
6152
|
+
locale: () => current().commandLocale
|
|
5910
6153
|
});
|
|
5911
6154
|
});
|
|
5912
6155
|
let refreshTuiSettings;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-opencode-provider",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.31",
|
|
4
4
|
"description": "OpenCode / OpenCode Go LLM provider plugin for DeepSeek Harness (dsh).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"build": "tsdown",
|
|
71
71
|
"prepare": "tsdown 1>&2",
|
|
72
72
|
"typecheck": "tsc --noEmit",
|
|
73
|
-
"test": "node --import tsx --test tests/core.test.ts tests/routes.test.ts tests/limits.test.ts tests/analytics.test.ts tests/adapter.test.ts tests/usage.test.ts tests/request-log.test.ts tests/account-rules.test.ts tests/commands.test.ts tests/tool-schema.test.ts tests/tui-settings.test.ts tests/plan-filter.test.ts tests/image-budget.test.ts tests/tools.test.ts tests/plugin-updater.test.ts tests/client-render.test.tsx tests/sse.test.ts tests/proxy.test.ts tests/tool-call-validation.test.ts",
|
|
73
|
+
"test": "node --import tsx --test tests/core.test.ts tests/routes.test.ts tests/limits.test.ts tests/analytics.test.ts tests/adapter.test.ts tests/usage.test.ts tests/request-log.test.ts tests/account-rules.test.ts tests/commands.test.ts tests/tool-schema.test.ts tests/tui-settings.test.ts tests/plan-filter.test.ts tests/image-budget.test.ts tests/tools.test.ts tests/plugin-updater.test.ts tests/client-render.test.tsx tests/sse.test.ts tests/proxy.test.ts tests/tool-call-validation.test.ts tests/pricing.test.ts",
|
|
74
74
|
"check": "npm run typecheck && npm test && npm run build",
|
|
75
75
|
"release": "npm run check && npm pack",
|
|
76
76
|
"sync:models": "node scripts/sync-models.mjs"
|