@aiwayds/dsh-llm-proxy 0.2.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,156 @@
1
+ # dsh-llm-proxy
2
+
3
+ dsh 插件:**SYSTEM proxy + LLM 分流代理**。通过一个进程级 undici 路由 Dispatcher,
4
+ 把 LLM 流量按配置列表分流到指定代理,其余流量走系统代理(启动环境变量),都没配则直连。
5
+
6
+ ## 优先级语义
7
+
8
+ ```
9
+ LLM 列表命中 > SYSTEM proxy(env 语义)> 直连
10
+ ```
11
+
12
+ - 请求 origin 命中 `llmProxy` 列表条目 → 走该条目指定的代理(undici `ProxyAgent`)
13
+ - 未命中 → 走共享的 undici `EnvHttpProxyAgent`,它自己处理
14
+ `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` / `NO_PROXY`
15
+ (大小写均兼容)——即标准 env 代理语义
16
+ - SYSTEM 也未配置 → 直连
17
+
18
+ > 环境变量**只读不写**:插件不会修改 `process.env.HTTP_PROXY`——对已启动进程无意义。
19
+
20
+ ## 安装
21
+
22
+ ```bash
23
+ # 方式一:作为 npm 插件加入 profile
24
+ dsh plugin add @aiwayds/dsh-llm-proxy # 或在 settings.yaml 的 plugins 里声明
25
+
26
+ # 方式二:手动 patch 挂载(本包自带 cordis.patch.yml)
27
+ # 在 profile 的 bundles 中插入:
28
+ # - id: dsh-llm-proxy
29
+ # name: '@aiwayds/dsh-llm-proxy'
30
+ ```
31
+
32
+ > ⚠️ 最低宿主要求:0.2.0 起本插件声明 `inject: ['skills']`,要求宿主 dsh 提供 skills
33
+ > 服务(`@deepseek-ai/dsh-skill` 0.1.1-rc 系列+)。无该服务的旧宿主上,本插件(含代理
34
+ > 路由功能)不会加载。
35
+
36
+ > 发布状态:本包已发布到 npm(0.2.0,自带内置 skill),方式一
37
+ > `dsh plugin add @aiwayds/dsh-llm-proxy` 即可直接用。
38
+
39
+ ## Skill(内置使用指南)
40
+
41
+ 本仓库自带一个 dsh skill(`skills/dsh-llm-proxy/SKILL.md`):面向 agent 的配置 + 排障
42
+ 使用指南,frontmatter `description` 内嵌触发词(dsh 代理、HTTP 代理、LLM 分流、llmProxy、
43
+ 407、CONNECT 挂起、SOCKS、NODE_USE_ENV_PROXY 等),进入每会话模型目录用于路由。三种获取方式:
44
+
45
+ 1. **随插件自动注册(零操作)**:通过 npm 包或 bundle(cordis.patch.yml)挂载本插件后,
46
+ skill 随插件 `apply()` 自动注册——进入每会话 skill 目录(`<available_skills>`,
47
+ frontmatter `description` 供模型路由),`/dsh-llm-proxy` 手势可直接调出指南(已实测可用)。
48
+ 2. **GitHub 安装(不装插件,只要 skill)**:
49
+
50
+ ```bash
51
+ npx skills add fan56/dsh-llm-proxy # 安装到 ~/.agents/skills/
52
+ ```
53
+
54
+ 3. **手动**:`git clone` 本仓库后,把 `skills/dsh-llm-proxy/` 拷贝或软链到 `~/.dsh/skills/`:
55
+
56
+ ```bash
57
+ git clone https://github.com/fan56/dsh-llm-proxy.git
58
+ ln -s "$(pwd)/dsh-llm-proxy/skills/dsh-llm-proxy" ~/.dsh/skills/dsh-llm-proxy
59
+ ```
60
+
61
+ ⚠️ 铁律:本包的 `@deepseek-ai/*` 只声明在 **peerDependencies**
62
+ (devDependencies 保留供本地构建)。请勿把它们挪进 dependencies——那会装出第二份
63
+ cordis 闭包,导致双实例崩溃(详见 dsh 生态 link-dsh-closure 机制)。
64
+
65
+ ## 配置入口(settings.yaml 命名空间段)
66
+
67
+ dsh 插件的用户配置唯一入口是 **settings.yaml 里按插件 id 命名的命名空间段**。本插件通过
68
+ `@deepseek-ai/dsh-settings` 的 `installSettingsSection` 注册了 `dsh-llm-proxy` 命名空间
69
+ (与 harness 内置插件 `llm-deepseek` 的 `llm-deepseek:` 段同机制),所以请在
70
+ `~/.dsh/settings.yaml` 写:
71
+
72
+ ```yaml
73
+ dsh-llm-proxy:
74
+ enabled: true # 总开关,默认 true;false 时完全不动全局
75
+ systemMode: env # 'env'(默认,读环境变量)| 'off'(全部直连,仅 llmProxy 生效)
76
+ llmProxy: # 匹配列表,按顺序首条命中生效
77
+ - match: "api.deepseek.org" # 域名精确匹配(含任意端口)
78
+ proxy: "http://127.0.0.1:7890"
79
+ - match: "*.volces.com" # 通配:volces.com 本身 + 任意子域
80
+ proxy: "http://127.0.0.1:7891"
81
+ - match: "https://api.openai.com" # 完整 origin 精确匹配(大小写不敏感)
82
+ proxy: "http://127.0.0.1:7892"
83
+ ```
84
+
85
+ 要点:
86
+
87
+ - **顶层键必须是 `dsh-llm-proxy:`**(与插件 id 一致)。写其他键(如 `llm-proxy:`)不会
88
+ 被读到,静默失效。
89
+ - 编辑 settings.yaml **热生效**:settings provider 发布变更后,插件会拆掉旧 router、按新
90
+ 配置重建,无需重启。
91
+ - bundle entry config(`cordis.patch.yml` insert 行的 `config:`)作为 base 层仍然有效;
92
+ settings.yaml 段覆盖其上的同名键。
93
+ - 未挂载 settings 服务的宿主自动回退到 entry config,插件照常工作。
94
+
95
+ ### match 规则
96
+
97
+ | 写法 | 含义 |
98
+ |------|------|
99
+ | `api.example.com` | host 精确匹配,**任意端口**都算命中 |
100
+ | `api.example.com:8443` | host + 显式端口必须相等(https 默认 443 / http 默认 80 会补全比较) |
101
+ | `*.example.com` | `example.com` 本身及任意层级子域 |
102
+ | `https://api.example.com` | 完整 origin 精确匹配(scheme 也要一致) |
103
+
104
+ 匹配全部大小写不敏感、纯词法比较(无 DNS/IO)。IDN 域名在匹配前统一归一化为 punycode
105
+ (`中文.com` 与 `xn--fiq228c.com` 两种写法等价,pattern 和 origin 两侧对称处理)。
106
+ 实现为纯函数(`src/match.ts`),有完整单测。
107
+
108
+ ### proxy 协议限制
109
+
110
+ undici `ProxyAgent` 只支持 **http:// 与 https://** 上游代理。
111
+ `socks5://` 等 schema 在启动校验时直接报错(fail-fast),README 明说:**SOCKS 不支持**。
112
+
113
+ ## 实现要点
114
+
115
+ - service start 时创建自定义 undici 路由 Dispatcher 并 `setGlobalDispatcher(router)`;
116
+ router 的 `dispatch()` 按 origin 分流:命中 → 该条的 `ProxyAgent`(按 URL 缓存复用);
117
+ 未命中 → 共享 `EnvHttpProxyAgent`(或 `systemMode: off` 时直连 `Agent`)。
118
+ - **照抄 pi-src 生产模板的防御细节**:
119
+ - `undici.install?.()` 防 Node 内建 fetch 与 npm undici 版本偏斜 bug
120
+ (Node 26 bundled fetch 经 npm undici dispatcher 不解压导致 `response.json()`
121
+ 失败);用 pristine/owned 双守卫只在未被替换时 install。
122
+ - undici Client 中途断流会发裸 `error` 事件导致 unhandled crash——对本插件创建的
123
+ 每个 dispatcher 挂 EventEmitter 级 error 兜底 listener(与 pi-src
124
+ `http-dispatcher.ts` 同款机制,覆盖进程内所有由本插件创建的 dispatcher)。
125
+ - **HMR 重载窗口闭环**:接管状态(原始 dispatcher/fetch + 活跃层列表)存放在
126
+ `Symbol.for` 共享槽位,跨模块副本可见。cordis `Group.update` 先 create 后 remove、
127
+ vendor HMR 先 re-import 后 dispose——新版 apply 可能发生在旧版 dispose 之前;
128
+ dispose 只摘除自己那一层,把 globals 还给最近的存活层,绝不打掉别人的路由器。
129
+ - dispose(cordis effect 语义)时**非阻塞拆除**:先把 globals 还回去(新请求立即不再
130
+ 进入旧 router),再后台 fire-and-forget 地 `close()` 排空在途请求——长 LLM 流式响应
131
+ 不会拖住热重载。
132
+ - proxy URL 中可能出现的 userinfo 凭证(`user:pass@host`)在日志/error message 里一律
133
+ 脱敏为 `***@host`。
134
+ - 与 `NODE_USE_ENV_PROXY` 官方姿势的关系:本插件是等价替代——无需该变量、无需重启,
135
+ 对新请求即时生效,且额外提供 LLM 分流能力。
136
+
137
+ ## 手动 e2e 验证(CI 无网,不做真实请求测试)
138
+
139
+ ```bash
140
+ # 1. 配一个本地回显代理(如 clash/mihomo 的 mixed-port 7890)
141
+ export HTTPS_PROXY=http://127.0.0.1:7890
142
+ # 2. settings.yaml 配 dsh-llm-proxy 段,把某个 API 域名指到另一端口
143
+ # 3. 启动 dsh 后发起对话,观察代理访问日志:
144
+ # - llmProxy 命中域名的请求出现在该条代理日志里
145
+ # - 其他外网请求出现在 7890 日志里
146
+ # 4. unset HTTPS_PROXY 重启 → 只有 llmProxy 命中的流量走代理,其余直连
147
+ ```
148
+
149
+ ## 开发
150
+
151
+ ```bash
152
+ npm run build # tsc -> lib/
153
+ npm run check # tsc --noEmit
154
+ npm test # node --test(match / router 探针分流 / config 边界 / skill 注册 /
155
+ # dispose 对称性 / e2e 全链路:apply → 全局 fetch 分流 → dispose 恢复 + HMR 窗口回归)
156
+ ```
@@ -0,0 +1,9 @@
1
+ # dsh-llm-proxy bundle patch: mounts the plugin into the profile's assembly
2
+ # tree under the stable plugin id `dsh-llm-proxy`. On service start it swaps
3
+ # the process-global undici dispatcher for a router: llmProxy hits go through
4
+ # their configured proxy, everything else follows the env SYSTEM proxy
5
+ # semantics (or connects directly with systemMode: off).
6
+
7
+ - insert:
8
+ - id: dsh-llm-proxy
9
+ name: '@aiwayds/dsh-llm-proxy'
package/lib/index.d.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * dsh-llm-proxy — SYSTEM proxy + LLM traffic-splitting proxy for dsh.
3
+ *
4
+ * Installs a single process-global undici routing dispatcher:
5
+ * - requests whose origin hits an `llmProxy` list entry go through that
6
+ * entry's proxy (undici ProxyAgent);
7
+ * - everything else goes through a shared EnvHttpProxyAgent, which applies
8
+ * the startup environment's HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY
9
+ * semantics (upper- and lowercase);
10
+ * - with no env proxy configured the fallback is a direct connection.
11
+ *
12
+ * Priority: llmProxy hit > SYSTEM (env) > direct.
13
+ *
14
+ * apply() also registers a bundled skill (skills/dsh-llm-proxy/SKILL.md) that
15
+ * carries the plugin's configuration and troubleshooting guide.
16
+ *
17
+ * User configuration arrives exclusively through the `dsh-llm-proxy`
18
+ * settings.yaml namespace (registered via installSettingsSection, the same
19
+ * seam harness core plugins use); the bundle entry config is the base layer.
20
+ * Edits to the settings section hot-publish: the router is rebuilt in place,
21
+ * unless the resolved values are unchanged, in which case the rebuild is
22
+ * skipped (the settings attach itself fires one redundant onChange).
23
+ *
24
+ * Environment variables are only read, never written: mutating
25
+ * process.env.HTTP_PROXY in an already-started process has no effect on
26
+ * undici/Node internals and is deliberately avoided.
27
+ *
28
+ * @module @aiwayds/dsh-llm-proxy
29
+ */
30
+ import type { Context } from '@deepseek-ai/cordis';
31
+ import z from '@deepseek-ai/schemastery';
32
+ import * as undici from 'undici';
33
+ import { type LlmProxyEntry } from './router.ts';
34
+ export { matchOrigin } from './match.ts';
35
+ export { createProxyRouterDispatcher, resolveRoute, type LlmProxyEntry, type RouteDecision } from './router.ts';
36
+ export declare const name = "dsh-llm-proxy";
37
+ /** Service required by the bundled skill provider. */
38
+ export declare const inject: string[];
39
+ /** Plugin configuration. */
40
+ export interface Config {
41
+ /** Master switch; `false` leaves globals untouched (default `true`). */
42
+ enabled?: boolean;
43
+ /**
44
+ * Fallback mode for requests no llmProxy entry matches (default `env`):
45
+ * `env` applies HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY from the
46
+ * startup environment; `off` connects directly.
47
+ */
48
+ systemMode?: 'env' | 'off';
49
+ /** Ordered LLM proxy list; the first entry whose match hits wins. */
50
+ llmProxy?: Array<{
51
+ match: string;
52
+ proxy: string;
53
+ }>;
54
+ }
55
+ /** Runtime schema for {@link Config}. */
56
+ export declare const Config: z<Config>;
57
+ /**
58
+ * Mask the userinfo part of a proxy URL ("user:pass@host") so log lines and
59
+ * error messages never carry credentials.
60
+ */
61
+ export declare function redactProxyUrl(proxyUrl: string): string;
62
+ /** Fully resolved plugin policy captured at apply time. */
63
+ export interface ResolvedConfig {
64
+ readonly enabled: boolean;
65
+ readonly systemMode: 'env' | 'off';
66
+ readonly llmProxy: readonly LlmProxyEntry[];
67
+ }
68
+ /**
69
+ * Validate, default, and freeze the plugin configuration.
70
+ * @param config - optional plugin configuration; omission selects defaults.
71
+ */
72
+ export declare function resolveConfig(config: Config | undefined): ResolvedConfig;
73
+ /** Injectable seams for tests; production defaults build real undici agents. */
74
+ export interface ApplyInternals {
75
+ /**
76
+ * Creates the shared system fallback; the default builds an
77
+ * EnvHttpProxyAgent for `env`, a direct Agent for `off`.
78
+ */
79
+ createSystemDispatcher?: (systemMode: 'env' | 'off') => undici.Dispatcher;
80
+ /** Creates the per-proxy dispatcher; defaults to ProxyAgent. Overridable in tests. */
81
+ createProxyDispatcher?: (proxyUrl: string) => undici.Dispatcher;
82
+ }
83
+ /**
84
+ * Strip a leading YAML frontmatter block (`---` / body / `---`) from a skill
85
+ * markdown file. `SkillDefinition.content` must be the instruction body after
86
+ * metadata removal — the same shape the filesystem provider serves — so the
87
+ * bundled SKILL.md, which keeps its frontmatter for the GitHub/manual install
88
+ * paths, has the block removed when served through {@link skillProvider.get}.
89
+ * Tolerant by design: input that does not open with a `---` line, or whose
90
+ * frontmatter block is never closed, is returned unchanged. Mirrors the
91
+ * delimiter semantics of the upstream skill-filesystem provider.
92
+ */
93
+ export declare function stripFrontmatter(raw: string): string;
94
+ /**
95
+ * Install the global routing dispatcher, wire the `dsh-llm-proxy` settings
96
+ * namespace, register the bundled skill provider, and register teardown.
97
+ * @param ctx - plugin context owning the dispose effect.
98
+ * @param config - composition entry config; the base layer under the
99
+ * settings.yaml `dsh-llm-proxy` section.
100
+ * @param internals - injectable dispatcher factories for tests.
101
+ */
102
+ export declare function apply(ctx: Context, config?: Config, internals?: ApplyInternals): void;
package/lib/index.js ADDED
@@ -0,0 +1,335 @@
1
+ /**
2
+ * dsh-llm-proxy — SYSTEM proxy + LLM traffic-splitting proxy for dsh.
3
+ *
4
+ * Installs a single process-global undici routing dispatcher:
5
+ * - requests whose origin hits an `llmProxy` list entry go through that
6
+ * entry's proxy (undici ProxyAgent);
7
+ * - everything else goes through a shared EnvHttpProxyAgent, which applies
8
+ * the startup environment's HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY
9
+ * semantics (upper- and lowercase);
10
+ * - with no env proxy configured the fallback is a direct connection.
11
+ *
12
+ * Priority: llmProxy hit > SYSTEM (env) > direct.
13
+ *
14
+ * apply() also registers a bundled skill (skills/dsh-llm-proxy/SKILL.md) that
15
+ * carries the plugin's configuration and troubleshooting guide.
16
+ *
17
+ * User configuration arrives exclusively through the `dsh-llm-proxy`
18
+ * settings.yaml namespace (registered via installSettingsSection, the same
19
+ * seam harness core plugins use); the bundle entry config is the base layer.
20
+ * Edits to the settings section hot-publish: the router is rebuilt in place,
21
+ * unless the resolved values are unchanged, in which case the rebuild is
22
+ * skipped (the settings attach itself fires one redundant onChange).
23
+ *
24
+ * Environment variables are only read, never written: mutating
25
+ * process.env.HTTP_PROXY in an already-started process has no effect on
26
+ * undici/Node internals and is deliberately avoided.
27
+ *
28
+ * @module @aiwayds/dsh-llm-proxy
29
+ */
30
+ import { readFile } from 'node:fs/promises';
31
+ import { fileURLToPath } from 'node:url';
32
+ import { deepEqualJson, installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
33
+ import { BUNDLED_SKILL_RANK, } from '@deepseek-ai/dsh-skill';
34
+ import z from '@deepseek-ai/schemastery';
35
+ import * as undici from 'undici';
36
+ import { createProxyRouterDispatcher, withUndiciErrorListener } from "./router.js";
37
+ export { matchOrigin } from "./match.js";
38
+ export { createProxyRouterDispatcher, resolveRoute } from "./router.js";
39
+ export const name = 'dsh-llm-proxy';
40
+ /** Service required by the bundled skill provider. */
41
+ export const inject = ['skills'];
42
+ /**
43
+ * The settings.yaml namespace this plugin owns. dsh feeds user configuration
44
+ * to plugins only through registered settings namespaces; the document
45
+ * section key that reaches this plugin is exactly this string.
46
+ */
47
+ const SETTINGS_NAMESPACE = settingsNamespace('dsh-llm-proxy');
48
+ /** Runtime schema for {@link Config}. */
49
+ export const Config = z.object({
50
+ enabled: z.boolean().default(true),
51
+ systemMode: z.union([z.const('env'), z.const('off')]).default('env'),
52
+ llmProxy: z.array(z.object({
53
+ match: z.string(),
54
+ proxy: z.string(),
55
+ })),
56
+ });
57
+ const CONFIG_KEYS = new Set(['enabled', 'systemMode', 'llmProxy']);
58
+ /**
59
+ * Mask the userinfo part of a proxy URL ("user:pass@host") so log lines and
60
+ * error messages never carry credentials.
61
+ */
62
+ export function redactProxyUrl(proxyUrl) {
63
+ let parsed;
64
+ try {
65
+ parsed = new URL(proxyUrl);
66
+ }
67
+ catch {
68
+ // Unparsable input: best-effort masking of a credential-shaped token.
69
+ return proxyUrl.replace(/(^|\/\/|\s)[^\s@/]+@/, '$1***@');
70
+ }
71
+ if (!parsed.username && !parsed.password)
72
+ return proxyUrl;
73
+ const auth = parsed.password ? '***:***@' : '***@';
74
+ const path = parsed.pathname === '/' ? '' : parsed.pathname;
75
+ return `${parsed.protocol}//${auth}${parsed.host}${path}${parsed.search}${parsed.hash}`;
76
+ }
77
+ function validateProxyUrl(proxy, index) {
78
+ let parsed;
79
+ try {
80
+ parsed = new URL(proxy);
81
+ }
82
+ catch {
83
+ throw new Error(`dsh-llm-proxy: config.llmProxy[${index}].proxy is not a valid URL: "${redactProxyUrl(proxy)}"`);
84
+ }
85
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
86
+ throw new Error(`dsh-llm-proxy: config.llmProxy[${index}].proxy must use http:// or https:// `
87
+ + `(undici ProxyAgent does not support ${parsed.protocol}//; SOCKS is not supported)`);
88
+ }
89
+ }
90
+ /**
91
+ * Validate, default, and freeze the plugin configuration.
92
+ * @param config - optional plugin configuration; omission selects defaults.
93
+ */
94
+ export function resolveConfig(config) {
95
+ if (config !== undefined) {
96
+ for (const key of Object.keys(config)) {
97
+ if (!CONFIG_KEYS.has(key))
98
+ throw new Error(`dsh-llm-proxy: config: unknown key "${key}"`);
99
+ }
100
+ }
101
+ const enabled = config?.enabled ?? true;
102
+ const systemMode = config?.systemMode ?? 'env';
103
+ if (systemMode !== 'env' && systemMode !== 'off') {
104
+ throw new Error('dsh-llm-proxy: config.systemMode must be "env" or "off"');
105
+ }
106
+ const entries = (config?.llmProxy ?? []).map((entry, index) => {
107
+ if (typeof entry?.match !== 'string' || !entry.match.trim()) {
108
+ throw new Error(`dsh-llm-proxy: config.llmProxy[${index}].match must be a non-empty string`);
109
+ }
110
+ if (typeof entry?.proxy !== 'string' || !entry.proxy.trim()) {
111
+ throw new Error(`dsh-llm-proxy: config.llmProxy[${index}].proxy must be a non-empty string`);
112
+ }
113
+ validateProxyUrl(entry.proxy, index);
114
+ return { match: entry.match.trim(), proxy: entry.proxy.trim() };
115
+ });
116
+ return Object.freeze({ enabled, systemMode, llmProxy: Object.freeze(entries) });
117
+ }
118
+ const GLOBALS_KEY = Symbol.for('dsh-llm-proxy.global-state');
119
+ function globalsState() {
120
+ const host = globalThis;
121
+ return (host[GLOBALS_KEY] ??= {
122
+ originalDispatcher: undefined,
123
+ originalFetch: undefined,
124
+ layers: [],
125
+ });
126
+ }
127
+ /**
128
+ * Put `router` — and, when fetch is still pristine or still ours, npm
129
+ * undici's fetch — on top of the process globals, recording the layer so
130
+ * dispose can unwind exactly one level even under interleaved reloads.
131
+ */
132
+ function takeoverGlobals(router) {
133
+ const state = globalsState();
134
+ state.originalDispatcher ??= undici.getGlobalDispatcher();
135
+ state.originalFetch ??= globalThis.fetch;
136
+ // Take over fetch only when it is still pristine or still owned by the top
137
+ // layer; a deliberate override by someone else must survive untouched.
138
+ const under = state.layers[state.layers.length - 1];
139
+ let fetch;
140
+ if (globalThis.fetch === (under?.fetch ?? state.originalFetch)) {
141
+ undici.install?.();
142
+ fetch = globalThis.fetch;
143
+ }
144
+ const layer = { router, fetch };
145
+ state.layers.push(layer);
146
+ undici.setGlobalDispatcher(router);
147
+ return {
148
+ restore() {
149
+ const index = state.layers.lastIndexOf(layer);
150
+ if (index !== -1)
151
+ state.layers.splice(index, 1);
152
+ // Restore only what this instance still owns: after an interleaved
153
+ // reload a newer router may sit on top, and clobbering it would break
154
+ // that one. Unwind hands control to the nearest live layer below, or
155
+ // back to the pristine values once no layer remains.
156
+ if (undici.getGlobalDispatcher() === router) {
157
+ const above = state.layers[state.layers.length - 1];
158
+ undici.setGlobalDispatcher(above?.router ?? state.originalDispatcher);
159
+ }
160
+ if (fetch !== undefined && globalThis.fetch === fetch) {
161
+ const above = [...state.layers].reverse().find((candidate) => candidate.fetch !== undefined);
162
+ globalThis.fetch = above?.fetch ?? state.originalFetch;
163
+ }
164
+ },
165
+ };
166
+ }
167
+ // --- Bundled skill -----------------------------------------------------------
168
+ /** Provider name under `ctx.skills`; doubles as the skill name. */
169
+ const SKILL_PROVIDER_NAME = 'dsh-llm-proxy';
170
+ /** Packaged skill body; `../skills/` resolves to the package root from both lib/ and src/. */
171
+ const SKILL_BODY_URL = new URL('../skills/dsh-llm-proxy/SKILL.md', import.meta.url);
172
+ /** Resource base served with the skill so its relative links resolve. */
173
+ const SKILL_RESOURCE_BASE = {
174
+ kind: 'directory',
175
+ path: fileURLToPath(new URL('../skills/dsh-llm-proxy/', import.meta.url)),
176
+ };
177
+ const SKILL_INVOCATION = { modelInvocable: true, userInvocable: true };
178
+ /** Routing description; must stay identical to the SKILL.md frontmatter (asserted in tests). */
179
+ const SKILL_DESCRIPTION = 'dsh 出站代理 / LLM 分流插件(@aiwayds/dsh-llm-proxy)使用指南。凡给 dsh 插件配置 HTTP 代理、LLM 出站分流,或排查代理网络问题时先读本指南:settings.yaml 顶层 `dsh-llm-proxy:` 段(enabled/systemMode/llmProxy)、llmProxy match 规则、代理 407、CONNECT 挂起、socks5 报错(SOCKS 不支持)、NODE_USE_ENV_PROXY 等价替代。触发词:dsh 代理、HTTP 代理、LLM 分流、llmProxy、llm-proxy、407、CONNECT 挂起、SOCKS、HTTPS_PROXY、NO_PROXY、出站代理、NODE_USE_ENV_PROXY。';
180
+ const SKILL_CANDIDATE = {
181
+ name: SKILL_PROVIDER_NAME,
182
+ description: SKILL_DESCRIPTION,
183
+ invocation: SKILL_INVOCATION,
184
+ provider: SKILL_PROVIDER_NAME,
185
+ source: 'bundled',
186
+ resourceBase: SKILL_RESOURCE_BASE,
187
+ rank: BUNDLED_SKILL_RANK,
188
+ locator: SKILL_BODY_URL,
189
+ };
190
+ const skillProvider = {
191
+ name: SKILL_PROVIDER_NAME,
192
+ list: () => Promise.resolve([SKILL_CANDIDATE]),
193
+ async get(_candidate) {
194
+ return {
195
+ name: SKILL_CANDIDATE.name,
196
+ description: SKILL_CANDIDATE.description,
197
+ invocation: SKILL_CANDIDATE.invocation,
198
+ provider: SKILL_CANDIDATE.provider,
199
+ source: SKILL_CANDIDATE.source,
200
+ resourceBase: SKILL_RESOURCE_BASE,
201
+ content: stripFrontmatter(await readFile(SKILL_BODY_URL, 'utf8')),
202
+ };
203
+ },
204
+ };
205
+ /**
206
+ * Strip a leading YAML frontmatter block (`---` / body / `---`) from a skill
207
+ * markdown file. `SkillDefinition.content` must be the instruction body after
208
+ * metadata removal — the same shape the filesystem provider serves — so the
209
+ * bundled SKILL.md, which keeps its frontmatter for the GitHub/manual install
210
+ * paths, has the block removed when served through {@link skillProvider.get}.
211
+ * Tolerant by design: input that does not open with a `---` line, or whose
212
+ * frontmatter block is never closed, is returned unchanged. Mirrors the
213
+ * delimiter semantics of the upstream skill-filesystem provider.
214
+ */
215
+ export function stripFrontmatter(raw) {
216
+ const firstLineEnd = raw.indexOf('\n');
217
+ if (firstLineEnd < 0 || raw.slice(0, firstLineEnd).replace(/\r$/, '') !== '---')
218
+ return raw;
219
+ let lineStart = firstLineEnd + 1;
220
+ while (lineStart <= raw.length) {
221
+ const nextNewline = raw.indexOf('\n', lineStart);
222
+ const lineEnd = nextNewline < 0 ? raw.length : nextNewline;
223
+ if (raw.slice(lineStart, lineEnd).replace(/\r$/, '') === '---') {
224
+ return raw.slice(nextNewline < 0 ? raw.length : nextNewline + 1).trim();
225
+ }
226
+ if (nextNewline < 0)
227
+ return raw;
228
+ lineStart = nextNewline + 1;
229
+ }
230
+ return raw;
231
+ }
232
+ /**
233
+ * Install the global routing dispatcher, wire the `dsh-llm-proxy` settings
234
+ * namespace, register the bundled skill provider, and register teardown.
235
+ * @param ctx - plugin context owning the dispose effect.
236
+ * @param config - composition entry config; the base layer under the
237
+ * settings.yaml `dsh-llm-proxy` section.
238
+ * @param internals - injectable dispatcher factories for tests.
239
+ */
240
+ export function apply(ctx, config = {}, internals = {}) {
241
+ // `inject = ['skills']` guarantees the service exists on every real host;
242
+ // register unconditionally so a missing service fails loud instead of
243
+ // silently dropping the bundled skill.
244
+ ctx.skills.registerProvider(() => skillProvider);
245
+ // Current authoritative config source: the entry until a settings scope
246
+ // layers the user's settings.yaml section on top (and back to the entry if
247
+ // the settings provider detaches). The seam hands over a thunk so every
248
+ // rejudge reads the live layered value instead of a stale snapshot.
249
+ let getSource = () => config;
250
+ let active;
251
+ let activePolicy;
252
+ let effectRegistered = false;
253
+ function installLayer(policy) {
254
+ const systemDispatcher = internals.createSystemDispatcher?.(policy.systemMode)
255
+ ?? withUndiciErrorListener(policy.systemMode === 'env' ? new undici.EnvHttpProxyAgent() : new undici.Agent());
256
+ const router = createProxyRouterDispatcher(policy.llmProxy, {
257
+ systemDispatcher,
258
+ proxyFactory: internals.createProxyDispatcher
259
+ ?? ((proxyUrl) => withUndiciErrorListener(new undici.ProxyAgent(proxyUrl))),
260
+ });
261
+ return { router, globals: takeoverGlobals(router) };
262
+ }
263
+ function teardownLayer(layer) {
264
+ layer.globals.restore();
265
+ // Non-blocking teardown: close() waits for in-flight requests to drain,
266
+ // which would stall hot reloads behind long LLM streams. The globals are
267
+ // already handed back above, so no new request reaches this router; let
268
+ // it finish draining and closing in the background instead of awaiting.
269
+ void layer.router.close().catch(() => { });
270
+ }
271
+ const ensureDisposeEffect = () => {
272
+ if (effectRegistered)
273
+ return;
274
+ effectRegistered = true;
275
+ ctx.effect(() => async () => {
276
+ const current = active;
277
+ active = undefined;
278
+ if (current)
279
+ teardownLayer(current);
280
+ }, 'dsh-llm-proxy: restore global dispatcher/fetch and close proxies');
281
+ };
282
+ const rejudge = (failFast) => {
283
+ let policy;
284
+ try {
285
+ policy = resolveConfig(getSource());
286
+ }
287
+ catch (error) {
288
+ if (failFast)
289
+ throw error;
290
+ ctx.logger?.error('dsh-llm-proxy: ignoring invalid settings update, keeping current routing: %s', error instanceof Error ? error.message : String(error));
291
+ return;
292
+ }
293
+ // Attaching the settings scope fires onChange unconditionally, even when
294
+ // the layered section resolves to the same values as the entry base
295
+ // layer; rebuilding then would tear down and reinstall an identical
296
+ // router for nothing. Skip when no resolved value changed.
297
+ if (activePolicy !== undefined && deepEqualJson(policy, activePolicy))
298
+ return;
299
+ if (!policy.enabled) {
300
+ const current = active;
301
+ active = undefined;
302
+ activePolicy = policy;
303
+ if (current)
304
+ teardownLayer(current);
305
+ return;
306
+ }
307
+ let next;
308
+ try {
309
+ next = installLayer(policy);
310
+ }
311
+ catch (error) {
312
+ if (failFast)
313
+ throw error;
314
+ ctx.logger?.error('dsh-llm-proxy: failed to rebuild routing, keeping current routing: %o', error);
315
+ return;
316
+ }
317
+ const previous = active;
318
+ active = next;
319
+ activePolicy = policy;
320
+ ensureDisposeEffect();
321
+ if (previous)
322
+ teardownLayer(previous);
323
+ };
324
+ // Start immediately with the entry config so the plugin works even on hosts
325
+ // that never mount a settings service; when the service appears, the
326
+ // layered section takes over and subsequent edits hot-publish here.
327
+ rejudge(true);
328
+ installSettingsSection(ctx, SETTINGS_NAMESPACE, Config, config, {
329
+ setSource: (current) => {
330
+ getSource = current;
331
+ },
332
+ onChange: () => rejudge(false),
333
+ });
334
+ }
335
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/lib/match.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Origin matcher for the LLM proxy routing list.
3
+ *
4
+ * A match pattern is one of:
5
+ * - a full origin ("https://api.deepseek.org") — case-insensitive exact match
6
+ * against the request origin;
7
+ * - a host authority ("api.deepseek.org", "api.deepseek.org:8443") — matches
8
+ * the host exactly, or with a leading `*.` wildcard also any subdomain and
9
+ * the bare domain itself ("*.volces.com" matches "volces.com" and
10
+ * "a.volces.com"). An explicit port in the pattern must equal the request
11
+ * origin's port (scheme defaults count: https=443, http=80).
12
+ *
13
+ * All comparisons are lowercase; matching is purely lexical — no DNS, no I/O.
14
+ * Hostnames are normalized the way WHATWG URL parsing normalizes request
15
+ * origins: lowercase ASCII with IDN labels converted to punycode, so a
16
+ * pattern and an origin written in either the unicode or the xn-- form of
17
+ * the same domain compare equal.
18
+ */
19
+ /**
20
+ * Whether a request origin hits a routing-list match pattern.
21
+ * @param match - pattern from the llmProxy list entry.
22
+ * @param origin - request origin, e.g. "https://api.example.com" or
23
+ * "https://api.example.com:8443"; a bare host[:port] is tolerated.
24
+ */
25
+ export declare function matchOrigin(match: string, origin: string): boolean;