dsh-host-router 0.1.0

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.md ADDED
@@ -0,0 +1,67 @@
1
+ # dsh-host-router
2
+
3
+ dsh 外挂式网络路由插件:按 **hostname** 给 dsh 的请求分流——勾选的域名走本地代理(Clash 等),其余直连。内置嗅探,自动记录 dsh 访问过的 hostname,设置页勾选即生效。
4
+
5
+ ## 跟 dsh-llm-proxy 的区别
6
+
7
+ | | dsh-llm-proxy | dsh-host-router |
8
+ |---|---|---|
9
+ | 路由维度 | 按模型(解出 baseURL host) | 按 hostname 通用 |
10
+ | 覆盖范围 | 仅 LLM 请求 | LLM + 市场目录 + 截图源 + 任意 dsh 网络请求 |
11
+ | 配置 | dsh Config(live) | 独立 config.json(重启生效) |
12
+ | 扫描清单 | 无 | 嗅探 + 主动扫(读 settings.yaml provider) |
13
+
14
+ ## 安装
15
+
16
+ ```sh
17
+ dsh plugin --profile web add D:/ai/dsh/dsh-host-router
18
+ ```
19
+ 重启 `dsh web`(托盘退出 → 启动)。
20
+
21
+ ## 使用
22
+
23
+ 1. 设置页 → 「网络路由」(settings.section 独立页)。
24
+ 2. 点「扫描配置」:读 dsh 已装 provider(`~/.dsh/settings.yaml` 的 baseURL)+ 常见境外兜底,批量直连探测,标出"直连不通→建议代理"。
25
+ 3. 勾选要走代理的域名 → 「保存勾选」写回 `~/.dsh/host-router/config.json`。
26
+ 4. 「保存勾选」即**即时生效**(live reload,免重启;只有装/卸插件才需要重启 dsh)。
27
+ 5. 「刷新」看嗅探实时清单(dsh 用一会儿后,实际访问过的 host 带 状态/耗时/次数)。
28
+
29
+ ## 配置
30
+
31
+ `~/.dsh/host-router/config.json`:
32
+
33
+ ```json
34
+ {
35
+ "proxyHost": "127.0.0.1",
36
+ "proxyPort": 7897,
37
+ "proxiedHosts": ["api.openai.com", "awesome-dsh-plugin.com"],
38
+ "sniff": true
39
+ }
40
+ ```
41
+
42
+ `proxiedHosts` 命中(含子域后缀 `xxx.example.com` 命中 `example.com`)走代理;loopback 永远直连。
43
+
44
+ ## 构建
45
+
46
+ ```sh
47
+ node scripts/build.mjs
48
+ ```
49
+
50
+ 跨平台(Windows/macOS/Linux)。工具链定位:优先本机 `node_modules`(标准 `npm install` 流程);本开发机故意不装 devDeps,自动回退复用 `D:/ai/dsh/dsh/dsh-capture/node_modules` 借来的 tsc/tsdown(host tsc + client tsdown factory bundle)。可用 `DSH_CAPTURE_NODE_MODULES` 指定工具链目录。`npm publish` 前会自动执行本脚本(prepublishOnly)。
51
+
52
+ ## 机制
53
+
54
+ - `globalThis.fetch` 替换(保留原引用,dispose 时还原):白名单 host → undici fetch + ProxyAgent(Clash 等);其余 → 原内置 fetch 原样直连;loopback 永远直连。
55
+ - 嗅探在 myFetch 内联:每次请求按 host 聚合(状态/耗时/次数,同步落 `~/.dsh/host-router/log.txt`),喂设置页清单。
56
+ - ProxyAgent `clientFactory pipelining:0`:避 Clash 关 idle 隧道复用死连接。
57
+ - 保存即热生效:`/save` 写完 config.json 立刻重建 myFetch(毫秒级,免重启)。
58
+ - 设置页:`settings.section` slot + React(纯 createElement),host `webServer.register` 前缀路由 `/api/dsh-host-router/*`(loopback-only 鉴权)。
59
+ - 纯外挂:`dependencies: {}`,运行时零 require,宿主 ctx 注入;不碰 settings.yaml provider 配置。
60
+
61
+ ## 为什么用独立 config.json(B 方案)
62
+
63
+ 配置走独立 `~/.dsh/host-router/config.json`,而非 dsh 的 schemastery Config(live)。收益:省掉 settings binder(~460 行,绑 dsh 内部 API),稳、轻、跟 dsh 版本解耦。代价只是「live 重载为整体重建 myFetch」——而 `/save` 已经做到保存即生效,免重启。
64
+
65
+ ## License
66
+
67
+ MIT
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: dsh-host-router
3
+ name: 'dsh-host-router'
@@ -0,0 +1,203 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-host-router",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region rolldown:runtime
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
16
+ key = keys[i];
17
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
18
+ get: ((k) => from[k]).bind(null, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+
29
+ //#endregion
30
+ const react = __toESM(require("react"));
31
+
32
+ //#region src/client/index.ts
33
+ const inject = ["slots"];
34
+ const API = "/api/dsh-host-router";
35
+ const STYLES = `
36
+ .hr-page{font-family:ui-monospace,monospace;font-size:13px;line-height:1.55;padding:10px 14px;max-width:900px}
37
+ .hr-page h3{margin:0 0 4px;font-size:14px}
38
+ .hr-ep{color:var(--theme-text-secondary,#999);margin:0 0 6px;font-size:12px}
39
+ .hr-hint{color:var(--theme-text-tertiary,#777);font-size:12px;margin:0 0 10px}
40
+ .hr-bar{display:flex;gap:6px;align-items:center;margin-bottom:8px;flex-wrap:wrap}
41
+ .hr-btn{background:var(--theme-accent,#4a9eff);color:#fff;border:none;border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px;white-space:nowrap}
42
+ .hr-btn.ghost{background:transparent;border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc)}
43
+ .hr-btn:disabled{opacity:.45;cursor:not-allowed}
44
+ .hr-msg{color:var(--theme-text-secondary,#999);font-size:12px;padding:5px 8px;border-radius:6px;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#333);white-space:pre-wrap;max-height:90px;overflow:auto;margin:6px 0}
45
+ .table-wrap{overflow:auto;max-height:55vh;margin-bottom:10px}
46
+ table{border-collapse:separate;border-spacing:0;width:auto;table-layout:fixed}
47
+ th,td{border-bottom:1px solid var(--theme-border,#333);padding:3px 6px;text-align:left;font-size:12px;white-space:nowrap}
48
+ td{overflow:hidden;text-overflow:ellipsis}
49
+ th{font-size:11px;color:var(--theme-text-secondary,#999);font-weight:600}
50
+ thead th{position:sticky;top:0;background:var(--theme-bg,#1b1b1c);z-index:1}
51
+ td.c,th.c{text-align:center}
52
+ tr.stale-row{opacity:.5}
53
+ .st-ok{color:#2ecc71}.st-fail{color:#e74c3c}.st-gray{color:#888}
54
+ .empty{padding:12px;text-align:center;color:var(--theme-text-secondary,#888)}
55
+ .hr-tip{position:fixed;z-index:1000;pointer-events:none;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc);padding:4px 8px;border-radius:5px;font-size:12px;max-width:440px;white-space:normal;word-break:break-all;box-shadow:0 2px 8px rgba(0,0,0,.35)}
56
+ .h4{margin:14px 0 4px;font-size:12px;color:var(--theme-text-secondary,#aaa)}
57
+ `;
58
+ async function jget(path) {
59
+ return fetch(API + path).then((r) => r.json()).catch(() => null);
60
+ }
61
+ async function jpost(path, body) {
62
+ return fetch(API + path, {
63
+ method: "POST",
64
+ headers: { "content-type": "application/json" },
65
+ body: body ? JSON.stringify(body) : void 0
66
+ }).then((r) => r.json()).catch(() => null);
67
+ }
68
+ function HostRouterPage() {
69
+ const [cfg, setCfg] = react.default.useState({});
70
+ const [draft, setDraft] = react.default.useState(/* @__PURE__ */ new Set());
71
+ const [seen, setSeen] = react.default.useState([]);
72
+ const [msg, setMsg] = react.default.useState("");
73
+ const [busy, setBusy] = react.default.useState(false);
74
+ const [tip, setTip] = react.default.useState(null);
75
+ const showTip = (ev, text) => setTip({
76
+ x: Math.min(ev.clientX + 12, window.innerWidth - 452),
77
+ y: ev.clientY + 16,
78
+ text
79
+ });
80
+ const hideTip = () => setTip(null);
81
+ const refreshData = react.default.useCallback(async () => {
82
+ const [seenR, cfgR] = await Promise.all([jget("/seen"), jget("/config")]);
83
+ if (cfgR?.ok?.value) setCfg(cfgR.value);
84
+ setSeen(seenR?.ok ? seenR.hosts : []);
85
+ }, []);
86
+ const refreshAll = react.default.useCallback(async () => {
87
+ const [seenR, cfgR] = await Promise.all([jget("/seen"), jget("/config")]);
88
+ if (cfgR?.ok?.value) {
89
+ setCfg(cfgR.value);
90
+ setDraft(new Set(cfgR.value.proxiedHosts ?? []));
91
+ }
92
+ setSeen(seenR?.ok ? seenR.hosts : []);
93
+ }, []);
94
+ react.default.useEffect(() => {
95
+ refreshAll();
96
+ const t = window.setInterval(refreshData, 5e3);
97
+ return () => window.clearInterval(t);
98
+ }, [refreshAll, refreshData]);
99
+ const toggle = (h) => setDraft((d) => {
100
+ const n = new Set(d);
101
+ n.has(h) ? n.delete(h) : n.add(h);
102
+ return n;
103
+ });
104
+ const doScan = async () => {
105
+ setMsg("扫描中…");
106
+ setBusy(true);
107
+ const r = await jpost("/scan");
108
+ setBusy(false);
109
+ if (!r?.ok) {
110
+ setMsg("扫描失败");
111
+ return;
112
+ }
113
+ setMsg(`扫描完成,${r.hosts.filter((h) => h.needsProxy).length} 个直连不通(建议代理)`);
114
+ await refreshData();
115
+ };
116
+ const doSave = async () => {
117
+ setBusy(true);
118
+ setMsg("保存中…");
119
+ const r = await jpost("/save", { proxiedHosts: [...draft] });
120
+ setBusy(false);
121
+ setMsg(r?.ok ? "已保存并即时生效" : "保存失败");
122
+ if (r?.ok) refreshData();
123
+ };
124
+ const doTest = async (h) => {
125
+ const r = await jpost("/test", { host: h });
126
+ setMsg(`${h}: ${r.status} · ${r.ms}ms · ${r.proxy ? "代理" : "直连"}` + (r.message ? ` · ${r.message}` : ""));
127
+ };
128
+ const th = (txt, c = true) => react.default.createElement("th", {
129
+ className: c ? "c" : "",
130
+ key: txt
131
+ }, txt);
132
+ const endpoint = `${cfg.proxyHost ?? "127.0.0.1"}:${cfg.proxyPort ?? 7897}`;
133
+ const doCleanStale = async () => {
134
+ setBusy(true);
135
+ setMsg("清理过期…");
136
+ const r = await jpost("/clean-stale");
137
+ setBusy(false);
138
+ setMsg(r?.ok ? `已清理 ${r.removed?.length ?? 0} 个过期 host:${(r.removed ?? []).join(", ") || "无"}` : "清理失败");
139
+ if (r?.ok) refreshAll();
140
+ };
141
+ return react.default.createElement("div", { className: "hr-page" }, react.default.createElement("style", null, STYLES), react.default.createElement("h3", null, "网络路由(dsh-host-router)"), react.default.createElement("p", { className: "hr-ep" }, "代理: ", react.default.createElement("code", null, endpoint)), react.default.createElement("p", { className: "hr-hint" }, "勾选走代理,保存即生效。绿=本次扫到在用,灰=本次没扫到(几次重启都灰可删),过期=白名单里但久未用。"), react.default.createElement("div", { className: "hr-bar" }, react.default.createElement("button", {
142
+ className: "hr-btn",
143
+ onClick: doScan,
144
+ disabled: busy
145
+ }, "扫描配置"), react.default.createElement("button", {
146
+ className: "hr-btn ghost",
147
+ onClick: doSave,
148
+ disabled: busy
149
+ }, "保存勾选"), react.default.createElement("button", {
150
+ className: "hr-btn ghost",
151
+ onClick: doCleanStale,
152
+ disabled: busy
153
+ }, "清理过期"), react.default.createElement("button", {
154
+ className: "hr-btn ghost",
155
+ onClick: refreshData
156
+ }, "刷新")), msg && react.default.createElement("div", { className: "hr-msg" }, msg), react.default.createElement("table", null, react.default.createElement("colgroup", null, react.default.createElement("col", { style: { width: "200px" } }), react.default.createElement("col", { style: { width: "44px" } }), react.default.createElement("col", { style: { width: "64px" } }), react.default.createElement("col", { style: { width: "68px" } }), react.default.createElement("col", { style: { width: "56px" } }), react.default.createElement("col", { style: { width: "76px" } }), react.default.createElement("col", { style: { width: "44px" } })), react.default.createElement("thead", null, react.default.createElement("tr", null, th("hostname", false), th("代理"), th("归属"), th("本次命中"), th("状态"), th("耗时"), th("测"))), react.default.createElement("tbody", null, seen.length === 0 ? react.default.createElement("tr", null, react.default.createElement("td", {
157
+ className: "empty",
158
+ colSpan: 5
159
+ }, "暂无数据,先用 dsh 一会儿或点「扫描配置」")) : seen.map((h) => {
160
+ const alive = h.alive !== false && (h.count ?? 0) > 0;
161
+ return react.default.createElement("tr", {
162
+ key: h.host,
163
+ className: h.stale ? "stale-row" : void 0
164
+ }, react.default.createElement("td", {
165
+ onMouseEnter: (e) => showTip(e, h.host),
166
+ onMouseLeave: hideTip
167
+ }, h.host), react.default.createElement("td", { className: "c" }, react.default.createElement("input", {
168
+ type: "checkbox",
169
+ checked: draft.has(h.host),
170
+ onChange: () => toggle(h.host)
171
+ })), react.default.createElement("td", {
172
+ className: "c",
173
+ onMouseEnter: (e) => showTip(e, `${h.owner ?? "未知"} · ${h.source ?? "-"}`),
174
+ onMouseLeave: hideTip
175
+ }, h.owner ?? "未知"), react.default.createElement("td", { className: "c" }, react.default.createElement("span", { className: alive ? "st-ok" : "st-gray" }, alive ? `●${h.count}` : "○")), react.default.createElement("td", { className: "c" }, react.default.createElement("span", { className: h.lastStatus === "ERR" ? "st-fail" : "st-ok" }, String(h.lastStatus ?? "—"))), react.default.createElement("td", { className: "c" }, h.lastMs != null ? `${h.lastMs}ms` : ""), react.default.createElement("td", { className: "c" }, react.default.createElement("button", {
176
+ className: "hr-btn ghost",
177
+ onClick: () => doTest(h.host),
178
+ style: { padding: "0 6px" }
179
+ }, "测")));
180
+ }))), tip && react.default.createElement("div", {
181
+ className: "hr-tip",
182
+ style: {
183
+ left: tip.x,
184
+ top: tip.y
185
+ }
186
+ }, tip.text));
187
+ }
188
+ function apply(ctx) {
189
+ ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
190
+ name: "settings.section",
191
+ id: "dsh-host-router",
192
+ order: 60,
193
+ label: "网络路由"
194
+ }, (props) => react.default.createElement(HostRouterPage, props))), "dsh-host-router: settings page");
195
+ }
196
+
197
+ //#endregion
198
+ exports.apply = apply;
199
+ exports.inject = inject;
200
+ return module.exports;
201
+ }
202
+ });
203
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.cjs","names":["path: string","body?: any","ev: any","text: string","h: string","h: any","txt: string","e: any","ctx: ClientContext","props: any"],"sources":["../src/client/index.ts"],"sourcesContent":["/**\n * dsh-host-router · client 设置页(纯 React)。\n *\n * 挂 settings.section slot:设置页里独立一页「网络路由」。\n * 通信:同源 fetch → host webServer API(/api/dsh-host-router/{seen,scan,save,test,config})。\n *\n * 渲染:纯 React(createElement + hooks),React 管 mount/unmount,切走回来自动重渲染不丢。\n * (之前 useEffect+ref 塞原生 DOM,切走回来组件未 remount 时 DOM 丢失 → 改纯 React。)\n */\nimport React from 'react'\n\ntype SlotsService = any\ntype ClientContext = { slots: SlotsService }\n\nexport const inject = ['slots']\n\nconst API = '/api/dsh-host-router'\n\nconst STYLES = `\n.hr-page{font-family:ui-monospace,monospace;font-size:13px;line-height:1.55;padding:10px 14px;max-width:900px}\n.hr-page h3{margin:0 0 4px;font-size:14px}\n.hr-ep{color:var(--theme-text-secondary,#999);margin:0 0 6px;font-size:12px}\n.hr-hint{color:var(--theme-text-tertiary,#777);font-size:12px;margin:0 0 10px}\n.hr-bar{display:flex;gap:6px;align-items:center;margin-bottom:8px;flex-wrap:wrap}\n.hr-btn{background:var(--theme-accent,#4a9eff);color:#fff;border:none;border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px;white-space:nowrap}\n.hr-btn.ghost{background:transparent;border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc)}\n.hr-btn:disabled{opacity:.45;cursor:not-allowed}\n.hr-msg{color:var(--theme-text-secondary,#999);font-size:12px;padding:5px 8px;border-radius:6px;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#333);white-space:pre-wrap;max-height:90px;overflow:auto;margin:6px 0}\n.table-wrap{overflow:auto;max-height:55vh;margin-bottom:10px}\ntable{border-collapse:separate;border-spacing:0;width:auto;table-layout:fixed}\nth,td{border-bottom:1px solid var(--theme-border,#333);padding:3px 6px;text-align:left;font-size:12px;white-space:nowrap}\ntd{overflow:hidden;text-overflow:ellipsis}\nth{font-size:11px;color:var(--theme-text-secondary,#999);font-weight:600}\nthead th{position:sticky;top:0;background:var(--theme-bg,#1b1b1c);z-index:1}\ntd.c,th.c{text-align:center}\ntr.stale-row{opacity:.5}\n.st-ok{color:#2ecc71}.st-fail{color:#e74c3c}.st-gray{color:#888}\n.empty{padding:12px;text-align:center;color:var(--theme-text-secondary,#888)}\n.hr-tip{position:fixed;z-index:1000;pointer-events:none;background:var(--theme-input-bg,#111);border:1px solid var(--theme-border,#444);color:var(--theme-text,#ccc);padding:4px 8px;border-radius:5px;font-size:12px;max-width:440px;white-space:normal;word-break:break-all;box-shadow:0 2px 8px rgba(0,0,0,.35)}\n.h4{margin:14px 0 4px;font-size:12px;color:var(--theme-text-secondary,#aaa)}\n`\n\nasync function jget(path: string): Promise<any> { return fetch(API + path).then((r) => r.json()).catch(() => null) }\nasync function jpost(path: string, body?: any): Promise<any> {\n return fetch(API + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: body ? JSON.stringify(body) : undefined })\n .then((r) => r.json()).catch(() => null)\n}\n\ninterface SeenHost {\n host: string; lastStatus?: number | string; lastMs?: number; count?: number\n owner?: string; source?: string; proxied?: boolean; hits?: number; stale?: boolean; alive?: boolean\n}\ninterface Cfg { proxyHost?: string; proxyPort?: number; proxiedHosts?: string[] }\n\nfunction HostRouterPage() {\n const [cfg, setCfg] = React.useState<Cfg>({})\n const [draft, setDraft] = React.useState<Set<string>>(new Set())\n const [seen, setSeen] = React.useState<SeenHost[]>([])\n const [msg, setMsg] = React.useState('')\n const [busy, setBusy] = React.useState(false)\n const [tip, setTip] = React.useState<{ x: number; y: number; text: string } | null>(null)\n\n // 超长内容浮窗(fixed 定位,不被表格 overflow 裁剪;右缘 clamp 防出屏)。\n const showTip = (ev: any, text: string) => setTip({ x: Math.min(ev.clientX + 12, window.innerWidth - 452), y: ev.clientY + 16, text })\n const hideTip = () => setTip(null)\n\n // 只刷数据(seen/cfg),不重置草稿:轮询与手动刷新不覆盖用户编辑到一半的勾选(2026-08-29)。\n const refreshData = React.useCallback(async () => {\n const [seenR, cfgR] = await Promise.all([jget('/seen'), jget('/config')])\n if (cfgR?.ok?.value) setCfg(cfgR.value)\n setSeen(seenR?.ok ? seenR.hosts : [])\n }, [])\n // 全量刷新:数据 + 草稿同步磁盘白名单(首次加载/清理过期后用)。\n const refreshAll = React.useCallback(async () => {\n const [seenR, cfgR] = await Promise.all([jget('/seen'), jget('/config')])\n if (cfgR?.ok?.value) { setCfg(cfgR.value); setDraft(new Set(cfgR.value.proxiedHosts ?? [])) }\n setSeen(seenR?.ok ? seenR.hosts : [])\n }, [])\n\n React.useEffect(() => {\n refreshAll()\n const t = window.setInterval(refreshData, 5000)\n return () => window.clearInterval(t)\n }, [refreshAll, refreshData])\n\n const toggle = (h: string) => setDraft((d) => { const n = new Set(d); n.has(h) ? n.delete(h) : n.add(h); return n })\n\n const doScan = async () => {\n setMsg('扫描中…'); setBusy(true)\n const r = await jpost('/scan')\n setBusy(false)\n if (!r?.ok) { setMsg('扫描失败'); return }\n setMsg(`扫描完成,${r.hosts.filter((h: any) => h.needsProxy).length} 个直连不通(建议代理)`)\n await refreshData() // 扫描结果已 upsert 进 seen,刷新拉合并表(不动草稿)\n }\n\n const doSave = async () => {\n setBusy(true); setMsg('保存中…')\n const r = await jpost('/save', { proxiedHosts: [...draft] })\n setBusy(false)\n setMsg(r?.ok ? '已保存并即时生效' : '保存失败')\n if (r?.ok) refreshData()\n }\n\n const doTest = async (h: string) => {\n const r = await jpost('/test', { host: h })\n setMsg(`${h}: ${r.status} · ${r.ms}ms · ${r.proxy ? '代理' : '直连'}` + (r.message ? ` · ${r.message}` : ''))\n }\n\n const th = (txt: string, c = true) => React.createElement('th', { className: c ? 'c' : '', key: txt }, txt)\n const endpoint = `${cfg.proxyHost ?? '127.0.0.1'}:${cfg.proxyPort ?? 7897}`\n\n const doCleanStale = async () => {\n setBusy(true); setMsg('清理过期…')\n const r = await jpost('/clean-stale')\n setBusy(false)\n setMsg(r?.ok ? `已清理 ${r.removed?.length ?? 0} 个过期 host:${(r.removed ?? []).join(', ') || '无'}` : '清理失败')\n if (r?.ok) refreshAll() // 磁盘白名单被清理,草稿需同步\n }\n\n return React.createElement('div', { className: 'hr-page' },\n React.createElement('style', null, STYLES),\n React.createElement('h3', null, '网络路由(dsh-host-router)'),\n React.createElement('p', { className: 'hr-ep' }, '代理: ', React.createElement('code', null, endpoint)),\n React.createElement('p', { className: 'hr-hint' }, '勾选走代理,保存即生效。绿=本次扫到在用,灰=本次没扫到(几次重启都灰可删),过期=白名单里但久未用。'),\n React.createElement('div', { className: 'hr-bar' },\n React.createElement('button', { className: 'hr-btn', onClick: doScan, disabled: busy }, '扫描配置'),\n React.createElement('button', { className: 'hr-btn ghost', onClick: doSave, disabled: busy }, '保存勾选'),\n React.createElement('button', { className: 'hr-btn ghost', onClick: doCleanStale, disabled: busy }, '清理过期'),\n React.createElement('button', { className: 'hr-btn ghost', onClick: refreshData }, '刷新'),\n ),\n msg && React.createElement('div', { className: 'hr-msg' }, msg),\n // 合一表:嗅探 + 白名单(没扫到也显示);精简 5 列,tip 浮窗展示超长内容\n React.createElement('table', null,\n React.createElement('colgroup', null,\n React.createElement('col', { style: { width: '200px' } }),\n React.createElement('col', { style: { width: '44px' } }),\n React.createElement('col', { style: { width: '64px' } }),\n React.createElement('col', { style: { width: '68px' } }),\n React.createElement('col', { style: { width: '56px' } }),\n React.createElement('col', { style: { width: '76px' } }),\n React.createElement('col', { style: { width: '44px' } }),\n ),\n React.createElement('thead', null, React.createElement('tr', null,\n th('hostname', false), th('代理'), th('归属'), th('本次命中'), th('状态'), th('耗时'), th('测'))),\n React.createElement('tbody', null,\n seen.length === 0\n ? React.createElement('tr', null, React.createElement('td', { className: 'empty', colSpan: 5 }, '暂无数据,先用 dsh 一会儿或点「扫描配置」'))\n : seen.map((h: any) => {\n const alive = h.alive !== false && (h.count ?? 0) > 0\n return React.createElement('tr', { key: h.host, className: h.stale ? 'stale-row' : undefined },\n React.createElement('td', { onMouseEnter: (e: any) => showTip(e, h.host), onMouseLeave: hideTip }, h.host),\n React.createElement('td', { className: 'c' }, React.createElement('input', { type: 'checkbox', checked: draft.has(h.host), onChange: () => toggle(h.host) })),\n React.createElement('td', { className: 'c', onMouseEnter: (e: any) => showTip(e, `${h.owner ?? '未知'} · ${h.source ?? '-'}`), onMouseLeave: hideTip }, h.owner ?? '未知'),\n React.createElement('td', { className: 'c' }, React.createElement('span', { className: alive ? 'st-ok' : 'st-gray' }, alive ? `●${h.count}` : '○')),\n React.createElement('td', { className: 'c' }, React.createElement('span', { className: h.lastStatus === 'ERR' ? 'st-fail' : 'st-ok' }, String(h.lastStatus ?? '—'))),\n React.createElement('td', { className: 'c' }, h.lastMs != null ? `${h.lastMs}ms` : ''),\n React.createElement('td', { className: 'c' }, React.createElement('button', { className: 'hr-btn ghost', onClick: () => doTest(h.host), style: { padding: '0 6px' } }, '测')),\n )\n })\n ),\n ),\n tip && React.createElement('div', { className: 'hr-tip', style: { left: tip.x, top: tip.y } }, tip.text),\n )\n}\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.slots.inject('settings.section', () =>\n ctx.slots.register(\n { name: 'settings.section', id: 'dsh-host-router', order: 60, label: '网络路由' },\n (props: any) => React.createElement(HostRouterPage, props),\n ),\n ), 'dsh-host-router: settings page')\n}\n"],"mappings":"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"}
package/lib/config.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * dsh-host-router · 配置(独立 config.json;/save 写完 live reload 即生效)。
3
+ *
4
+ * 存 ~/.dsh/host-router/config.json。优先 DSH_HOME(实测:部署 web 进程 homedir
5
+ * 可能与 DSH_HOME 指向不同用户,homedir() 推导路径错位——DSH_HOME 才是权威)。
6
+ */
7
+ import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
8
+ import { join, dirname } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+ const DEFAULT = {
11
+ proxyHost: '127.0.0.1',
12
+ proxyPort: 7897,
13
+ proxiedHosts: ['api.openai.com', 'awesome-dsh-plugin.com'],
14
+ sniff: true,
15
+ };
16
+ export function configDir() {
17
+ const dshHome = process.env.DSH_HOME || join(homedir(), '.dsh');
18
+ return join(dshHome, 'host-router');
19
+ }
20
+ export function configPath() {
21
+ return join(configDir(), 'config.json');
22
+ }
23
+ export function logPath() {
24
+ return join(configDir(), 'log.txt');
25
+ }
26
+ /** 读配置(缺字段用默认;文件不存在用默认并落盘)。 */
27
+ export function readConfig() {
28
+ const p = configPath();
29
+ try {
30
+ const raw = JSON.parse(readFileSync(p, 'utf8'));
31
+ return { ...DEFAULT, ...raw };
32
+ }
33
+ catch {
34
+ return { ...DEFAULT };
35
+ }
36
+ }
37
+ /** 原子写配置(先 tmp 再 rename,防中途崩溃留半截 JSON)。 */
38
+ export function writeConfig(cfg) {
39
+ const p = configPath();
40
+ mkdirSync(dirname(p), { recursive: true });
41
+ const tmp = p + '.tmp';
42
+ writeFileSync(tmp, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
43
+ // rename 跨设备可能失败,fallback 直接 write
44
+ try {
45
+ renameSync(tmp, p);
46
+ }
47
+ catch {
48
+ writeFileSync(p, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
49
+ }
50
+ }
51
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/host/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAC5E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AASjC,MAAM,OAAO,GAAqB;IAChC,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,IAAI;IACf,YAAY,EAAE,CAAC,gBAAgB,EAAE,wBAAwB,CAAC;IAC1D,KAAK,EAAE,IAAI;CACZ,CAAA;AAED,MAAM,UAAU,SAAS;IACvB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAA;IAC/D,OAAO,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;AACrC,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,CAAC,CAAA;AACrC,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,UAAU;IACxB,MAAM,CAAC,GAAG,UAAU,EAAE,CAAA;IACtB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAA8B,CAAA;QAC5E,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,GAAG,EAAE,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;IACvB,CAAC;AACH,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,WAAW,CAAC,GAAqB;IAC/C,MAAM,CAAC,GAAG,UAAU,EAAE,CAAA;IACtB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1C,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,CAAA;IACtB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;IAC/D,mCAAmC;IACnC,IAAI,CAAC;QACH,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,367 @@
1
+ /**
2
+ * dsh-host-router · host 入口。
3
+ *
4
+ * 外挂式 host 路由:按 hostname 给 dsh 请求分流(勾选走代理,其余直连)+ 嗅探
5
+ * + 设置页 API(/api/dsh-host-router/{seen,scan,clean-stale,save,test,config})。
6
+ *
7
+ * 全程在 globalThis.fetch 层替换(白名单走 undici+ProxyAgent,其余原样直连),
8
+ * 位于 LLM 适配器之下,不碰 settings.yaml
9
+ * provider 配置。纯外挂:dependencies 空,运行时零 require,宿主 ctx 注入。
10
+ * 配置存 ~/.dsh/host-router/config.json;/save 写完 live reload 即生效(免重启)。
11
+ */
12
+ import { normalizeHost, isLoopback, matchesProxied } from './routing-dispatcher.js';
13
+ import { makeSeenStore, scanCandidates } from './seen.js';
14
+ import { readConfig, writeConfig, logPath } from './config.js';
15
+ import { appendFileSync } from 'node:fs';
16
+ import { createRequire } from 'node:module';
17
+ import { join } from 'node:path';
18
+ import { homedir } from 'node:os';
19
+ import { pathToFileURL } from 'node:url';
20
+ let undiciMod = null;
21
+ let installed = false;
22
+ /** 7 天没嗅探到 = stale(清理过期用)。 */
23
+ const STALE_MS = 7 * 24 * 3600 * 1000;
24
+ /** 解析 undici 绝对路径:插件目录可能无 node_modules,从 profile node_modules 找。
25
+ * 优先 DSH_HOME/profiles/web/node_modules,退 homedir/.dsh/...
26
+ * 返回 file:// URL(Windows 绝对路径不能直接 import,需转 URL)。 */
27
+ function resolveUndiciPath() {
28
+ const candidates = [
29
+ process.env.DSH_HOME && join(process.env.DSH_HOME, 'profiles', 'web', 'node_modules'),
30
+ join(homedir(), '.dsh', 'profiles', 'web', 'node_modules'),
31
+ ].filter(Boolean);
32
+ for (const base of candidates) {
33
+ try {
34
+ const req = createRequire(join(base, 'package.json'));
35
+ const p = req.resolve('undici');
36
+ return pathToFileURL(p).href;
37
+ }
38
+ catch { /* try next */ }
39
+ }
40
+ return null;
41
+ }
42
+ let originalFetch = null;
43
+ let patched = false;
44
+ const seen = makeSeenStore();
45
+ /** 造 myFetch:替换 globalThis.fetch。白名单 host → 包 undici fetch + ProxyAgent;其余 → 原内置 fetch。
46
+ * 嗅探也在 myFetch 里记每个请求(按 host 聚合 + log.txt)。 */
47
+ function buildMyFetch(undici, config) {
48
+ const proxied = new Set((config.proxiedHosts ?? []).map(String));
49
+ const proxyUrl = `http://${config.proxyHost}:${config.proxyPort}`;
50
+ // ProxyAgent:Clash 关 idle 隧道复用死连接 → 超时;clientFactory 每请求新连接避坑。
51
+ let proxyAgent = null;
52
+ try {
53
+ proxyAgent = new undici.ProxyAgent({
54
+ uri: proxyUrl,
55
+ clientFactory: (origin, o) => new undici.Pool(origin, { ...o, pipelining: 0 }),
56
+ });
57
+ }
58
+ catch (e) { /* proxy 不可用 → 白名单也走直连 */ }
59
+ const sniff = config.sniff !== false;
60
+ const doSniff = (host, path, status, ms, proxy, error) => {
61
+ if (!sniff)
62
+ return;
63
+ seen.record({ host, path, status, ms, proxy, ts: Date.now(), error });
64
+ };
65
+ return async function myFetch(input, init) {
66
+ let host = '';
67
+ let path = '';
68
+ try {
69
+ const u = new URL(typeof input === 'string' ? input : (input?.url ?? input));
70
+ host = u.hostname;
71
+ path = u.pathname;
72
+ }
73
+ catch {
74
+ host = String(input ?? '');
75
+ }
76
+ const useProxy = proxyAgent && matchesProxied(host, proxied);
77
+ const t = Date.now();
78
+ try {
79
+ let res;
80
+ if (useProxy) {
81
+ // 白名单 → 包 undici fetch + ProxyAgent(走代理)
82
+ res = await undici.fetch(input, { ...init, dispatcher: proxyAgent });
83
+ }
84
+ else {
85
+ // 其余 → 原内置 fetch(行为不变)
86
+ res = await originalFetch.call(globalThis, input, init);
87
+ }
88
+ doSniff(host, path, res.status, Date.now() - t, useProxy);
89
+ return res;
90
+ }
91
+ catch (e) {
92
+ doSniff(host, path, 'ERR', Date.now() - t, useProxy, String(e.message).slice(0, 140));
93
+ throw e;
94
+ }
95
+ };
96
+ }
97
+ /** monkeypatch globalThis.fetch(保留原引用,可还原)。 */
98
+ function patchFetch(undici, config) {
99
+ if (!patched)
100
+ originalFetch = globalThis.fetch;
101
+ globalThis.fetch = buildMyFetch(undici, config);
102
+ patched = true;
103
+ installed = true;
104
+ try {
105
+ appendFileSync(logPath(), `# fetch patched at ${new Date().toISOString()} proxied=[${config.proxiedHosts.join(',') || '(none)'}]\n`);
106
+ }
107
+ catch { }
108
+ }
109
+ /** 还原 globalThis.fetch。 */
110
+ function unpatchFetch() {
111
+ if (!patched)
112
+ return;
113
+ try {
114
+ if (originalFetch)
115
+ globalThis.fetch = originalFetch;
116
+ }
117
+ catch { }
118
+ patched = false;
119
+ }
120
+ /** live 重载:用新 config 重建 myFetch(保存即生效,免重启)。
121
+ * fetch 全局引用换一下即可,毫秒级,无重连开销。 */
122
+ function reloadFetch(undici, config) {
123
+ if (!undici || !patched) {
124
+ patchFetch(undici, config);
125
+ return;
126
+ }
127
+ ;
128
+ globalThis.fetch = buildMyFetch(undici, config);
129
+ try {
130
+ appendFileSync(logPath(), `# fetch reloaded at ${new Date().toISOString()} proxied=[${config.proxiedHosts.join(',') || '(none)'}]\n`);
131
+ }
132
+ catch { }
133
+ }
134
+ /** 同源 loopback guard(照 dsh-llm-proxy bridge)。 */
135
+ function isTrusted(req) {
136
+ const addr = req.socket?.remoteAddress;
137
+ if (addr !== '127.0.0.1' && addr !== '::1' && addr !== '::ffff:127.0.0.1')
138
+ return false;
139
+ return true;
140
+ }
141
+ function json(res, status, body) {
142
+ res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
143
+ res.end(JSON.stringify(body));
144
+ }
145
+ async function readBody(req) {
146
+ return new Promise((resolve) => {
147
+ const ch = [];
148
+ req.on('data', (c) => ch.push(c));
149
+ req.on('end', () => { try {
150
+ resolve(JSON.parse(Buffer.concat(ch).toString('utf8')));
151
+ }
152
+ catch {
153
+ resolve(null);
154
+ } });
155
+ req.on('error', () => resolve(null));
156
+ });
157
+ }
158
+ /** 对某 host 探测(走当前全局 fetch,真实路径)。 */
159
+ async function testHost(host) {
160
+ const h = normalizeHost(host);
161
+ if (!h || isLoopback(h))
162
+ return { host: h, status: 'ERR', ms: 0, proxy: false, message: 'loopback/empty' };
163
+ const cfg = readConfig();
164
+ const useProxy = matchesProxied(h, new Set((cfg.proxiedHosts ?? []).map(String)));
165
+ const t = Date.now();
166
+ try {
167
+ const r = await fetch(`https://${h}/`, { method: 'HEAD', signal: AbortSignal.timeout(8000) });
168
+ return { host: h, status: r.status, ms: Date.now() - t, proxy: useProxy };
169
+ }
170
+ catch (e) {
171
+ return { host: h, status: 'ERR', ms: Date.now() - t, proxy: useProxy, message: String(e.message).slice(0, 100) };
172
+ }
173
+ }
174
+ export const inject = ['webServer'];
175
+ export function apply(ctx) {
176
+ let diag = 'apply-start';
177
+ try {
178
+ appendFileSync(logPath(), `# apply at ${new Date().toISOString()}\n`);
179
+ diag = 'logged';
180
+ }
181
+ catch { }
182
+ const start = (u) => {
183
+ undiciMod = u;
184
+ if (!u || typeof u.fetch !== 'function' || typeof u.ProxyAgent !== 'function') {
185
+ diag = 'no-undici-fetch';
186
+ try {
187
+ appendFileSync(logPath(), `# start: undici fetch/ProxyAgent unavailable\n`);
188
+ }
189
+ catch { }
190
+ ctx.logger?.error?.('dsh-host-router: undici fetch/ProxyAgent unavailable — disabled');
191
+ return;
192
+ }
193
+ try {
194
+ const cfg = readConfig();
195
+ patchFetch(u, cfg);
196
+ diag = 'patched';
197
+ ctx.logger?.info?.(`dsh-host-router: globalThis.fetch patched (proxy=${cfg.proxyHost}:${cfg.proxyPort}, proxied=[${cfg.proxiedHosts.join(',') || '(none)'}])`);
198
+ }
199
+ catch (e) {
200
+ diag = 'patch-failed:' + String(e?.message ?? e);
201
+ try {
202
+ appendFileSync(logPath(), `# patch FAILED: ${diag}\n`);
203
+ }
204
+ catch { }
205
+ ctx.logger?.error?.('dsh-host-router: patch failed: ' + String(e?.message ?? e));
206
+ }
207
+ };
208
+ // undici:插件目录无 node_modules,从 profile node_modules 解析绝对路径再 import。
209
+ const undiciMain = resolveUndiciPath();
210
+ try {
211
+ appendFileSync(logPath(), `# undiciMain=${undiciMain ?? '(null)'}\n`);
212
+ }
213
+ catch { }
214
+ if (!undiciMain) {
215
+ diag = 'undici-not-found';
216
+ ctx.logger?.error?.('dsh-host-router: undici not found in profile node_modules — disabled');
217
+ return;
218
+ }
219
+ import(undiciMain).then(async (u) => {
220
+ try {
221
+ appendFileSync(logPath(), `# undici imported OK\n`);
222
+ }
223
+ catch { }
224
+ start(u);
225
+ }).catch((e) => {
226
+ diag = 'import-failed:' + String(e?.message ?? e);
227
+ try {
228
+ appendFileSync(logPath(), `# import FAILED: ${diag}\n`);
229
+ }
230
+ catch { }
231
+ ctx.logger?.error?.('dsh-host-router: undici load failed: ' + String(e));
232
+ });
233
+ // 设置页 API:webServer.register 前缀路由(照 dsh-super-injector /api 同款)。
234
+ ctx.effect(() => ctx.webServer.register({
235
+ kind: 'prefix',
236
+ path: '/api/dsh-host-router',
237
+ handler: async (req, res) => {
238
+ if (!isTrusted(req)) {
239
+ json(res, 403, { error: 'forbidden' });
240
+ return;
241
+ }
242
+ const url = req.url ?? '';
243
+ try {
244
+ if (url.endsWith('/seen') && req.method === 'GET') {
245
+ let fetchPatched = false;
246
+ try {
247
+ fetchPatched = patched && globalThis.fetch !== originalFetch;
248
+ }
249
+ catch { }
250
+ const cfg = readConfig();
251
+ const proxied = new Set(cfg.proxiedHosts ?? []);
252
+ const now = Date.now();
253
+ // 合并:嗅探到的 host + 白名单 host(没嗅探到也显示,灰)
254
+ const map = new Map();
255
+ for (const h of seen.list())
256
+ map.set(h.host, { ...h });
257
+ for (const h of proxied) {
258
+ if (!map.has(h))
259
+ map.set(h, { host: h, count: 0, owner: '未知', source: 'config', mode: 'passive' });
260
+ }
261
+ const hosts = [...map.values()].map((h) => {
262
+ const hits = h.count ?? 0;
263
+ const stale = proxied.has(h.host) && (hits === 0 || (h.lastSeen ? now - h.lastSeen > STALE_MS : true));
264
+ return { ...h, proxied: proxied.has(h.host), hits, stale, alive: hits > 0 };
265
+ });
266
+ json(res, 200, { ok: true, hosts, installed, diag, fetchPatched });
267
+ return;
268
+ }
269
+ if (url.endsWith('/scan') && req.method === 'POST') {
270
+ // 候选带 owner/source,批量直连探测(2.5s),结果 upsert 进 store
271
+ const candidates = scanCandidates();
272
+ const probe = async (c) => {
273
+ const t = Date.now();
274
+ try {
275
+ const r = await fetch(`https://${c.host}/`, { method: 'HEAD', signal: AbortSignal.timeout(2500) });
276
+ return { ...c, direct: r.status, ms: Date.now() - t, needsProxy: false };
277
+ }
278
+ catch (e) {
279
+ return { ...c, direct: 'fail', ms: Date.now() - t, needsProxy: true, reason: String(e.message).slice(0, 80) };
280
+ }
281
+ };
282
+ const results = [];
283
+ let idx = 0;
284
+ async function worker() { while (idx < candidates.length) {
285
+ const c = candidates[idx++];
286
+ results.push(await probe(c));
287
+ } }
288
+ await Promise.all(Array.from({ length: 8 }, worker));
289
+ // upsert 进 store(带探测结果,不增 hits)
290
+ for (const r of results)
291
+ seen.upsert(r.host, { owner: r.owner, source: r.source, mode: 'active', lastStatus: r.direct, lastMs: r.ms });
292
+ results.sort((a, b) => Number(b.needsProxy) - Number(a.needsProxy) || a.host.localeCompare(b.host));
293
+ json(res, 200, { ok: true, hosts: results });
294
+ return;
295
+ }
296
+ if (url.endsWith('/clean-stale') && req.method === 'POST') {
297
+ // 一键清灰:移除 stale(本次 run hits=0 或 7 天没动)的白名单 host
298
+ const cfg = readConfig();
299
+ const proxied = new Set(cfg.proxiedHosts ?? []);
300
+ const now = Date.now();
301
+ const seenList = new Map(seen.list().map((h) => [h.host, h]));
302
+ const removed = [];
303
+ cfg.proxiedHosts = (cfg.proxiedHosts ?? []).filter((h) => {
304
+ const s = seenList.get(h);
305
+ const hits = s?.count ?? 0;
306
+ const stale = hits === 0 || (s?.lastSeen ? now - s.lastSeen > STALE_MS : true);
307
+ if (stale) {
308
+ removed.push(h);
309
+ return false;
310
+ }
311
+ return true;
312
+ });
313
+ writeConfig(cfg);
314
+ try {
315
+ reloadFetch(undiciMod, cfg);
316
+ }
317
+ catch { }
318
+ json(res, 200, { ok: true, removed });
319
+ return;
320
+ }
321
+ if (url.endsWith('/save') && req.method === 'POST') {
322
+ const body = await readBody(req);
323
+ const cfg = readConfig();
324
+ if (body && Array.isArray(body.proxiedHosts))
325
+ cfg.proxiedHosts = body.proxiedHosts;
326
+ if (body && typeof body.sniff === 'boolean')
327
+ cfg.sniff = body.sniff;
328
+ if (body && typeof body.proxyHost === 'string')
329
+ cfg.proxyHost = body.proxyHost;
330
+ if (body && typeof body.proxyPort === 'number')
331
+ cfg.proxyPort = body.proxyPort;
332
+ writeConfig(cfg);
333
+ // live 重载:用新 config 重建 myFetch,保存即生效(免重启)
334
+ try {
335
+ reloadFetch(undiciMod, cfg);
336
+ }
337
+ catch (e) {
338
+ try {
339
+ appendFileSync(logPath(), `# reload FAILED: ${String(e?.message ?? e)}\n`);
340
+ }
341
+ catch { }
342
+ }
343
+ json(res, 200, { ok: true, note: '已保存并即时生效' });
344
+ return;
345
+ }
346
+ if (url.endsWith('/test') && req.method === 'POST') {
347
+ const body = await readBody(req);
348
+ const r = await testHost(body?.host ?? '');
349
+ json(res, 200, r);
350
+ return;
351
+ }
352
+ if (url.endsWith('/config') && req.method === 'GET') {
353
+ json(res, 200, { ok: true, value: readConfig() });
354
+ return;
355
+ }
356
+ json(res, 404, { error: 'not found' });
357
+ }
358
+ catch (e) {
359
+ json(res, 500, { error: String(e?.message ?? e) });
360
+ }
361
+ },
362
+ }), 'dsh-host-router: api');
363
+ ctx.on('dispose', () => {
364
+ unpatchFetch();
365
+ });
366
+ }
367
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * dsh-host-router · hostname 归一化工具(直连/代理判断共用)。
3
+ *
4
+ * 早期设计里的 duck-typed HostRouter dispatcher(undici setGlobalDispatcher)
5
+ * 已由 globalThis.fetch 替换方案取代(见 index.ts),这里只保留被
6
+ * index.ts / seen.ts 实际使用的两个纯函数。
7
+ */
8
+ /** 归一化 hostname(去端口/括号/scheme)。 */
9
+ export function normalizeHost(host) {
10
+ if (!host)
11
+ return '';
12
+ let h = String(host).trim().toLowerCase();
13
+ if (h.includes('://')) {
14
+ try {
15
+ h = new URL(h).hostname;
16
+ }
17
+ catch { /* fall through */ }
18
+ }
19
+ h = h.replace(/:\d+$/, '').replace(/^\[(.+)\]$/, '$1');
20
+ return h;
21
+ }
22
+ /** loopback 永远直连。 */
23
+ export function isLoopback(host) {
24
+ return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';
25
+ }
26
+ /** host 是否命中代理白名单(含子域后缀 `xxx.example.com` 命中 `example.com`);loopback 永远否。
27
+ * 快路径用 Set,白名单很小,成员遍历开销可忽略。 */
28
+ export function matchesProxied(host, proxied) {
29
+ if (!host || isLoopback(host))
30
+ return false;
31
+ if (proxied.has(host))
32
+ return true;
33
+ for (const entry of proxied)
34
+ if (host.endsWith(`.${entry}`))
35
+ return true;
36
+ return false;
37
+ }
38
+ //# sourceMappingURL=routing-dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-dispatcher.js","sourceRoot":"","sources":["../src/host/routing-dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,mCAAmC;AACnC,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACpB,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACzC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC;YAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAC9D,CAAC;IACD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;IACtD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,qBAAqB;AACrB,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,OAAO,CAAA;AAC3F,CAAC;AAED;gCACgC;AAChC,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAA4B;IACvE,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO;QAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAO,IAAI,CAAA;IACxE,OAAO,KAAK,CAAA;AACd,CAAC"}
package/lib/seen.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * dsh-host-router · 嗅探聚合 + 主动扫描候选。
3
+ *
4
+ * seenStore:按 host 聚合(首次/最近/命中数/状态/owner/source),给设置页清单。
5
+ * count = 本次进程(run)命中数,>0 绿(本次扫到)、=0 灰(本次没扫到)。
6
+ * dsh 重启 = 新进程 = count 清零,看几次重启都灰 = 可删。
7
+ * scanCandidates:读 ~/.dsh/settings.yaml provider baseURL host + 已知境外兜底,
8
+ * 每个带 owner(粗归属)+ source(settings/known),给主动扫描不靠猜的候选表。
9
+ */
10
+ import { readFileSync, appendFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ import { normalizeHost, isLoopback } from './routing-dispatcher.js';
14
+ import { logPath } from './config.js';
15
+ /** owner 映射:已知 host → 归属(粗标)。 */
16
+ const OWNER_MAP = {
17
+ 'api.openai.com': 'openai',
18
+ 'awesome-dsh-plugin.com': 'dshmarket 目录',
19
+ 'raw.githubusercontent.com': 'dshmarket 截图/README',
20
+ 'user-images.githubusercontent.com': 'dshmarket 截图',
21
+ 'camo.githubusercontent.com': 'dshmarket 截图',
22
+ 'api.anthropic.com': 'anthropic',
23
+ 'generativelanguage.googleapis.com': 'gemini',
24
+ 'api.deepseek.com': 'deepseek',
25
+ 'api.siliconflow.cn': 'siliconflow',
26
+ 'open.bigmodel.cn': 'zhipu',
27
+ 'dashscope.aliyuncs.com': 'qwen',
28
+ };
29
+ function ownerOf(host) {
30
+ if (OWNER_MAP[host])
31
+ return OWNER_MAP[host];
32
+ for (const k of Object.keys(OWNER_MAP))
33
+ if (host.endsWith(`.${k}`))
34
+ return OWNER_MAP[k];
35
+ return '未知';
36
+ }
37
+ /** 嗅探收集器:按 host 聚合 + 落 log.txt。 */
38
+ export function makeSeenStore() {
39
+ const byHost = new Map();
40
+ function record(r) {
41
+ const e = byHost.get(r.host);
42
+ if (e) {
43
+ e.lastSeen = r.ts;
44
+ e.count = (e.count ?? 1) + 1;
45
+ e.lastStatus = r.status;
46
+ e.lastMs = r.ms;
47
+ e.proxy = r.proxy;
48
+ if (r.error)
49
+ e.error = r.error;
50
+ }
51
+ else {
52
+ byHost.set(r.host, { host: r.host, firstSeen: r.ts, lastSeen: r.ts, count: 1, lastStatus: r.status, lastMs: r.ms, proxy: r.proxy, error: r.error, owner: ownerOf(r.host), source: 'sniff', mode: 'passive' });
53
+ }
54
+ try {
55
+ appendFileSync(logPath(), `${r.ts} | ${r.proxy ? 'PROXY ' : 'DIRECT'} | ${r.host}${r.path} | ${r.status} | ${r.ms}ms\n`);
56
+ }
57
+ catch { /* ignore */ }
58
+ }
59
+ return {
60
+ record,
61
+ /** upsert 一个 host(主动扫描/白名单补显用),不增 count。 */
62
+ upsert(host, patch) {
63
+ const e = byHost.get(host);
64
+ if (e)
65
+ Object.assign(e, patch);
66
+ else
67
+ byHost.set(host, { host, count: 0, owner: ownerOf(host), ...patch });
68
+ },
69
+ list: () => [...byHost.values()].sort((a, b) => (b.lastSeen ?? 0) - (a.lastSeen ?? 0)),
70
+ };
71
+ }
72
+ // 已知常见境外 host 兜底(settings.yaml 没记、但 dsh 运行时会打的)。
73
+ const KNOWN_REMOTE = Object.keys(OWNER_MAP);
74
+ /** dsh settings.yaml 路径(优先 DSH_HOME)。 */
75
+ function settingsYamlPath() {
76
+ const dshHome = process.env.DSH_HOME || join(homedir(), '.dsh');
77
+ return join(dshHome, 'settings.yaml');
78
+ }
79
+ /** 从 settings.yaml 提所有 provider baseURL 的 host(正则:只取 `baseURL: <url>`)。 */
80
+ function providersFromSettings() {
81
+ const hosts = new Set();
82
+ try {
83
+ const txt = readFileSync(settingsYamlPath(), 'utf8');
84
+ for (const m of txt.matchAll(/^\s*baseURL:\s*['"]?(https?:\/\/[^'"\s#]+)/gm)) {
85
+ const h = normalizeHost(m[1]);
86
+ if (h && !isLoopback(h))
87
+ hosts.add(h);
88
+ }
89
+ }
90
+ catch { /* settings.yaml 缺 */ }
91
+ return [...hosts];
92
+ }
93
+ /** 扫描候选 = settings.yaml provider host + 已知境外兜底,带 owner/source。 */
94
+ export function scanCandidates() {
95
+ const byHost = new Map();
96
+ for (const h of providersFromSettings())
97
+ byHost.set(h, { host: h, count: 0, owner: ownerOf(h), source: 'settings', mode: 'active' });
98
+ for (const h of KNOWN_REMOTE) {
99
+ if (!byHost.has(h))
100
+ byHost.set(h, { host: h, count: 0, owner: OWNER_MAP[h] ?? '未知', source: 'known', mode: 'active' });
101
+ }
102
+ return [...byHost.values()].filter((s) => !isLoopback(s.host));
103
+ }
104
+ //# sourceMappingURL=seen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seen.js","sourceRoot":"","sources":["../src/host/seen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface HostRouterConfig {
2
+ proxyHost: string;
3
+ proxyPort: number;
4
+ proxiedHosts: string[];
5
+ sniff: boolean;
6
+ }
7
+ export declare function configDir(): string;
8
+ export declare function configPath(): string;
9
+ export declare function logPath(): string;
10
+ /** 读配置(缺字段用默认;文件不存在用默认并落盘)。 */
11
+ export declare function readConfig(): HostRouterConfig;
12
+ /** 原子写配置(先 tmp 再 rename,防中途崩溃留半截 JSON)。 */
13
+ export declare function writeConfig(cfg: HostRouterConfig): void;
@@ -0,0 +1,2 @@
1
+ export declare const inject: string[];
2
+ export declare function apply(ctx: any): void;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * dsh-host-router · hostname 归一化工具(直连/代理判断共用)。
3
+ *
4
+ * 早期设计里的 duck-typed HostRouter dispatcher(undici setGlobalDispatcher)
5
+ * 已由 globalThis.fetch 替换方案取代(见 index.ts),这里只保留被
6
+ * index.ts / seen.ts 实际使用的两个纯函数。
7
+ */
8
+ /** 归一化 hostname(去端口/括号/scheme)。 */
9
+ export declare function normalizeHost(host: string): string;
10
+ /** loopback 永远直连。 */
11
+ export declare function isLoopback(host: string): boolean;
12
+ /** host 是否命中代理白名单(含子域后缀 `xxx.example.com` 命中 `example.com`);loopback 永远否。
13
+ * 快路径用 Set,白名单很小,成员遍历开销可忽略。 */
14
+ export declare function matchesProxied(host: string, proxied: ReadonlySet<string>): boolean;
@@ -0,0 +1,22 @@
1
+ export interface SeenHost {
2
+ host: string;
3
+ firstSeen?: number;
4
+ lastSeen?: number;
5
+ count?: number;
6
+ lastStatus?: number | string;
7
+ lastMs?: number;
8
+ proxy?: boolean;
9
+ error?: string;
10
+ owner?: string;
11
+ source?: 'sniff' | 'settings' | 'known';
12
+ mode?: 'passive' | 'active';
13
+ }
14
+ /** 嗅探收集器:按 host 聚合 + 落 log.txt。 */
15
+ export declare function makeSeenStore(): {
16
+ record: (r: any) => void;
17
+ /** upsert 一个 host(主动扫描/白名单补显用),不增 count。 */
18
+ upsert(host: string, patch: Partial<SeenHost>): void;
19
+ list: () => SeenHost[];
20
+ };
21
+ /** 扫描候选 = settings.yaml provider host + 已知境外兜底,带 owner/source。 */
22
+ export declare function scanCandidates(): SeenHost[];
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "dsh-host-router",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "lib/index.js",
6
+ "description": "dsh 外挂式网络路由:按 hostname 给 dsh 请求分流——勾选的域名走本地代理(Clash 等),其余直连。内置嗅探,自动扫描 dsh 访问过的 hostname,设置页勾选即生效(保存即热生效,免重启)。纯外挂:dependencies 空,运行时零 require,全靠宿主 ctx 注入;不碰 settings.yaml provider 配置。",
7
+ "license": "MIT",
8
+ "author": "",
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": ""
15
+ },
16
+ "keywords": [
17
+ "deepseek",
18
+ "dsh",
19
+ "deepseek-harness",
20
+ "proxy",
21
+ "clash",
22
+ "undici",
23
+ "routing",
24
+ "dispatcher",
25
+ "network",
26
+ "sniff",
27
+ "cordis-plugin"
28
+ ],
29
+ "dsh": {
30
+ "bundle": {
31
+ "patch": "./cordis.patch.yml"
32
+ },
33
+ "client": {
34
+ "platform": "web",
35
+ "inject": [
36
+ "@deepseek-ai/dsh-client-runtime",
37
+ "@deepseek-ai/dsh-client-ui-slots",
38
+ "@deepseek-ai/dsh-client-ui-settings"
39
+ ]
40
+ }
41
+ },
42
+ "exports": {
43
+ ".": "./lib/index.js",
44
+ "./client": "./dist/client.cjs",
45
+ "./cordis.patch.yml": "./cordis.patch.yml",
46
+ "./package.json": "./package.json"
47
+ },
48
+ "files": [
49
+ "lib",
50
+ "dist",
51
+ "cordis.patch.yml",
52
+ "README.md"
53
+ ],
54
+ "scripts": {
55
+ "build:host": "tsc -p tsconfig.json",
56
+ "build:client": "tsdown",
57
+ "build": "npm run build:host && npm run build:client",
58
+ "typecheck": "tsc -p tsconfig.json --noEmit",
59
+ "prepublishOnly": "node scripts/build.mjs"
60
+ },
61
+ "dependencies": {},
62
+ "peerDependencies": {
63
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
65
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6"
66
+ },
67
+ "devDependencies": {
68
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
69
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
70
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
71
+ "typescript": "^5.6.0",
72
+ "tsdown": "^0.3.0",
73
+ "@types/node": "^22.0.0"
74
+ },
75
+ "pnpm": {
76
+ "onlyBuiltDependencies": ["esbuild"]
77
+ },
78
+ "publishConfig": {
79
+ "access": "public"
80
+ }
81
+ }