@goodandready/dsh-opencode-provider 0.5.30 → 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 +6 -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 +86 -36
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
|
|
3
9
|
## 0.5.30 — release 7: pricing accuracy
|
|
4
10
|
|
|
5
11
|
- **#74 — peak/off-peak multipliers**: analytics now reads live gateway `cost.multipliers` and evaluates UTC weekday/hour/date conditions at the session timestamp.
|
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,
|
|
@@ -3978,22 +3980,66 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3978
3980
|
return error.code === "RATE_LIMIT" || error.code === "QUOTA" || error.code === "SERVER" || error.code === "TIMEOUT" || error.code === "TRANSPORT" || error.code === "EMPTY_RESPONSE";
|
|
3979
3981
|
}
|
|
3980
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
|
+
}
|
|
3981
4027
|
// src/commands.ts
|
|
3982
4028
|
function bar(percent, width = 10) {
|
|
3983
4029
|
const clamped = Math.max(0, Math.min(100, percent));
|
|
3984
4030
|
const filled = Math.round(clamped / 100 * width);
|
|
3985
4031
|
return `[${"█".repeat(filled)}${"░".repeat(width - filled)}] ${clamped.toFixed(0)}%`;
|
|
3986
4032
|
}
|
|
3987
|
-
function reset(iso) {
|
|
4033
|
+
function reset(iso, copy) {
|
|
3988
4034
|
if (iso === void 0) return "";
|
|
3989
4035
|
const date = new Date(iso);
|
|
3990
4036
|
if (Number.isNaN(date.getTime())) return "";
|
|
3991
|
-
return ` · resets ${date.toISOString().replace("T", " ").slice(0, 16)}Z`;
|
|
4037
|
+
return ` · ${copy.resets} ${date.toISOString().replace("T", " ").slice(0, 16)}Z`;
|
|
3992
4038
|
}
|
|
3993
|
-
function windowLine(label, window) {
|
|
4039
|
+
function windowLine(label, window, copy) {
|
|
3994
4040
|
if (window === void 0) return void 0;
|
|
3995
4041
|
const status = window.status === void 0 || window.status === "ok" ? "" : ` (${window.status})`;
|
|
3996
|
-
return `${label}: ${bar(window.percentUsed)}${status}${reset(window.resetsAt)}`;
|
|
4042
|
+
return `${label}: ${bar(window.percentUsed)}${status}${reset(window.resetsAt, copy)}`;
|
|
3997
4043
|
}
|
|
3998
4044
|
function percent(window) {
|
|
3999
4045
|
if (window === void 0) return "—";
|
|
@@ -4015,18 +4061,18 @@ function relative(until) {
|
|
|
4015
4061
|
if (delta >= 36e5) return `${Math.round(delta / 36e5)}h`;
|
|
4016
4062
|
return `${Math.max(1, Math.round(delta / 6e4))}m`;
|
|
4017
4063
|
}
|
|
4018
|
-
async function accountsText(deps) {
|
|
4064
|
+
async function accountsText(deps, copy) {
|
|
4019
4065
|
const lines = [];
|
|
4020
4066
|
for (const provider of [GO_PROVIDER, CONSOLE_PROVIDER]) {
|
|
4021
4067
|
let keys;
|
|
4022
4068
|
try {
|
|
4023
4069
|
keys = await deps.pool.resolveAll(provider);
|
|
4024
4070
|
} catch {
|
|
4025
|
-
lines.push(`${provider}:
|
|
4071
|
+
lines.push(`${provider}: ${copy.noCredential}`);
|
|
4026
4072
|
continue;
|
|
4027
4073
|
}
|
|
4028
4074
|
if (keys.length === 0) {
|
|
4029
|
-
lines.push(`${provider}:
|
|
4075
|
+
lines.push(`${provider}: ${copy.noCredential}`);
|
|
4030
4076
|
continue;
|
|
4031
4077
|
}
|
|
4032
4078
|
const active = keys.find((entry) => deps.adapter.accountHealth(entry.key) === void 0);
|
|
@@ -4036,8 +4082,8 @@ async function accountsText(deps) {
|
|
|
4036
4082
|
const usage = provider === "opencode-go-direct" ? await deps.usage(provider, entry.key) : void 0;
|
|
4037
4083
|
const tags = [
|
|
4038
4084
|
entry.source,
|
|
4039
|
-
entry === active ?
|
|
4040
|
-
health === void 0 ? "" :
|
|
4085
|
+
entry === active ? copy.active : "",
|
|
4086
|
+
health === void 0 ? "" : `${copy.cooling} ${health.reason} ${relative(health.coolingUntil)}`,
|
|
4041
4087
|
usage?.report === void 0 ? usage?.error ?? "" : summary(usage.report)
|
|
4042
4088
|
].filter((tag) => tag !== "");
|
|
4043
4089
|
lines.push(` ${index + 1}. ${entry.label ?? "default"}: ${tags.join(" · ")}`);
|
|
@@ -4045,27 +4091,27 @@ async function accountsText(deps) {
|
|
|
4045
4091
|
}
|
|
4046
4092
|
return lines.join("\n");
|
|
4047
4093
|
}
|
|
4048
|
-
function rulesText(deps) {
|
|
4094
|
+
function rulesText(deps, copy) {
|
|
4049
4095
|
const rules = deps.config().modelAccountRules;
|
|
4050
|
-
if (rules.length === 0) return
|
|
4096
|
+
if (rules.length === 0) return copy.noRules;
|
|
4051
4097
|
return rules.map((rule) => `${rule.pattern} → ${rule.account}`).join("\n");
|
|
4052
4098
|
}
|
|
4053
|
-
async function modelsText(deps) {
|
|
4099
|
+
async function modelsText(deps, copy) {
|
|
4054
4100
|
const config = deps.config();
|
|
4055
4101
|
try {
|
|
4056
4102
|
const models = await deps.adapter.listModels(GO_PROVIDER, { unfiltered: true });
|
|
4057
|
-
if (models.length === 0) return
|
|
4103
|
+
if (models.length === 0) return copy.noModels;
|
|
4058
4104
|
return models.map((model) => {
|
|
4059
4105
|
const meta = effectiveModel(model.id, config);
|
|
4060
4106
|
const limits = limitsFor(model.id);
|
|
4061
|
-
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 !== "");
|
|
4062
4108
|
return `- ${model.id}${tags.length === 0 ? "" : ` · ${tags.join(" · ")}`}`;
|
|
4063
4109
|
}).join("\n");
|
|
4064
4110
|
} catch (error) {
|
|
4065
|
-
return
|
|
4111
|
+
return `${copy.modelsUnavailable}: ${error instanceof Error ? error.message : String(error)}`;
|
|
4066
4112
|
}
|
|
4067
4113
|
}
|
|
4068
|
-
async function testText(deps, model) {
|
|
4114
|
+
async function testText(deps, model, copy) {
|
|
4069
4115
|
const started = Date.now();
|
|
4070
4116
|
let text = "";
|
|
4071
4117
|
try {
|
|
@@ -4088,18 +4134,18 @@ async function testText(deps, model) {
|
|
|
4088
4134
|
} catch (error) {
|
|
4089
4135
|
return {
|
|
4090
4136
|
ok: false,
|
|
4091
|
-
text: `${model}:
|
|
4137
|
+
text: `${model}: ${copy.testFailed} — ${error instanceof Error ? error.message : String(error)}`
|
|
4092
4138
|
};
|
|
4093
4139
|
}
|
|
4094
4140
|
}
|
|
4095
|
-
async function statusText(deps) {
|
|
4141
|
+
async function statusText(deps, copy) {
|
|
4096
4142
|
const config = deps.config();
|
|
4097
|
-
const rules = config.modelAccountRules.length === 0 ?
|
|
4143
|
+
const rules = config.modelAccountRules.length === 0 ? copy.none : config.modelAccountRules.map((rule) => `${rule.pattern} → ${rule.account}`).join(", ");
|
|
4098
4144
|
return [
|
|
4099
|
-
|
|
4100
|
-
await accountsText(deps),
|
|
4101
|
-
|
|
4102
|
-
|
|
4145
|
+
copy.provider,
|
|
4146
|
+
await accountsText(deps, copy),
|
|
4147
|
+
`${copy.rules}: ${rules}`,
|
|
4148
|
+
`${copy.auth}: ${config.openCodeAuthEntry} · ${config.apiKeyEnv}`
|
|
4103
4149
|
].filter((line) => line !== "").join("\n");
|
|
4104
4150
|
}
|
|
4105
4151
|
function applyCommands(ctx, deps) {
|
|
@@ -4108,6 +4154,7 @@ function applyCommands(ctx, deps) {
|
|
|
4108
4154
|
description: "OpenCode / OpenCode Go status, accounts, quotas, models and rules",
|
|
4109
4155
|
input: { hint: "status | usage | accounts | models | rules | test [model]" },
|
|
4110
4156
|
handler: async (invocation) => {
|
|
4157
|
+
const copy = commandCopy(deps.locale());
|
|
4111
4158
|
const raw = invocation.rawInput.trim();
|
|
4112
4159
|
const parts = raw === "" ? ["status"] : raw.split(/\s+/);
|
|
4113
4160
|
const verb = parts[0] ?? "status";
|
|
@@ -4115,39 +4162,39 @@ function applyCommands(ctx, deps) {
|
|
|
4115
4162
|
switch (verb.toLowerCase()) {
|
|
4116
4163
|
case "status": return {
|
|
4117
4164
|
kind: "success",
|
|
4118
|
-
text: await statusText(deps)
|
|
4165
|
+
text: await statusText(deps, copy)
|
|
4119
4166
|
};
|
|
4120
4167
|
case "usage": {
|
|
4121
4168
|
const usage = await deps.usage(GO_PROVIDER);
|
|
4122
4169
|
if (usage?.report === void 0) return {
|
|
4123
4170
|
kind: "error",
|
|
4124
|
-
text:
|
|
4171
|
+
text: `${copy.quotaUnavailable}: ${usage?.error ?? "no report"}`
|
|
4125
4172
|
};
|
|
4126
4173
|
const windows = usage.report.windows;
|
|
4127
4174
|
const text = [
|
|
4128
|
-
windowLine(
|
|
4129
|
-
windowLine(
|
|
4130
|
-
windowLine(
|
|
4175
|
+
windowLine(copy.fiveHour, windows.fiveHour, copy),
|
|
4176
|
+
windowLine(copy.weekly, windows.weekly, copy),
|
|
4177
|
+
windowLine(copy.monthly, windows.monthly, copy)
|
|
4131
4178
|
].filter((line) => line !== void 0).join("\n");
|
|
4132
4179
|
return {
|
|
4133
4180
|
kind: "success",
|
|
4134
|
-
text: text === "" ?
|
|
4181
|
+
text: text === "" ? copy.noWindows : text
|
|
4135
4182
|
};
|
|
4136
4183
|
}
|
|
4137
4184
|
case "accounts": return {
|
|
4138
4185
|
kind: "success",
|
|
4139
|
-
text: await accountsText(deps)
|
|
4186
|
+
text: await accountsText(deps, copy)
|
|
4140
4187
|
};
|
|
4141
4188
|
case "models": return {
|
|
4142
4189
|
kind: "success",
|
|
4143
|
-
text: await modelsText(deps)
|
|
4190
|
+
text: await modelsText(deps, copy)
|
|
4144
4191
|
};
|
|
4145
4192
|
case "rules": return {
|
|
4146
4193
|
kind: "success",
|
|
4147
|
-
text: rulesText(deps)
|
|
4194
|
+
text: rulesText(deps, copy)
|
|
4148
4195
|
};
|
|
4149
4196
|
case "test": {
|
|
4150
|
-
const result = await testText(deps, arg === "" ? "deepseek-v4.1-flash" : arg);
|
|
4197
|
+
const result = await testText(deps, arg === "" ? "deepseek-v4.1-flash" : arg, copy);
|
|
4151
4198
|
return result.ok ? {
|
|
4152
4199
|
kind: "success",
|
|
4153
4200
|
text: result.text
|
|
@@ -4158,7 +4205,7 @@ function applyCommands(ctx, deps) {
|
|
|
4158
4205
|
}
|
|
4159
4206
|
default: return {
|
|
4160
4207
|
kind: "error",
|
|
4161
|
-
text:
|
|
4208
|
+
text: `${copy.unknown} "${verb}". ${copy.rules}: status | usage | accounts | models | rules | test [model]`
|
|
4162
4209
|
};
|
|
4163
4210
|
}
|
|
4164
4211
|
}
|
|
@@ -4989,6 +5036,7 @@ function publicConfig(config) {
|
|
|
4989
5036
|
requestImageMaxBytes: config.requestImageMaxBytes,
|
|
4990
5037
|
proxyUrl: config.proxyUrl,
|
|
4991
5038
|
catalogRefreshMinutes: config.catalogRefreshMinutes,
|
|
5039
|
+
commandLocale: config.commandLocale,
|
|
4992
5040
|
visibleModels: config.visibleModels,
|
|
4993
5041
|
modelVisibility: config.modelVisibility,
|
|
4994
5042
|
filterModelsByPlan: config.filterModelsByPlan,
|
|
@@ -5191,6 +5239,7 @@ function configOps(patch) {
|
|
|
5191
5239
|
if ("requestImageMaxBytes" in patch) set("requestImageMaxBytes", patch.requestImageMaxBytes);
|
|
5192
5240
|
if ("proxyUrl" in patch) set("proxyUrl", normalizeProxyUrl(typeof patch.proxyUrl === "string" ? patch.proxyUrl : ""));
|
|
5193
5241
|
if ("catalogRefreshMinutes" in patch) set("catalogRefreshMinutes", patch.catalogRefreshMinutes);
|
|
5242
|
+
if ("commandLocale" in patch) set("commandLocale", patch.commandLocale === "zh" ? "zh" : "en");
|
|
5194
5243
|
if ("visibleModels" in patch) set("visibleModels", cleanStringArray(patch.visibleModels));
|
|
5195
5244
|
if ("modelVisibility" in patch) set("modelVisibility", cleanBooleanRecord(patch.modelVisibility));
|
|
5196
5245
|
if ("filterModelsByPlan" in patch) set("filterModelsByPlan", patch.filterModelsByPlan === true);
|
|
@@ -6099,7 +6148,8 @@ function apply(ctx, config) {
|
|
|
6099
6148
|
config: current,
|
|
6100
6149
|
pool: keys,
|
|
6101
6150
|
usage,
|
|
6102
|
-
adapter
|
|
6151
|
+
adapter,
|
|
6152
|
+
locale: () => current().commandLocale
|
|
6103
6153
|
});
|
|
6104
6154
|
});
|
|
6105
6155
|
let refreshTuiSettings;
|