dsh-model 0.23.0 → 0.23.3

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/README.en.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  # dsh-model
4
4
 
5
- **One address for all your models: subscriptions, free tiers and local models, with daily check-in credits claimed for you.**
5
+ **📅 Daily check-in credits claimed for you · 🔁 one address that relays every model · 🧩 free and subscription models, your pick**
6
6
 
7
- One OpenAI-compatible endpoint, one key, dozens of models. dsh, Cline, Continue, Cherry Studio, scripts: anything that speaks the OpenAI API just works.
7
+ Install once and all three are taken care of. dsh gets a management page; without dsh, the command line does everything.
8
8
 
9
9
  <p>
10
10
  <a href="LICENSE"><img src="https://img.shields.io/github/license/AIcivilization/dsh-model" alt="MIT license"></a>
@@ -19,22 +19,45 @@ English · [中文](README.md)
19
19
 
20
20
  </div>
21
21
 
22
- ## Why
22
+ ## 📅 1. Daily check-in: credits land on their own
23
23
 
24
- You may have several at once: Codex from ChatGPT Plus, a Kimi coding plan, free WorkBuddy credits, free Gemini and Groq keys, and Ollama on your machine. Each has its own API and sign-in, every tool needs its own setup, and when a quota runs out you switch by hand.
24
+ **The most hands-off part: no more remembering to claim the daily check-in credits.**
25
25
 
26
- dsh-model turns them into **one address and one kind of key**:
26
+ | Product | How it connects | Every day |
27
+ |---|---|---|
28
+ | **Trae** | Detected automatically if Trae is installed and signed in; nothing to do | Check-in credits (200 a day in testing) |
29
+ | **Qoder** | Paste a personal access token once (China or international) | That day's check-in credits |
30
+ | **WorkBuddy (China)** | Sign in once | Check-in credits |
31
+
32
+ - **Automatic by default**: done at a random 5–55 minutes after midnight Beijing time; failures retry an hour later; if the computer was off, it catches up within a minute of starting.
33
+ - **You can see it**: one line at the top of the settings page, "Checked in today: …", with a one-click "Check in all" for anything missed, plus a one-time notice in the corner of dsh.
34
+ - **Check-in only, credits untouched**: they stay on your account for use in each product's own app. Command line: `dsh-model checkin`.
35
+
36
+ ## 🔁 2. Relay: one address, one key, every model
27
37
 
28
- - 🔌 **Connect once, use everywhere**: every model shows up in dsh, grouped by source; any other tool uses the same address and key.
29
- - 🆓 **Get the most from free tiers**: free Gemini / Groq / OpenRouter keys, local models and WorkBuddy credits; every source is tagged Free / Free · limited / Paid.
30
- - 📅 **Daily check-in, credits claimed for you**: WorkBuddy (China), Qoder and Trae are checked in automatically every day.
31
- - 🔁 **Rate limits are not the end**: build a combo (say Codex → Kimi → WorkBuddy) and requests move to the next model on a rate limit or used-up quota; a model offered by several sources also switches automatically.
32
- - 📊 **Know where you stand**: plan usage with reset countdowns, remaining credits and when they expire, success rate and speed per model, recent failures with advice, and a notice when quota runs low.
33
- - 🔒 **Secure by default**: a key is always required, one per device, revocable at any time; every sign-in happens on your own machine.
34
- - 🖥️ **Laptop or server**: Windows, macOS, Linux; on a VPS (with [dsh-vps](https://github.com/AIcivilization/dsh-vps)) it opens an https address you can use from anywhere.
35
- - 🧹 **Installs in one command, uninstalls cleanly**: dsh's config is restored byte for byte.
38
+ Your model sources each have their own API and sign-in. dsh-model puts them behind **one OpenAI-compatible address**:
39
+
40
+ - **Shows up in dsh**: every model, grouped by source, with nothing to configure.
41
+ - **Works in other tools**: Cline, Continue, Cherry Studio, scripts: anything that speaks the OpenAI API uses the same address and key.
42
+ - **Rate limits are not the end**: build a combo (say Codex → Kimi → Gemini) and the next model takes over on a rate limit, used-up quota or error.
43
+ - **Know where you stand**: plan usage with reset countdowns, success rate and speed per model, recent failures with what to do, and a notice when quota runs low.
44
+ - **Secure by default**: a key is always required, one per device, revocable any time; every sign-in happens on your own machine.
45
+ - **Laptop or server**: Windows, macOS, Linux; on a VPS (with [dsh-vps](https://github.com/AIcivilization/dsh-vps)) it opens an https address you can use from anywhere. Uninstall restores dsh's config byte for byte.
46
+
47
+ ## 🧩 3. Models: free or subscription, connect what you have
48
+
49
+ | Kind | Sources |
50
+ |---|---|
51
+ | **Free (suggested)** | **SiliconFlow** (reachable from China; some models free after identity verification), **Gemini** (if you can reach Google; the best free models) |
52
+ | Free · limited | Groq, OpenRouter (`:free` models) |
53
+ | Local | Ollama, LM Studio: appear once started, no sign-in |
54
+ | Subscription | Codex (ChatGPT Plus / Pro), Kimi Code, Grok, Muse, Devin: sign in from the management page |
55
+ | Pay as you go | DeepSeek official, OpenCode Zen, any OpenAI-compatible service |
56
+
57
+ Every source is tagged Free / Free · limited / Paid; see [Sources and cost](#sources-and-cost) below. dsh-model only connects; it provides no quota of its own.
58
+
59
+ > WorkBuddy: since 2026-10 it recognises conversations sent by dsh and blocks them, so dsh-model uses it for check-in only and does not connect its models by default.
36
60
 
37
- No dsh? It still works: the command line manages everything; dsh just adds a management page.
38
61
 
39
62
  ## Quick start
40
63
 
@@ -56,12 +79,12 @@ On a dsh-vps server, use `sudo dsh-model setup`. On Windows, run it in a normal
56
79
 
57
80
  | Section | What it holds |
58
81
  |---|---|
82
+ | **Daily check-in** | WorkBuddy (China), Qoder and Trae are checked in automatically every day (or with one click); the card shows what you got today |
59
83
  | **Endpoint** | Local and public addresses, each with a Copy button |
60
84
  | **Sources** | One switch per provider: on connects (and signs in if needed), off pauses and keeps the sign-in. Each row is tagged Free / Free · limited / Paid and shows plan usage with reset countdowns, or remaining credits and the next batch to expire; a used-up plan shows when it comes back |
61
85
  | **Models** | Pick which models of each source appear in dsh (5 suggested per source by default); the rest stay available through the endpoint; a "Test" button per model |
62
86
  | **Combos** | Put several models in order and it becomes one model in dsh: the first is used, and the next takes over on a rate limit, used-up quota or error. Other tools use the model name `combo/<id>` |
63
87
  | **Auto switch** | When a model is offered by several sources, dsh gets an "Auto switch (multiple sources)" group that moves to another source when one is rate-limited or failing |
64
- | **Daily check-in** | WorkBuddy (China), Qoder and Trae are checked in automatically every day (or with one click); the card shows what you got today |
65
88
  | **Recent failures** | Failed requests in the last 24 hours: which model, why, and what to do (no conversation content kept) |
66
89
  | **API keys** | OpenAI-style, one per device. Requests, success rate, latency and speed over 24 hours; copy, rotate, revoke |
67
90
  | **Model stats** | Success rate, latency and speed per model |
@@ -140,6 +163,7 @@ See the [design doc](docs/DESIGN.md) (Chinese).
140
163
  - Meant for personal use: one account of your own per provider; do not use it to serve others.
141
164
  - Using subscription and WorkBuddy credentials outside the official clients may break the providers' terms and put the account at risk. Decide for yourself.
142
165
  - Windows 10 / 11 (x64, ARM64), macOS and Linux, Node ≥ 20.
166
+ - Downloading the engine needs GitHub: setup uses the system proxy (Windows, macOS) or `HTTPS_PROXY` automatically; if it still cannot connect it says why, and you can pass `--proxy <url>` or download the file yourself and set `DSH_MODEL_ENGINE_ARCHIVE`.
143
167
  - Windows: the background services start at sign-in with no window. If an antivirus blocks them (the program is gone or will not start), trust `%USERPROFILE%\.dsh-model` and run `dsh-model repair`.
144
168
 
145
169
  ## License
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  # dsh-model
4
4
 
5
- **一个地址,用上你所有的模型:订阅、免费额度、本机模型,签到积分自动领。**
5
+ **📅 每天自动签到领积分 · 🔁 一个地址中转所有模型 · 🧩 免费和订阅模型随你接**
6
6
 
7
- 一个 OpenAI 兼容地址,一把 key,几十个模型。dsh、Cline、Continue、Cherry Studio、脚本……凡是支持 OpenAI 接口的软件都能直接用。
7
+ 装一次,三件事都替你做好。dsh 里有图形管理页,没有 dsh 也能用命令行。
8
8
 
9
9
  <p>
10
10
  <a href="LICENSE"><img src="https://img.shields.io/github/license/AIcivilization/dsh-model" alt="MIT license"></a>
@@ -19,22 +19,45 @@
19
19
 
20
20
  </div>
21
21
 
22
- ## 为什么用它
22
+ ## 📅 1. 每日签到:积分自动到账
23
23
 
24
- 你手上可能有好几样:ChatGPT Plus 带的 Codex、Kimi 的编程套餐、WorkBuddy 的免费积分、Gemini 和 Groq 的免费 key,还有本机的 Ollama。每家接口和登录方式都不一样,每个软件都得分别配置,额度用完了还要手动换。
24
+ **最省心的一件事:每天的签到积分,不用再记着去领。**
25
25
 
26
- dsh-model 把它们合成**一个地址、一种 key**:
26
+ | 产品 | 怎么接入 | 每天 |
27
+ |---|---|---|
28
+ | **Trae** | 本机装了 Trae 并登录过,自动识别,什么都不用做 | 签到送积分(实测每天 200) |
29
+ | **Qoder** | 粘贴一次个人访问令牌(国内版、国际版都行) | 领当天的签到积分 |
30
+ | **WorkBuddy 国内版** | 登录一次 | 签到送积分 |
31
+
32
+ - **自动签到默认打开**:北京时间零点后随机延后 5–55 分钟签好;失败的一小时后重试;电脑关机错过了,开机一分钟内补签。
33
+ - **结果看得见**:设置页顶部一行「今日已完成签到:…」,没签上的可以「一键签到」;签完在 dsh 右下角提示一次。
34
+ - **只签到,不碰积分**:积分留在你的账号里,照常在各家官方软件里用。命令行:`dsh-model checkin`。
35
+
36
+ ## 🔁 2. 中转:一个地址、一把 key,用上所有模型
27
37
 
28
- - 🔌 **一处接入,处处可用**:dsh 里自动出现全部模型,按来源分组;其他软件填上同一个地址和 key 就行。
29
- - 🆓 **把免费的用足**:Gemini / Groq / OpenRouter 免费 key、本机模型、WorkBuddy 积分都能接;每个来源标出「免费 / 免费·额度少 / 需付费」。
30
- - 📅 **每日签到,自动领积分**:WorkBuddy 国内版、Qoder、Trae 每天自动签到,不用记。
31
- - 🔁 **不怕限流**:自己排一个组合(如 Codex → Kimi → WorkBuddy),一个限流或额度用完就自动换下一个;同一个模型在几个来源都有时也会自动切换。
32
- - 📊 **心里有数**:套餐用量和重置倒计时、剩余积分和到期时间、每个模型的成功率和速度、最近失败的原因和建议;额度快用完时提醒。
33
- - 🔒 **默认安全**:始终要求 key,每台设备一把,可随时吊销;登录全在本机完成,不经过第三方。
34
- - 🖥️ **电脑和服务器都行**:Windows、macOS、Linux;装在 VPS 上(配合 [dsh-vps](https://github.com/AIcivilization/dsh-vps))自动开一个 https 地址,电脑、手机随处用。
35
- - 🧹 **装得上,卸得净**:一条命令安装;卸载时把 dsh 的配置逐字节还原。
38
+ 你手上的模型来源,各家接口、登录方式都不一样。dsh-model 把它们合成**一个 OpenAI 兼容地址**:
39
+
40
+ - **dsh 里自动出现**:全部模型按来源分组,不用手动配置。
41
+ - **其他软件也能用**:Cline、Continue、Cherry Studio、脚本……凡是支持 OpenAI 接口的,填同一个地址和 key 就行。
42
+ - **不怕限流**:自己排一个组合(如 Codex → Kimi → Gemini),一个限流、额度用完或出错,就自动换下一个。
43
+ - **心里有数**:套餐用量和重置倒计时、每个模型的成功率和速度、最近失败的原因和该怎么办;额度快用完时提醒。
44
+ - **默认安全**:始终要求 key,每台设备一把,可随时吊销;登录全在本机完成。
45
+ - **电脑和服务器都行**:Windows、macOS、Linux;装在 VPS 上(配合 [dsh-vps](https://github.com/AIcivilization/dsh-vps))自动开一个 https 地址,电脑、手机随处用。卸载时把 dsh 的配置逐字节还原。
46
+
47
+ ## 🧩 3. 模型提供:免费的、订阅的都能接
48
+
49
+ | 类型 | 来源 |
50
+ |---|---|
51
+ | **免费(推荐)** | **硅基流动**(国内直连,实名后部分模型免费)、**Gemini**(能上外网,免费模型里质量最好) |
52
+ | 免费 · 额度少 | Groq、OpenRouter(`:free` 模型) |
53
+ | 本机 | Ollama、LM Studio:启动后自动出现,不用登录 |
54
+ | 订阅 | Codex(ChatGPT Plus / Pro)、Kimi Code、Grok、Muse、Devin:在管理页里登录即可 |
55
+ | 按量付费 | DeepSeek 官方、OpenCode Zen、任意 OpenAI 兼容服务 |
56
+
57
+ 每个来源都标出「免费 / 免费·额度少 / 需付费」,详见下面的[来源与费用](#来源与费用)。dsh-model 只负责转接,不提供额度。
58
+
59
+ > WorkBuddy:2026-10 起它会认出 dsh 发出的对话并拦下,所以 dsh-model 只用它来签到,模型默认不接进 dsh。
36
60
 
37
- 没有 dsh 也能用:命令行就能管理全部功能,dsh 里多一个图形管理页而已。
38
61
 
39
62
  ## 快速开始
40
63
 
@@ -56,12 +79,12 @@ dsh-model setup
56
79
 
57
80
  | 区块 | 内容 |
58
81
  |---|---|
82
+ | **每日签到** | WorkBuddy 国内版、Qoder、Trae 每天自动签到(也可一键签到),卡片显示今天领了多少 |
59
83
  | **访问地址** | 本机地址和外网地址,各带「复制」按钮 |
60
- | **来源** | 每家一个开关:打开就接入(没登录会弹出登录框),关闭只停用、保留登录。每行标出「免费 / 免费·额度少 / 需付费」,下面显示套餐用量和重置倒计时,或剩余积分和最近到期的一批;额度用完会标出多久后恢复 |
84
+ | **来源** | 每家一个开关:打开就接入(没登录会弹出登录框),关闭只停用、保留登录。每行标出「免费 / 免费·额度少 / 需付费」;没登录的、关掉的收进折叠列表。下面显示套餐用量和重置倒计时,或剩余积分和最近到期的一批;额度用完会标出多久后恢复 |
61
85
  | **选模型** | 每个来源选哪些模型显示在 dsh 里,默认每家推荐 5 个,其余的经统一地址照样可用;每个模型旁边有「测一下」 |
62
86
  | **组合切换** | 把几个模型按顺序排成一个组合,在 dsh 里就是一个模型:先用第一个,限流、额度用完或出错就换下一个。其他软件用模型名 `combo/<id>` |
63
87
  | **自动切换** | 同一个模型在几个来源都有时,dsh 里多一组「自动切换(多来源)」,一个来源限流或出错就自动换另一个 |
64
- | **每日签到** | WorkBuddy 国内版、Qoder、Trae 每天自动签到(也可一键签到),卡片显示今天领了多少 |
65
88
  | **最近失败** | 最近 24 小时失败的请求:哪个模型、为什么、该怎么办(不记录对话内容) |
66
89
  | **API key** | OpenAI 格式,每台设备一把。显示 24 小时内的请求数、成功率、延迟和速度,可复制、轮换、吊销 |
67
90
  | **模型统计** | 按模型显示成功率、延迟和速度 |
@@ -140,6 +163,7 @@ dsh · 编辑器 · 脚本 ──Bearer key──▶ 引擎 127.0.0.1:8317/v1(
140
163
  - 适合个人使用:每家用自己的一个账号,不要拿来对外提供服务。
141
164
  - 订阅和 WorkBuddy 的凭据在官方客户端之外使用,可能不符合服务商的条款,账号有风险,请自行判断。
142
165
  - 支持 Windows 10 / 11(x64、ARM64)、macOS、Linux,需要 Node ≥ 20。
166
+ - 下载引擎要连 GitHub:setup 会自动用上系统代理(Windows、macOS)或 `HTTPS_PROXY`;连不上时会说明原因,可以用 `--proxy <地址>` 指定代理,或手动下载后设置 `DSH_MODEL_ENGINE_ARCHIVE`。
143
167
  - Windows:后台服务登录后自动运行、不弹窗口。如果被杀毒软件拦下(程序不见了或启动不了),把 `%USERPROFILE%\.dsh-model` 加入信任,再执行 `dsh-model repair`。
144
168
 
145
169
  ## License
@@ -45,9 +45,9 @@ async function upgrade(ctx, requested) {
45
45
  let asset = version === manifest.version ? manifest.assets[target] : undefined;
46
46
  if (!asset) {
47
47
  warn(L(`v${version} 不在本版 dsh-model 的锁定清单里,校验值取自上游发布的 checksums.txt`, `v${version} is not pinned by this dsh-model; using the checksum from the upstream checksums.txt`));
48
- asset = await fetchUpstreamAsset(manifest, version, target);
48
+ asset = await fetchUpstreamAsset(manifest, version, target, { proxy: all.config.proxy });
49
49
  }
50
- await installVersion(ctx, version, asset);
50
+ await installVersion(ctx, version, asset, { proxy: all.config.proxy });
51
51
  ok(L(`v${version} 已下载并校验`, `v${version} downloaded and verified`));
52
52
  await trialRun(ctx, all, versionBinary(ctx, version));
53
53
  ok(L('试运行通过', 'Trial run passed'));
@@ -164,10 +164,10 @@ export async function ensureEngine(ctx, all, opts = {}) {
164
164
  const manifest = await loadManifest();
165
165
  const target = platformTarget();
166
166
  const version = config.engine.version ?? manifest.version;
167
- const asset = version === manifest.version ? manifest.assets[target] : await fetchUpstreamAsset(manifest, version, target);
167
+ const asset = version === manifest.version ? manifest.assets[target] : await fetchUpstreamAsset(manifest, version, target, { proxy: config.proxy });
168
168
  if (!asset)
169
169
  throw new DshModelError('unsupported_platform', L(`引擎没有 ${target} 的发布包`, `No engine build for ${target}`));
170
- const installed = await installVersion(ctx, version, asset);
170
+ const installed = await installVersion(ctx, version, asset, { proxy: config.proxy });
171
171
  if ((await currentVersion(ctx)) !== version)
172
172
  await activateVersion(ctx, version);
173
173
  config.engine.version = version;
@@ -3,13 +3,12 @@
3
3
  // 只信任仓库里 engine-manifest.json 的 sha256;校验不过直接失败。
4
4
  // 版本目录 engine/versions/<v>/,current 软链指向在用版本,升级与回滚只切软链。
5
5
  // Windows:发布包是 zip、程序叫 cli-proxy-api.exe;建软链要管理员,所以 current 用目录联接(junction),不需要权限。
6
- import { createWriteStream } from 'node:fs';
7
6
  import { chmod, mkdir, readFile, readlink, rename, rm, symlink, unlink } from 'node:fs/promises';
7
+ import { tmpdir } from 'node:os';
8
8
  import { join, resolve } from 'node:path';
9
- import { Readable } from 'node:stream';
10
- import { pipeline } from 'node:stream/promises';
11
- import { DshModelError } from '../errors.js';
9
+ import { DshModelError, isDshModelError } from '../errors.js';
12
10
  import { L } from '../i18n.js';
11
+ import { downloadFile, downloadText } from '../util/download.js';
13
12
  import { run } from '../util/exec.js';
14
13
  import { ensureDir, exists, sha256 } from '../util/fs.js';
15
14
  import { PKG_ROOT } from '../util/pkg.js';
@@ -44,13 +43,19 @@ export async function currentVersion(ctx) {
44
43
  }
45
44
  }
46
45
  /** 从上游发布的 checksums.txt 取某版本的资产(engine upgrade <ver> 指定了清单外版本时用) */
47
- export async function fetchUpstreamAsset(manifest, version, target) {
46
+ export async function fetchUpstreamAsset(manifest, version, target, opts = {}) {
48
47
  const base = `https://github.com/${manifest.repo}/releases/download/v${version}`;
49
48
  const file = assetFileName(version, target);
50
- const res = await fetch(`${base}/checksums.txt`);
51
- if (!res.ok)
52
- throw new DshModelError('engine_version_not_found', L(`找不到引擎版本 v${version}(HTTP ${res.status})`, `Engine version v${version} not found (HTTP ${res.status})`));
53
- const line = (await res.text()).split('\n').find((l) => l.trim().endsWith(file));
49
+ let text;
50
+ try {
51
+ text = await downloadText(`${base}/checksums.txt`, join(tmpdir(), `dsh-model-checksums-${process.pid}-${Date.now()}.txt`), opts);
52
+ }
53
+ catch (error) {
54
+ if (isDshModelError(error) && error.code === 'download_not_found')
55
+ throw new DshModelError('engine_version_not_found', L(`找不到引擎版本 v${version}`, `Engine version v${version} not found`));
56
+ throw error;
57
+ }
58
+ const line = text.split('\n').find((l) => l.trim().endsWith(file));
54
59
  const sha = line?.trim().split(/\s+/)[0];
55
60
  if (!sha || !/^[0-9a-f]{64}$/.test(sha))
56
61
  throw new DshModelError('engine_checksum_missing', L(`v${version} 的 checksums.txt 里没有 ${file}`, `${file} is missing from v${version} checksums.txt`));
@@ -60,7 +65,7 @@ export async function fetchUpstreamAsset(manifest, version, target) {
60
65
  * 安装某版本到 versions/<v>/(已装就跳过)。返回是否真的装了。
61
66
  * DSH_MODEL_ENGINE_ARCHIVE 可指向本地 tar.gz(离线 / 测试),同样要过 sha256 校验。
62
67
  */
63
- export async function installVersion(ctx, version, asset) {
68
+ export async function installVersion(ctx, version, asset, opts = {}) {
64
69
  const bin = versionBinary(ctx, version);
65
70
  if (await exists(bin))
66
71
  return false;
@@ -75,10 +80,14 @@ export async function installVersion(ctx, version, asset) {
75
80
  data = await readFile(local);
76
81
  }
77
82
  else {
78
- const res = await fetch(asset.url);
79
- if (!res.ok || !res.body)
80
- throw new DshModelError('engine_download_failed', L(`下载引擎失败:HTTP ${res.status}`, `Engine download failed: HTTP ${res.status}`), asset.url);
81
- await pipeline(Readable.fromWeb(res.body), createWriteStream(archive, { mode: 0o600 }));
83
+ try {
84
+ await downloadFile(asset.url, archive, opts);
85
+ }
86
+ catch (error) {
87
+ if (isDshModelError(error))
88
+ throw new DshModelError('engine_download_failed', L(`下载引擎失败:${error.message.replace(/^下载失败:|^Download failed: /, '')}`, `Engine download failed: ${error.message.replace(/^下载失败:|^Download failed: /, '')}`), error.hint);
89
+ throw error;
90
+ }
82
91
  data = await readFile(archive);
83
92
  }
84
93
  const actual = sha256(data);
@@ -0,0 +1,69 @@
1
+ // util/download.ts — 下载文件(引擎发布包、checksums.txt)
2
+ //
3
+ // Node 的 fetch 不走系统代理,也不认 HTTPS_PROXY。国内直连 GitHub 的下载地址经常不通,
4
+ // Windows 上 setup 只报一句 "fetch failed"(实测)。所以设了代理时改用系统自带的 curl 走代理
5
+ // (macOS、Linux 都有;Windows 10 1803 起自带 curl.exe),没有 curl 再退回 fetch。
6
+ // 下载的内容一律由调用方按清单里的 sha256 校验,走哪条路都一样可信。
7
+ import { createWriteStream } from 'node:fs';
8
+ import { readFile, rm } from 'node:fs/promises';
9
+ import { Readable } from 'node:stream';
10
+ import { pipeline } from 'node:stream/promises';
11
+ import { DshModelError } from '../errors.js';
12
+ import { L } from '../i18n.js';
13
+ import { run, which } from './exec.js';
14
+ import { redactProxy } from './proxy.js';
15
+ /** fetch 抛的错里真正的原因("fetch failed" 的 cause:ENOTFOUND、ECONNRESET、UND_ERR_CONNECT_TIMEOUT…) */
16
+ export function fetchCause(error) {
17
+ const e = error;
18
+ const cause = e?.cause?.code ?? e?.cause?.message;
19
+ return cause ? `${e.message ?? 'fetch failed'}(${cause})` : String(e?.message ?? error);
20
+ }
21
+ /** 连不上时怎么办:开代理、指定代理,或手动下载后用本地文件 */
22
+ export function downloadHint(url, proxy) {
23
+ return proxy
24
+ ? L(`经代理 ${redactProxy(proxy)} 也没下载成功:确认代理软件开着、能打开 GitHub;或换一个代理(dsh-model setup --proxy <地址>)。也可以手动下载 ${url},把环境变量 DSH_MODEL_ENGINE_ARCHIVE 设成下载好的文件路径,再执行 dsh-model setup`, `Still failed through the proxy ${redactProxy(proxy)}: check the proxy app is running and can reach GitHub, or use another one (dsh-model setup --proxy <url>). You can also download ${url} yourself, set the environment variable DSH_MODEL_ENGINE_ARCHIVE to the file's path, and run dsh-model setup again`)
25
+ : L(`连不上 GitHub(下载引擎要用)。用代理的话:打开代理软件的"系统代理",或执行 dsh-model setup --proxy http://127.0.0.1:7890(换成你的代理端口)。也可以手动下载 ${url},把环境变量 DSH_MODEL_ENGINE_ARCHIVE 设成下载好的文件路径,再执行 dsh-model setup`, `Cannot reach GitHub (needed to download the engine). If you use a proxy: turn on its "system proxy", or run dsh-model setup --proxy http://127.0.0.1:7890 (your proxy's port). You can also download ${url} yourself, set the environment variable DSH_MODEL_ENGINE_ARCHIVE to the file's path, and run dsh-model setup again`);
26
+ }
27
+ /** 下载到 dest。HTTP 404 抛 code=download_not_found,其余失败抛 download_failed(带原因和建议) */
28
+ export async function downloadFile(url, dest, opts = {}) {
29
+ const curl = opts.proxy ? await which('curl') : null;
30
+ if (opts.proxy && curl) {
31
+ // --noproxy '':只用这里指定的代理,不受环境里 NO_PROXY 的影响
32
+ const r = await run(curl, ['-fsSL', '--retry', '2', '--connect-timeout', '20', '--proxy', opts.proxy, '--noproxy', '', '-o', dest, url], { timeoutMs: 10 * 60_000 });
33
+ if (r.code === 0)
34
+ return;
35
+ await rm(dest, { force: true });
36
+ // curl -f:HTTP 4xx/5xx 退出码 22,stderr 里有 "The requested URL returned error: 404"
37
+ if (/\b404\b/.test(r.stderr))
38
+ throw new DshModelError('download_not_found', L(`没有这个文件:${url}`, `No such file: ${url}`));
39
+ throw new DshModelError('download_failed', L(`下载失败:${r.stderr.trim() || `curl 退出码 ${r.code}`}`, `Download failed: ${r.stderr.trim() || `curl exit code ${r.code}`}`), downloadHint(url, opts.proxy));
40
+ }
41
+ let res;
42
+ try {
43
+ res = await fetch(url, { signal: AbortSignal.timeout(10 * 60_000) });
44
+ }
45
+ catch (error) {
46
+ throw new DshModelError('download_failed', L(`下载失败:${fetchCause(error)}`, `Download failed: ${fetchCause(error)}`), downloadHint(url, opts.proxy));
47
+ }
48
+ if (res.status === 404)
49
+ throw new DshModelError('download_not_found', L(`没有这个文件:${url}`, `No such file: ${url}`));
50
+ if (!res.ok || !res.body)
51
+ throw new DshModelError('download_failed', L(`下载失败:HTTP ${res.status}`, `Download failed: HTTP ${res.status}`), downloadHint(url, opts.proxy));
52
+ try {
53
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(dest, { mode: 0o600 }));
54
+ }
55
+ catch (error) {
56
+ await rm(dest, { force: true });
57
+ throw new DshModelError('download_failed', L(`下载中断:${fetchCause(error)}`, `Download interrupted: ${fetchCause(error)}`), downloadHint(url, opts.proxy));
58
+ }
59
+ }
60
+ /** 下载一个小文本文件(checksums.txt) */
61
+ export async function downloadText(url, tmpFile, opts = {}) {
62
+ try {
63
+ await downloadFile(url, tmpFile, opts);
64
+ return await readFile(tmpFile, 'utf8');
65
+ }
66
+ finally {
67
+ await rm(tmpFile, { force: true });
68
+ }
69
+ }
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "dsh-model",
3
- "version": "0.23.0",
4
- "description": "One OpenAI-compatible address for all your models: subscriptions (Codex, Kimi…), free tiers (WorkBuddy, Gemini, Groq, OpenRouter), local models (Ollama, LM Studio), daily check-in credits, auto failover. Works with DeepSeek Harness (dsh) and any OpenAI-compatible tool. 一个地址用上你所有的模型。",
3
+ "version": "0.23.3",
4
+ "description": "Daily check-in credits claimed for you (Trae, Qoder, WorkBuddy China), one OpenAI-compatible address that relays every model, and free or subscription model sources (SiliconFlow, Gemini, Codex, Kimi, Ollama…). For DeepSeek Harness (dsh) and any OpenAI-compatible tool. 每天自动签到领积分 · 一个地址中转所有模型 · 免费和订阅模型随你接。",
5
5
  "keywords": [
6
+ "siliconflow",
7
+ "checkin",
6
8
  "dsh-plugin",
7
9
  "dsh-plugins",
8
10
  "dsh",
package/plugin/client.js CHANGED
@@ -462,8 +462,9 @@ window.__ModuleLoader__.load({
462
462
  try { await api('POST', `/sources/${s.id}/logout`); await reload() } catch (e) { setErr(e.message) }
463
463
  }
464
464
  // 已登录的照常列出;没登录的收进一个可折叠的列表(默认收起;一个都没登录时默认展开)
465
- const signedIn = visible.filter((s) => s.loggedIn)
466
- const rest = visible.filter((s) => !s.loggedIn)
465
+ // 顶部只放正在用的(已登录且打开);没登录的、关掉的都收进折叠列表
466
+ const signedIn = visible.filter((s) => s.loggedIn && s.enabled)
467
+ const rest = visible.filter((s) => !(s.loggedIn && s.enabled))
467
468
  const [restPref, setRestPref] = useState(readShowRest)
468
469
  const openRest = restPref ?? signedIn.length === 0
469
470
  const toggleRest = (v) => {
@@ -509,7 +510,7 @@ window.__ModuleLoader__.load({
509
510
  showRisky ? h('p', { style: { ...S.note, color: C.warn } }, L('高风险来源(Claude、Antigravity):服务商有封禁第三方使用订阅的先例,账号可能被封。仅在你清楚风险时使用。', 'High-risk sources (Claude, Antigravity): the providers have banned third-party use of subscriptions before; your account may be suspended. Use only if you accept the risk.')) : null,
510
511
  signedIn.length ? h('div', { style: S.card }, signedIn.map(row)) : h('div', { style: { ...S.note, margin: '0 0 8px' } }, L('还没有接入任何来源:展开下面的列表挑一个。', 'No source connected yet: open the list below and pick one.')),
511
512
  rest.length ? h('button', { type: 'button', style: { ...S.btn, marginTop: 10 }, 'aria-expanded': openRest, onClick: () => toggleRest(!openRest) },
512
- openRest ? L(`▾ 收起未登录的来源(${rest.length} 个)`, `▾ Hide signed-out sources (${rest.length})`) : L(`▸ 未登录的来源(${rest.length} 个)`, `▸ Signed-out sources (${rest.length})`)) : null,
513
+ openRest ? L(`▾ 收起没在用的来源(${rest.length} 个:未登录或已关闭)`, `▾ Hide sources not in use (${rest.length}: signed out or off)`) : L(`▸ 没在用的来源(${rest.length} 个:未登录或已关闭)`, `▸ Sources not in use (${rest.length}: signed out or off)`)) : null,
513
514
  openRest && rest.length ? h('div', { style: { ...S.card, marginTop: 8 } }, rest.map(row)) : null)
514
515
  }
515
516