dsh-model 0.21.2 → 0.21.4
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 +1 -1
- package/README.md +1 -1
- package/dist/commands/key.js +7 -7
- package/dist/commands/setup.js +5 -4
- package/dist/daemon/core.js +6 -5
- package/dist/ops.js +32 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -42,7 +42,7 @@ On a dsh-vps server, use `sudo dsh-model setup`. On Windows, run it in a normal
|
|
|
42
42
|
`setup` is safe to re-run. It:
|
|
43
43
|
|
|
44
44
|
1. downloads and verifies the engine and runs it on `127.0.0.1:8317/v1`, with key auth enforced;
|
|
45
|
-
2. connects
|
|
45
|
+
2. connects WorkBuddy (dsh-model's own bridge). Setup asks for no keys: turn on OpenCode Zen and other key-based sources from the management page when you need them;
|
|
46
46
|
3. installs the management page into dsh and syncs the models into dsh;
|
|
47
47
|
4. on a VPS, also opens a public address `https://<dsh's domain>:9443/v1` (or `<IP>.sslip.io` when there is no domain).
|
|
48
48
|
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ dsh-model setup
|
|
|
42
42
|
`setup` 可以重复执行,它会依次:
|
|
43
43
|
|
|
44
44
|
1. 下载并校验引擎,在本机 `127.0.0.1:8317/v1` 上运行,强制 key 鉴权;
|
|
45
|
-
2. 接入
|
|
45
|
+
2. 接入 WorkBuddy(dsh-model 自己的 bridge)。安装时不问任何 key,OpenCode Zen 等要 key 的来源,需要时在管理页里打开;
|
|
46
46
|
3. 把管理页装进 dsh,把模型同步进 dsh;
|
|
47
47
|
4. 在 VPS 上,额外开通对外地址 `https://<dsh 的域名>:9443/v1`(没有域名时用 `<IP>.sslip.io`)。
|
|
48
48
|
|
package/dist/commands/key.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -17,7 +17,7 @@ import { ensureDir } from '../util/fs.js';
|
|
|
17
17
|
import { bold, info, next, ok, skip, warn } from '../util/output.js';
|
|
18
18
|
import { findFreePort, isPortFree } from '../util/port.js';
|
|
19
19
|
import { detectProxy, normalizeProxyUrl, redactProxy } from '../util/proxy.js';
|
|
20
|
-
import {
|
|
20
|
+
import { configureOpencode } from './opencode.js';
|
|
21
21
|
import { opencodeConfigured } from '../integrations/opencode.js';
|
|
22
22
|
import { ensureDaemon } from '../daemon/service.js';
|
|
23
23
|
import { installDshPlugin } from '../integrations/dshplugin.js';
|
|
@@ -26,8 +26,6 @@ import { installAdmin } from '../service/admin.js';
|
|
|
26
26
|
import { DEFAULT_PUBLIC_PORT, publicHost } from '../remote/public.js';
|
|
27
27
|
export async function setup(ctx, opts) {
|
|
28
28
|
requireRootInVps(ctx);
|
|
29
|
-
// 要问的先问完再拿锁:等你输入时不占着锁,dsh 管理页照常能用(实测:卡在问 key 时页面新增 key 报"另一个命令正在运行")
|
|
30
|
-
const opencodeKey = opts.skipOpencode || (await opencodeConfigured(ctx)) ? undefined : await askOpencodeKey();
|
|
31
29
|
return withLock(ctx, async () => {
|
|
32
30
|
const all = await loadAll(ctx);
|
|
33
31
|
await ensureDir(ctx.paths.home, { owner: ctx.owner });
|
|
@@ -75,8 +73,11 @@ export async function setup(ctx, opts) {
|
|
|
75
73
|
info(bold('OpenCode Zen'));
|
|
76
74
|
if (opts.skipOpencode)
|
|
77
75
|
skip(L('已跳过(--skip-opencode)', 'Skipped (--skip-opencode)'));
|
|
76
|
+
// 安装时不问 key:要用的话在 dsh「设置 → dsh-model → 来源」里填,或执行 dsh-model opencode key
|
|
77
|
+
else if (await opencodeConfigured(ctx))
|
|
78
|
+
await step('opencode', () => configureOpencode(ctx, all));
|
|
78
79
|
else
|
|
79
|
-
|
|
80
|
+
skip(L('没填 key,跳过;需要时在 dsh「设置 → dsh-model → 来源」里打开它(或执行 dsh-model opencode key)', 'No key, skipped; when you need it, turn it on in dsh Settings → dsh-model → Sources (or run dsh-model opencode key)'));
|
|
80
81
|
info('');
|
|
81
82
|
info(bold('WorkBuddy'));
|
|
82
83
|
if (opts.skipWorkbuddy)
|
package/dist/daemon/core.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.4",
|
|
4
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 和其他软件都能直接用。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh-plugin",
|