dsh-model 0.21.3 → 0.21.5

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,7 +2,9 @@
2
2
 
3
3
  # dsh-model
4
4
 
5
- **Many model sources behind one OpenAI-compatible endpoint — for dsh and every other tool you use.**
5
+ **One address for all your models: subscriptions, free tiers and local models, with daily check-in credits claimed for you.**
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.
6
8
 
7
9
  <p>
8
10
  <a href="LICENSE"><img src="https://img.shields.io/github/license/AIcivilization/dsh-model" alt="MIT license"></a>
@@ -17,18 +19,22 @@ English · [中文](README.md)
17
19
 
18
20
  </div>
19
21
 
20
- ## What it is
22
+ ## Why
21
23
 
22
- You may have several model sources at once: free WorkBuddy credits, an OpenCode Zen key, paid Codex or Kimi subscriptions. Each has its own API and sign-in, and every tool needs its own setup.
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.
23
25
 
24
26
  dsh-model turns them into **one address and one kind of key**:
25
27
 
26
- - **In dsh**: the models show up in dsh's model list automatically, grouped by source. Nothing to configure.
27
- - **Everywhere else**: any editor or script that speaks the OpenAI API uses the same address and key to reach every model.
28
- - **Low maintenance**: one command to install; a key is always required; uninstall restores dsh's config exactly.
29
- - **On a server**: runs next to [dsh-vps](https://github.com/AIcivilization/dsh-vps) on the same VPS and opens a public address on dsh's own domain, so your laptop and phone can use it too.
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**: WorkBuddy credits, free Gemini / Groq / OpenRouter keys and local models; 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**: when a model is offered by several sources, requests move to another one when the first is limited.
32
+ - 📊 **Know where you stand**: plan usage, remaining credits, 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.
30
36
 
31
- > A personal tool: one person, one account per provider, not a public service.
37
+ No dsh? It still works: the command line manages everything; dsh just adds a management page.
32
38
 
33
39
  ## Quick start
34
40
 
@@ -54,6 +60,7 @@ On a dsh-vps server, use `sudo dsh-model setup`. On Windows, run it in a normal
54
60
  | **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 or remaining credits |
55
61
  | **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 |
56
62
  | **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 |
63
+ | **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 |
57
64
  | **Recent failures** | Failed requests in the last 24 hours: which model, why, and what to do (no conversation content kept) |
58
65
  | **API keys** | OpenAI-style, one per device. Requests, success rate, latency and speed over 24 hours; copy, rotate, revoke |
59
66
  | **Model stats** | Success rate, latency and speed per model |
@@ -98,6 +105,7 @@ dsh-model only connects; it provides no quota of its own.
98
105
  | `sources` | Sources: switch, cost, sign-in state, usage |
99
106
  | `source enable\|disable\|logout <source>` | Turn on (signing in if needed) / off / sign out |
100
107
  | `key list\|add\|revoke\|rotate <name>` | Access keys, one per device |
108
+ | `checkin` | Check in now (WorkBuddy China, Qoder, Trae; automatic daily by default) |
101
109
  | `failures` | Failed requests in the last 24 hours, with advice |
102
110
  | `stats` | Success rate, latency and speed by key, source and model |
103
111
  | `status` / `doctor [--e2e]` | Overview / health checks |
@@ -127,6 +135,7 @@ See the [design doc](docs/DESIGN.md) (Chinese).
127
135
 
128
136
  ## Notes
129
137
 
138
+ - Meant for personal use: one account of your own per provider; do not use it to serve others.
130
139
  - Using subscription and WorkBuddy credentials outside the official clients may break the providers' terms and put the account at risk. Decide for yourself.
131
140
  - Windows 10 / 11 (x64, ARM64), macOS and Linux, Node ≥ 20.
132
141
  - 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`.
package/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  # dsh-model
4
4
 
5
- **把多家模型汇成一个 OpenAI 兼容地址,dsh 和你的其他软件都能直接用。**
5
+ **一个地址,用上你所有的模型:订阅、免费额度、本机模型,签到积分自动领。**
6
+
7
+ 一个 OpenAI 兼容地址,一把 key,几十个模型。dsh、Cline、Continue、Cherry Studio、脚本……凡是支持 OpenAI 接口的软件都能直接用。
6
8
 
7
9
  <p>
8
10
  <a href="LICENSE"><img src="https://img.shields.io/github/license/AIcivilization/dsh-model" alt="MIT license"></a>
@@ -17,18 +19,22 @@
17
19
 
18
20
  </div>
19
21
 
20
- ## 它是什么
22
+ ## 为什么用它
21
23
 
22
- 你可能同时有好几个模型来源:WorkBuddy 的免费积分、OpenCode Zen 的 key,还有已经付费的 Codex、Kimi 订阅。它们的接口和登录方式各不相同,每个软件都要分别配置。
24
+ 你手上可能有好几样:ChatGPT Plus 带的 Codex、Kimi 的编程套餐、WorkBuddy 的免费积分、Gemini 和 Groq 的免费 key,还有本机的 Ollama。每家接口和登录方式都不一样,每个软件都得分别配置,额度用完了还要手动换。
23
25
 
24
26
  dsh-model 把它们合成**一个地址、一种 key**:
25
27
 
26
- - **dsh 里**:装好后自动出现在 dsh 的模型列表里,按来源分组,不用手动配置。
27
- - **其他软件**:编辑器、脚本,凡是支持 OpenAI 接口的,填上同一个地址和 key 就能用全部模型。
28
- - **省心**:一条命令装好;始终要求 key,不会出现谁都能用的情况;卸载时把 dsh 的配置原样还原。
29
- - **服务器上**:可以和 [dsh-vps](https://github.com/AIcivilization/dsh-vps) 装在同一台 VPS 上,自动用 dsh 的域名开一个对外地址,你的电脑、手机都能访问。
28
+ - 🔌 **一处接入,处处可用**:dsh 里自动出现全部模型,按来源分组;其他软件填上同一个地址和 key 就行。
29
+ - 🆓 **把免费的用足**:WorkBuddy 积分、Gemini / Groq / OpenRouter 免费 key、本机模型都能接;每个来源标出「免费 / 免费·额度少 / 需付费」。
30
+ - 📅 **每日签到,自动领积分**:WorkBuddy 国内版、Qoder、Trae 每天自动签到,不用记。
31
+ - 🔁 **不怕限流**:同一个模型在几个来源都有时自动切换,一个限流就换另一个。
32
+ - 📊 **心里有数**:套餐用量、剩余积分、每个模型的成功率和速度、最近失败的原因和建议;额度快用完时提醒。
33
+ - 🔒 **默认安全**:始终要求 key,每台设备一把,可随时吊销;登录全在本机完成,不经过第三方。
34
+ - 🖥️ **电脑和服务器都行**:Windows、macOS、Linux;装在 VPS 上(配合 [dsh-vps](https://github.com/AIcivilization/dsh-vps))自动开一个 https 地址,电脑、手机随处用。
35
+ - 🧹 **装得上,卸得净**:一条命令安装;卸载时把 dsh 的配置逐字节还原。
30
36
 
31
- > 个人自用工具:一个人、每家一个账号,不对外提供服务。
37
+ 没有 dsh 也能用:命令行就能管理全部功能,dsh 里多一个图形管理页而已。
32
38
 
33
39
  ## 快速开始
34
40
 
@@ -54,6 +60,7 @@ dsh-model setup
54
60
  | **来源** | 每家一个开关:打开就接入(没登录会弹出登录框),关闭只停用、保留登录。每行标出「免费 / 免费·额度少 / 需付费」,下面显示套餐用量或剩余积分 |
55
61
  | **选模型** | 每个来源选哪些模型显示在 dsh 里,默认每家推荐 5 个,其余的经统一地址照样可用;每个模型旁边有「测一下」 |
56
62
  | **自动切换** | 同一个模型在几个来源都有时,dsh 里多一组「自动切换(多来源)」,一个来源限流或出错就自动换另一个 |
63
+ | **每日签到** | WorkBuddy 国内版、Qoder、Trae 每天自动签到(也可一键签到),卡片显示今天领了多少 |
57
64
  | **最近失败** | 最近 24 小时失败的请求:哪个模型、为什么、该怎么办(不记录对话内容) |
58
65
  | **API key** | OpenAI 格式,每台设备一把。显示 24 小时内的请求数、成功率、延迟和速度,可复制、轮换、吊销 |
59
66
  | **模型统计** | 按模型显示成功率、延迟和速度 |
@@ -98,6 +105,7 @@ dsh-model 只负责转接,不提供额度。
98
105
  | `sources` | 来源列表:开关、费用、登录状态、用量 |
99
106
  | `source enable\|disable\|logout <来源>` | 打开(需要时登录)/ 关闭 / 退出登录 |
100
107
  | `key list\|add\|revoke\|rotate <名称>` | 访问 key,每台设备一把 |
108
+ | `checkin` | 现在签到一次(WorkBuddy 国内版、Qoder、Trae;默认每天自动签) |
101
109
  | `failures` | 最近 24 小时失败的请求和建议 |
102
110
  | `stats` | 按 key、来源、模型的成功率、延迟和速度 |
103
111
  | `status` / `doctor [--e2e]` | 总览 / 逐项自检 |
@@ -127,6 +135,7 @@ dsh · 编辑器 · 脚本 ──Bearer key──▶ 引擎 127.0.0.1:8317/v1(
127
135
 
128
136
  ## 注意
129
137
 
138
+ - 适合个人使用:每家用自己的一个账号,不要拿来对外提供服务。
130
139
  - 订阅和 WorkBuddy 的凭据在官方客户端之外使用,可能不符合服务商的条款,账号有风险,请自行判断。
131
140
  - 支持 Windows 10 / 11(x64、ARM64)、macOS、Linux,需要 Node ≥ 20。
132
141
  - Windows:后台服务登录后自动运行、不弹窗口。如果被杀毒软件拦下(程序不见了或启动不了),把 `%USERPROFILE%\.dsh-model` 加入信任,再执行 `dsh-model repair`。
package/dist/cli.js CHANGED
@@ -69,7 +69,7 @@ function help() {
69
69
  login <来源> / logout <来源> 同 source enable / source logout
70
70
  stats 按 key / 来源 / 模型的成功率、延迟、速度
71
71
  failures 最近 24 小时失败的请求,以及该怎么办
72
- checkin 现在签到一次(WorkBuddy 国内版、Qoder;默认每天自动签)
72
+ checkin 现在签到一次(WorkBuddy 国内版、Qoder、Trae;默认每天自动签)
73
73
  status 一屏总览
74
74
  doctor [--e2e] [--all] [--model M] 逐项自检;--e2e 实测流式与工具调用
75
75
  connect-dsh [--dry-run] [--force] 写入 / 刷新 dsh 配置
@@ -102,7 +102,7 @@ Usage: dsh-model <command> [options]
102
102
  login <source> / logout <source> Same as source enable / source logout
103
103
  stats Success rate, latency and speed by key / source / model
104
104
  failures Failed requests in the last 24 hours, and what to do
105
- checkin Check in now (WorkBuddy China, Qoder; automatic daily by default)
105
+ checkin Check in now (WorkBuddy China, Qoder, Trae; automatic daily by default)
106
106
  status Overview
107
107
  doctor [--e2e] [--all] [--model M] Health checks; --e2e tests streaming and tool calls
108
108
  connect-dsh [--dry-run] [--force] Write / refresh dsh config
@@ -2,8 +2,8 @@
2
2
  import { requireRootInVps } from '../context.js';
3
3
  import { DshModelError } from '../errors.js';
4
4
  import { L } from '../i18n.js';
5
- import { DSH_KEY_NAME, addKey, revokeKey, rotateKey, saveKeys } from '../keys.js';
6
- import { applyEngineConfig, dshKey, loadAll, syncModels } from '../ops.js';
5
+ import { DSH_KEY_NAME, addKey, findActive, revokeKey, rotateKey, saveKeys } from '../keys.js';
6
+ import { applyKeyChange, dshKey, loadAll, syncModels } from '../ops.js';
7
7
  import { withLock } from '../state.js';
8
8
  import { baseUrl } from '../engine/client.js';
9
9
  import { bold, info, isJsonMode, ok, printJson, table, warn } from '../util/output.js';
@@ -20,7 +20,7 @@ export async function key(ctx, sub, name) {
20
20
  if (sub === 'add') {
21
21
  const entry = addKey(all.keys, name);
22
22
  await saveKeys(ctx, all.keys);
23
- await applyEngineConfig(ctx, all);
23
+ await applyKeyChange(ctx, all, { accepts: entry.key });
24
24
  if (isJsonMode())
25
25
  printJson({ ok: true, name, key: entry.key, baseURL: `${baseUrl(all.config.port)}/v1` });
26
26
  ok(L(`已新增 key:${name}(只显示这一次,请妥善保存)`, `Key added: ${name} (shown only once; keep it safe)`));
@@ -32,19 +32,19 @@ export async function key(ctx, sub, name) {
32
32
  if (name === DSH_KEY_NAME) {
33
33
  throw new DshModelError('cannot_revoke_dsh', L('dsh 用的 key 不能吊销,只能轮换', 'The dsh key cannot be revoked, only rotated'), L('dsh-model key rotate dsh', 'dsh-model key rotate dsh'));
34
34
  }
35
+ const old = findActive(all.keys, name)?.key;
35
36
  revokeKey(all.keys, name);
36
37
  await saveKeys(ctx, all.keys);
37
- await applyEngineConfig(ctx, all);
38
+ await applyKeyChange(ctx, all, { rejects: old });
38
39
  ok(L(`已吊销 key:${name}`, `Key revoked: ${name}`));
39
40
  return 0;
40
41
  }
41
42
  if (sub === 'rotate') {
42
43
  const entry = rotateKey(all.keys, name);
43
44
  await saveKeys(ctx, all.keys);
44
- await applyEngineConfig(ctx, all);
45
+ // 等新 key 在引擎里生效后再改 dsh
46
+ await applyKeyChange(ctx, all, { accepts: entry.key });
45
47
  if (name === DSH_KEY_NAME) {
46
- // 引擎热重载约 1s:等新 key 生效后再改 dsh
47
- await new Promise((r) => setTimeout(r, 1500));
48
48
  await syncModels(ctx, all, { quiet: true });
49
49
  ok(L('已轮换 dsh 用的 key,并同步到 dsh', 'Rotated the dsh key and updated dsh'));
50
50
  }
@@ -31,8 +31,8 @@ import { DshModelError, isDshModelError } from '../errors.js';
31
31
  import { L } from '../i18n.js';
32
32
  import { refreshOpencodeModels, removeOpencodeKey, saveOpencodeKey, validateKeyShape, verifyOpencodeKey } from '../integrations/opencode.js';
33
33
  import { riskNotice as workbuddyRiskNotice } from '../integrations/workbuddy.js';
34
- import { DSH_KEY_NAME, addKey, loadKeys, revokeKey, rotateKey, saveKeys } from '../keys.js';
35
- import { applyEngineConfig, dshCandidates, dshKey, loadAll, saveAll, syncAll } from '../ops.js';
34
+ import { DSH_KEY_NAME, addKey, findActive, loadKeys, revokeKey, rotateKey, saveKeys } from '../keys.js';
35
+ import { applyKeyChange, dshCandidates, dshKey, loadAll, saveAll, syncAll } from '../ops.js';
36
36
  import { defaultPick, pickedFor } from '../dsh/pick.js';
37
37
  import { loadSecrets, saveSecrets } from '../secrets.js';
38
38
  import { SOURCES, credsFor, findSource, paymentRequired } from '../sources.js';
@@ -929,7 +929,7 @@ export class Daemon {
929
929
  const all = await loadAll(this.ctx);
930
930
  const entry = addKey(all.keys, name);
931
931
  await saveKeys(this.ctx, all.keys);
932
- await applyEngineConfig(this.ctx, all);
932
+ await applyKeyChange(this.ctx, all, { accepts: entry.key });
933
933
  await this.reloadKeyNames();
934
934
  return { name: entry.name, key: entry.key };
935
935
  });
@@ -939,9 +939,10 @@ export class Daemon {
939
939
  throw new DshModelError('cannot_revoke_dsh', L('dsh 用的 key 不能吊销,只能轮换', 'The dsh key cannot be revoked, only rotated'));
940
940
  await withLock(this.ctx, async () => {
941
941
  const all = await loadAll(this.ctx);
942
+ const old = findActive(all.keys, name)?.key;
942
943
  revokeKey(all.keys, name);
943
944
  await saveKeys(this.ctx, all.keys);
944
- await applyEngineConfig(this.ctx, all);
945
+ await applyKeyChange(this.ctx, all, { rejects: old });
945
946
  });
946
947
  await this.reloadKeyNames();
947
948
  }
@@ -950,7 +951,7 @@ export class Daemon {
950
951
  const all = await loadAll(this.ctx);
951
952
  const e = rotateKey(all.keys, name);
952
953
  await saveKeys(this.ctx, all.keys);
953
- await applyEngineConfig(this.ctx, all);
954
+ await applyKeyChange(this.ctx, all, { accepts: e.key });
954
955
  return e;
955
956
  });
956
957
  await this.reloadKeyNames();
package/dist/ops.js CHANGED
@@ -12,6 +12,7 @@ import { DSH_KEY_NAME, findActive, loadKeys } from './keys.js';
12
12
  import { serviceFor } from './service/index.js';
13
13
  import { loadConfig, loadState, saveConfig, saveState } from './state.js';
14
14
  import { ok, skip, warn } from './util/output.js';
15
+ import { readText, writeInPlace } from './util/fs.js';
15
16
  export async function loadAll(ctx) {
16
17
  const [config, state, keys] = await Promise.all([loadConfig(ctx), loadState(ctx), loadKeys(ctx)]);
17
18
  return { config, state, keys };
@@ -34,6 +35,37 @@ export function dshKey(keys) {
34
35
  export async function applyEngineConfig(ctx, all) {
35
36
  return writeEngineConfig(ctx, all.config, all.keys);
36
37
  }
38
+ /**
39
+ * 改 key 后确认引擎真的换上了:热重载偶尔会漏掉一次变化(CI 上 macOS、Windows 都见过等 15 秒仍没生效)。
40
+ * 没生效就原样再写一次配置文件,还不行就重启引擎。引擎没在运行时只写文件。
41
+ */
42
+ export async function applyKeyChange(ctx, all, probe) {
43
+ await writeEngineConfig(ctx, all.config, all.keys);
44
+ const port = all.config.port;
45
+ if (!(await waitHealthy(port, 1000)))
46
+ return;
47
+ const works = (key) => listModels(port, key).then(() => true, () => false);
48
+ const effective = async () => (!probe.accepts || (await works(probe.accepts))) && (!probe.rejects || !(await works(probe.rejects)));
49
+ const wait = async (ms) => {
50
+ const deadline = Date.now() + ms;
51
+ while (Date.now() < deadline) {
52
+ if (await effective())
53
+ return true;
54
+ await new Promise((r) => setTimeout(r, 250));
55
+ }
56
+ return effective();
57
+ };
58
+ if (await wait(5000))
59
+ return;
60
+ const text = await readText(ctx.paths.engineYaml);
61
+ if (text !== null)
62
+ await writeInPlace(ctx.paths.engineYaml, text, { owner: ctx.owner });
63
+ if (await wait(5000))
64
+ return;
65
+ if ((await restartEngineBestEffort(ctx, all)) && (await wait(5000)))
66
+ return;
67
+ warn(L('引擎还没换上这次的 key 改动;稍后执行 dsh-model repair', 'The engine has not picked up this key change yet; run dsh-model repair later'));
68
+ }
37
69
  /**
38
70
  * 兜底重启:热重载没生效时用。vps 模式下 bridge 以 dsh 用户运行,没有权限重启系统服务——
39
71
  * 这时只记警告不失败(实测:bridge 写完配置后 systemctl restart 被拒)。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-model",
3
- "version": "0.21.3",
4
- "description": "Many model sources behind one authenticated OpenAI-compatible endpoint for DeepSeek Harness (dsh) and your tools: WorkBuddy, OpenCode Zen and your CLI subscriptions (Codex, Kimi…), with a management page inside dsh. 多家模型汇成一个 OpenAI 兼容地址,dsh 和其他软件都能直接用。",
3
+ "version": "0.21.5",
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. 一个地址用上你所有的模型。",
5
5
  "keywords": [
6
6
  "dsh-plugin",
7
7
  "dsh-plugins",
@@ -21,7 +21,20 @@
21
21
  "opencode",
22
22
  "cliproxyapi",
23
23
  "self-hosted",
24
- "llm-tools"
24
+ "llm-tools",
25
+ "ai-gateway",
26
+ "llm-router",
27
+ "openai-proxy",
28
+ "free-llm",
29
+ "gemini",
30
+ "groq",
31
+ "openrouter",
32
+ "ollama",
33
+ "lm-studio",
34
+ "qoder",
35
+ "trae",
36
+ "daily-checkin",
37
+ "failover"
25
38
  ],
26
39
  "license": "MIT",
27
40
  "type": "module",