dsh-model-proxy-switch 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +76 -2
- package/client.js +473 -0
- package/cordis.patch.yml +7 -0
- package/icon.svg +13 -0
- package/index.js +341 -0
- package/locale/en.json +25 -0
- package/locale/zh.json +25 -0
- package/package.json +55 -4
- package/vendor/http-proxy.js +542 -0
- package/vendor/node_modules/undici/LICENSE +21 -0
- package/vendor/node_modules/undici/README.md +741 -0
- package/vendor/node_modules/undici/docs/docs/api/Agent.md +84 -0
- package/vendor/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
- package/vendor/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
- package/vendor/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
- package/vendor/node_modules/undici/docs/docs/api/Client.md +290 -0
- package/vendor/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
- package/vendor/node_modules/undici/docs/docs/api/Connector.md +115 -0
- package/vendor/node_modules/undici/docs/docs/api/ContentType.md +57 -0
- package/vendor/node_modules/undici/docs/docs/api/Cookies.md +128 -0
- package/vendor/node_modules/undici/docs/docs/api/Debug.md +62 -0
- package/vendor/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +317 -0
- package/vendor/node_modules/undici/docs/docs/api/Dispatcher.md +1393 -0
- package/vendor/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
- package/vendor/node_modules/undici/docs/docs/api/Errors.md +49 -0
- package/vendor/node_modules/undici/docs/docs/api/EventSource.md +45 -0
- package/vendor/node_modules/undici/docs/docs/api/Fetch.md +60 -0
- package/vendor/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
- package/vendor/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
- package/vendor/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
- package/vendor/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
- package/vendor/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
- package/vendor/node_modules/undici/docs/docs/api/MockClient.md +81 -0
- package/vendor/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
- package/vendor/node_modules/undici/docs/docs/api/MockPool.md +555 -0
- package/vendor/node_modules/undici/docs/docs/api/Pool.md +84 -0
- package/vendor/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
- package/vendor/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
- package/vendor/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
- package/vendor/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
- package/vendor/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
- package/vendor/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
- package/vendor/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
- package/vendor/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
- package/vendor/node_modules/undici/docs/docs/api/Util.md +25 -0
- package/vendor/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
- package/vendor/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
- package/vendor/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
- package/vendor/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
- package/vendor/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
- package/vendor/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
- package/vendor/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
- package/vendor/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
- package/vendor/node_modules/undici/index-fetch.js +65 -0
- package/vendor/node_modules/undici/index.d.ts +3 -0
- package/vendor/node_modules/undici/index.js +234 -0
- package/vendor/node_modules/undici/lib/api/abort-signal.js +59 -0
- package/vendor/node_modules/undici/lib/api/api-connect.js +110 -0
- package/vendor/node_modules/undici/lib/api/api-pipeline.js +252 -0
- package/vendor/node_modules/undici/lib/api/api-request.js +214 -0
- package/vendor/node_modules/undici/lib/api/api-stream.js +209 -0
- package/vendor/node_modules/undici/lib/api/api-upgrade.js +111 -0
- package/vendor/node_modules/undici/lib/api/index.js +7 -0
- package/vendor/node_modules/undici/lib/api/readable.js +580 -0
- package/vendor/node_modules/undici/lib/cache/memory-cache-store.js +279 -0
- package/vendor/node_modules/undici/lib/cache/sqlite-cache-store.js +467 -0
- package/vendor/node_modules/undici/lib/core/connect.js +159 -0
- package/vendor/node_modules/undici/lib/core/constants.js +143 -0
- package/vendor/node_modules/undici/lib/core/diagnostics.js +227 -0
- package/vendor/node_modules/undici/lib/core/errors.js +477 -0
- package/vendor/node_modules/undici/lib/core/request.js +485 -0
- package/vendor/node_modules/undici/lib/core/socks5-client.js +422 -0
- package/vendor/node_modules/undici/lib/core/socks5-utils.js +212 -0
- package/vendor/node_modules/undici/lib/core/symbols.js +76 -0
- package/vendor/node_modules/undici/lib/core/tree.js +160 -0
- package/vendor/node_modules/undici/lib/core/util.js +992 -0
- package/vendor/node_modules/undici/lib/dispatcher/agent.js +162 -0
- package/vendor/node_modules/undici/lib/dispatcher/balanced-pool.js +221 -0
- package/vendor/node_modules/undici/lib/dispatcher/client-h1.js +1744 -0
- package/vendor/node_modules/undici/lib/dispatcher/client-h2.js +1056 -0
- package/vendor/node_modules/undici/lib/dispatcher/client.js +669 -0
- package/vendor/node_modules/undici/lib/dispatcher/dispatcher-base.js +196 -0
- package/vendor/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
- package/vendor/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
- package/vendor/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
- package/vendor/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
- package/vendor/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
- package/vendor/node_modules/undici/lib/dispatcher/pool.js +118 -0
- package/vendor/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
- package/vendor/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
- package/vendor/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
- package/vendor/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
- package/vendor/node_modules/undici/lib/encoding/index.js +33 -0
- package/vendor/node_modules/undici/lib/global.js +59 -0
- package/vendor/node_modules/undici/lib/handler/cache-handler.js +824 -0
- package/vendor/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
- package/vendor/node_modules/undici/lib/handler/decorator-handler.js +67 -0
- package/vendor/node_modules/undici/lib/handler/deduplication-handler.js +504 -0
- package/vendor/node_modules/undici/lib/handler/redirect-handler.js +245 -0
- package/vendor/node_modules/undici/lib/handler/retry-handler.js +504 -0
- package/vendor/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
- package/vendor/node_modules/undici/lib/handler/wrap-handler.js +105 -0
- package/vendor/node_modules/undici/lib/interceptor/cache.js +621 -0
- package/vendor/node_modules/undici/lib/interceptor/decompress.js +561 -0
- package/vendor/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
- package/vendor/node_modules/undici/lib/interceptor/dns.js +571 -0
- package/vendor/node_modules/undici/lib/interceptor/dump.js +99 -0
- package/vendor/node_modules/undici/lib/interceptor/redirect.js +21 -0
- package/vendor/node_modules/undici/lib/interceptor/response-error.js +95 -0
- package/vendor/node_modules/undici/lib/interceptor/retry.js +19 -0
- package/vendor/node_modules/undici/lib/llhttp/.gitkeep +0 -0
- package/vendor/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
- package/vendor/node_modules/undici/lib/llhttp/constants.js +531 -0
- package/vendor/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
- package/vendor/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
- package/vendor/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
- package/vendor/node_modules/undici/lib/llhttp/utils.js +12 -0
- package/vendor/node_modules/undici/lib/mock/mock-agent.js +232 -0
- package/vendor/node_modules/undici/lib/mock/mock-call-history.js +248 -0
- package/vendor/node_modules/undici/lib/mock/mock-client.js +68 -0
- package/vendor/node_modules/undici/lib/mock/mock-errors.js +29 -0
- package/vendor/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
- package/vendor/node_modules/undici/lib/mock/mock-pool.js +68 -0
- package/vendor/node_modules/undici/lib/mock/mock-symbols.js +32 -0
- package/vendor/node_modules/undici/lib/mock/mock-utils.js +486 -0
- package/vendor/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
- package/vendor/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
- package/vendor/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
- package/vendor/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
- package/vendor/node_modules/undici/lib/util/cache.js +676 -0
- package/vendor/node_modules/undici/lib/util/date.js +670 -0
- package/vendor/node_modules/undici/lib/util/promise.js +28 -0
- package/vendor/node_modules/undici/lib/util/runtime-features.js +124 -0
- package/vendor/node_modules/undici/lib/util/stats.js +32 -0
- package/vendor/node_modules/undici/lib/util/timers.js +425 -0
- package/vendor/node_modules/undici/lib/web/cache/cache.js +864 -0
- package/vendor/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
- package/vendor/node_modules/undici/lib/web/cache/util.js +45 -0
- package/vendor/node_modules/undici/lib/web/cookies/constants.js +12 -0
- package/vendor/node_modules/undici/lib/web/cookies/index.js +201 -0
- package/vendor/node_modules/undici/lib/web/cookies/parse.js +315 -0
- package/vendor/node_modules/undici/lib/web/cookies/util.js +371 -0
- package/vendor/node_modules/undici/lib/web/eventsource/eventsource-stream.js +575 -0
- package/vendor/node_modules/undici/lib/web/eventsource/eventsource.js +504 -0
- package/vendor/node_modules/undici/lib/web/eventsource/util.js +29 -0
- package/vendor/node_modules/undici/lib/web/fetch/LICENSE +21 -0
- package/vendor/node_modules/undici/lib/web/fetch/body.js +509 -0
- package/vendor/node_modules/undici/lib/web/fetch/constants.js +131 -0
- package/vendor/node_modules/undici/lib/web/fetch/data-url.js +596 -0
- package/vendor/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
- package/vendor/node_modules/undici/lib/web/fetch/formdata.js +259 -0
- package/vendor/node_modules/undici/lib/web/fetch/global.js +40 -0
- package/vendor/node_modules/undici/lib/web/fetch/headers.js +719 -0
- package/vendor/node_modules/undici/lib/web/fetch/index.js +2413 -0
- package/vendor/node_modules/undici/lib/web/fetch/request.js +1115 -0
- package/vendor/node_modules/undici/lib/web/fetch/response.js +641 -0
- package/vendor/node_modules/undici/lib/web/fetch/util.js +1522 -0
- package/vendor/node_modules/undici/lib/web/infra/index.js +243 -0
- package/vendor/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
- package/vendor/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
- package/vendor/node_modules/undici/lib/web/webidl/index.js +1006 -0
- package/vendor/node_modules/undici/lib/web/websocket/connection.js +339 -0
- package/vendor/node_modules/undici/lib/web/websocket/constants.js +126 -0
- package/vendor/node_modules/undici/lib/web/websocket/events.js +331 -0
- package/vendor/node_modules/undici/lib/web/websocket/frame.js +133 -0
- package/vendor/node_modules/undici/lib/web/websocket/permessage-deflate.js +105 -0
- package/vendor/node_modules/undici/lib/web/websocket/receiver.js +520 -0
- package/vendor/node_modules/undici/lib/web/websocket/sender.js +109 -0
- package/vendor/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
- package/vendor/node_modules/undici/lib/web/websocket/stream/websocketstream.js +496 -0
- package/vendor/node_modules/undici/lib/web/websocket/util.js +347 -0
- package/vendor/node_modules/undici/lib/web/websocket/websocket.js +758 -0
- package/vendor/node_modules/undici/package.json +152 -0
- package/vendor/node_modules/undici/scripts/strip-comments.js +10 -0
- package/vendor/node_modules/undici/types/README.md +6 -0
- package/vendor/node_modules/undici/types/agent.d.ts +32 -0
- package/vendor/node_modules/undici/types/api.d.ts +43 -0
- package/vendor/node_modules/undici/types/balanced-pool.d.ts +30 -0
- package/vendor/node_modules/undici/types/cache-interceptor.d.ts +179 -0
- package/vendor/node_modules/undici/types/cache.d.ts +36 -0
- package/vendor/node_modules/undici/types/client-stats.d.ts +15 -0
- package/vendor/node_modules/undici/types/client.d.ts +153 -0
- package/vendor/node_modules/undici/types/connector.d.ts +36 -0
- package/vendor/node_modules/undici/types/content-type.d.ts +21 -0
- package/vendor/node_modules/undici/types/cookies.d.ts +30 -0
- package/vendor/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
- package/vendor/node_modules/undici/types/dispatcher.d.ts +275 -0
- package/vendor/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
- package/vendor/node_modules/undici/types/errors.d.ts +177 -0
- package/vendor/node_modules/undici/types/eventsource.d.ts +66 -0
- package/vendor/node_modules/undici/types/fetch.d.ts +231 -0
- package/vendor/node_modules/undici/types/formdata.d.ts +114 -0
- package/vendor/node_modules/undici/types/global-dispatcher.d.ts +9 -0
- package/vendor/node_modules/undici/types/global-origin.d.ts +7 -0
- package/vendor/node_modules/undici/types/h2c-client.d.ts +73 -0
- package/vendor/node_modules/undici/types/handlers.d.ts +14 -0
- package/vendor/node_modules/undici/types/header.d.ts +160 -0
- package/vendor/node_modules/undici/types/index.d.ts +91 -0
- package/vendor/node_modules/undici/types/interceptors.d.ts +82 -0
- package/vendor/node_modules/undici/types/mock-agent.d.ts +68 -0
- package/vendor/node_modules/undici/types/mock-call-history.d.ts +111 -0
- package/vendor/node_modules/undici/types/mock-client.d.ts +27 -0
- package/vendor/node_modules/undici/types/mock-errors.d.ts +12 -0
- package/vendor/node_modules/undici/types/mock-interceptor.d.ts +94 -0
- package/vendor/node_modules/undici/types/mock-pool.d.ts +27 -0
- package/vendor/node_modules/undici/types/patch.d.ts +29 -0
- package/vendor/node_modules/undici/types/pool-stats.d.ts +19 -0
- package/vendor/node_modules/undici/types/pool.d.ts +41 -0
- package/vendor/node_modules/undici/types/proxy-agent.d.ts +29 -0
- package/vendor/node_modules/undici/types/readable.d.ts +68 -0
- package/vendor/node_modules/undici/types/retry-agent.d.ts +8 -0
- package/vendor/node_modules/undici/types/retry-handler.d.ts +125 -0
- package/vendor/node_modules/undici/types/round-robin-pool.d.ts +41 -0
- package/vendor/node_modules/undici/types/snapshot-agent.d.ts +109 -0
- package/vendor/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
- package/vendor/node_modules/undici/types/util.d.ts +18 -0
- package/vendor/node_modules/undici/types/utility.d.ts +7 -0
- package/vendor/node_modules/undici/types/webidl.d.ts +347 -0
- package/vendor/node_modules/undici/types/websocket.d.ts +188 -0
package/index.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model proxy switch.
|
|
3
|
+
*
|
|
4
|
+
* While enabled, this plugin installs a process-wide outbound HTTP proxy
|
|
5
|
+
* policy, so the requests Node's fetch would send direct (model API, model
|
|
6
|
+
* discovery, web search, MCP over HTTP) are routed through the configured
|
|
7
|
+
* proxy instead. Disabling it restores the launcher's original routing.
|
|
8
|
+
*
|
|
9
|
+
* The proxy address follows the operating system's proxy setting by default
|
|
10
|
+
* (Windows: the "Internet Settings" registry values), falling back to the
|
|
11
|
+
* plugin's configured URL when detection finds nothing. An optional host
|
|
12
|
+
* allowlist narrows routing to named domains only — loopback always stays
|
|
13
|
+
* direct either way.
|
|
14
|
+
*
|
|
15
|
+
* The harness reads proxy names from its launch environment only: the
|
|
16
|
+
* launcher resolves and installs the policy before the first plugin loads, so
|
|
17
|
+
* writing `process.env` later changes nothing on its own. The policy installer
|
|
18
|
+
* is vendored under `vendor/`, with its `undici` dependency in this package's
|
|
19
|
+
* `node_modules/`: the harness's installation-scope module resolution only
|
|
20
|
+
* reaches packages installed inside the profile, and this one is linked from
|
|
21
|
+
* outside it.
|
|
22
|
+
*
|
|
23
|
+
* The switch is exposed to the browser half through a Typert remote
|
|
24
|
+
* (`modelProxy.status` / `setEnabled` / `setHosts`), and its position is
|
|
25
|
+
* remembered in the harness home so a restart keeps the last choice.
|
|
26
|
+
*/
|
|
27
|
+
import { execFileSync } from 'node:child_process'
|
|
28
|
+
import { appendFileSync, readFileSync, writeFileSync } from 'node:fs'
|
|
29
|
+
import { homedir } from 'node:os'
|
|
30
|
+
import { join } from 'node:path'
|
|
31
|
+
import { installProxyFromEnvironment } from './vendor/http-proxy.js'
|
|
32
|
+
|
|
33
|
+
export const name = 'model-proxy'
|
|
34
|
+
|
|
35
|
+
const DEFAULTS = {
|
|
36
|
+
proxy: 'auto',
|
|
37
|
+
bypass: 'localhost,127.0.0.1,::1',
|
|
38
|
+
hosts: '',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const PACKAGE = 'dsh-model-proxy-switch'
|
|
42
|
+
const SERVICE = 'modelProxy'
|
|
43
|
+
|
|
44
|
+
const HOME = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
45
|
+
|
|
46
|
+
/** Append one line to the plugin's own diagnostic log, best effort. */
|
|
47
|
+
function diag(message) {
|
|
48
|
+
try {
|
|
49
|
+
appendFileSync(join(HOME, 'model-proxy.log'), `${new Date().toISOString()} ${message}\n`)
|
|
50
|
+
} catch {
|
|
51
|
+
// Diagnostics must never break the plugin.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
diag('module loaded')
|
|
56
|
+
|
|
57
|
+
//#region system proxy detection
|
|
58
|
+
function readRegistryValue(name) {
|
|
59
|
+
try {
|
|
60
|
+
const out = execFileSync(
|
|
61
|
+
'reg',
|
|
62
|
+
['query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', '/v', name],
|
|
63
|
+
{ encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] },
|
|
64
|
+
)
|
|
65
|
+
const match = new RegExp(`${name}\\s+REG_(?:SZ|DWORD)\\s+(\\S+)`).exec(out)
|
|
66
|
+
return match?.[1]
|
|
67
|
+
} catch {
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read the operating system's HTTP proxy address.
|
|
74
|
+
*
|
|
75
|
+
* Windows stores it under HKCU Internet Settings: `ProxyServer` is either one
|
|
76
|
+
* `host:port` for every protocol or a semicolon list of `scheme=host:port`
|
|
77
|
+
* entries, and `ProxyEnable` says whether apps should follow it. This plugin
|
|
78
|
+
* is itself an explicit opt-in, so the recorded address is used whenever it
|
|
79
|
+
* is present; `enabled` only changes how the panel labels the source.
|
|
80
|
+
*
|
|
81
|
+
* @returns `{ url, enabled }` with an http(s):// URL, or undefined when
|
|
82
|
+
* nothing usable is recorded.
|
|
83
|
+
*/
|
|
84
|
+
function detectSystemProxy() {
|
|
85
|
+
if (process.platform !== 'win32') return undefined
|
|
86
|
+
try {
|
|
87
|
+
const server = readRegistryValue('ProxyServer')
|
|
88
|
+
if (!server) return undefined
|
|
89
|
+
const byScheme = {}
|
|
90
|
+
for (const part of server.split(';')) {
|
|
91
|
+
const eq = part.indexOf('=')
|
|
92
|
+
if (eq > 0) byScheme[part.slice(0, eq).trim().toLowerCase()] = part.slice(eq + 1).trim()
|
|
93
|
+
}
|
|
94
|
+
const picked = byScheme.https ?? byScheme.http ?? (Object.keys(byScheme).length === 0 ? server.trim() : undefined)
|
|
95
|
+
if (!picked) return undefined
|
|
96
|
+
const url = picked.includes('://') ? picked : `http://${picked}`
|
|
97
|
+
return { url, enabled: readRegistryValue('ProxyEnable') === '0x1' }
|
|
98
|
+
} catch (error) {
|
|
99
|
+
diag(`system proxy detection failed: ${error?.message ?? error}`)
|
|
100
|
+
return undefined
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
|
|
105
|
+
/** The launch environment is a Map of name to `{ value }`, not a plain object. */
|
|
106
|
+
function proxyEnvironment(proxy, bypass) {
|
|
107
|
+
return new Map([
|
|
108
|
+
['http_proxy', { value: proxy }],
|
|
109
|
+
['https_proxy', { value: proxy }],
|
|
110
|
+
['all_proxy', { value: proxy }],
|
|
111
|
+
['no_proxy', { value: bypass }],
|
|
112
|
+
])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function stateFile() {
|
|
116
|
+
return join(HOME, 'model-proxy.json')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readState(fallback) {
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(readFileSync(stateFile(), 'utf8'))
|
|
122
|
+
return {
|
|
123
|
+
enabled: typeof parsed?.enabled === 'boolean' ? parsed.enabled : fallback.enabled,
|
|
124
|
+
hosts: typeof parsed?.hosts === 'string' ? parsed.hosts : fallback.hosts,
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
return fallback
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function writeState(state) {
|
|
132
|
+
try {
|
|
133
|
+
writeFileSync(stateFile(), `${JSON.stringify(state, null, 2)}\n`, 'utf8')
|
|
134
|
+
} catch {
|
|
135
|
+
// A read-only home must not break the switch for this run.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#region typert face
|
|
140
|
+
const codec = (typeSymbol, parse) => ({ mode: 'strict', typeSymbol: `${PACKAGE}#${typeSymbol}`, create: () => ({ parse }) })
|
|
141
|
+
const booleanValue = codec('Boolean', (value) => {
|
|
142
|
+
if (typeof value !== 'boolean') throw new Error(`Expected a boolean, got ${typeof value}`)
|
|
143
|
+
return value
|
|
144
|
+
})
|
|
145
|
+
const hostsValue = codec('Hosts', (value) => {
|
|
146
|
+
if (typeof value !== 'string') throw new Error(`Expected a string, got ${typeof value}`)
|
|
147
|
+
if (value.length > 2048) throw new Error('Host list is too long')
|
|
148
|
+
return value
|
|
149
|
+
})
|
|
150
|
+
const jsonResult = codec('ModelProxyStatus', (value) => value)
|
|
151
|
+
|
|
152
|
+
const methodDescriptor = (method, parameters) => ({
|
|
153
|
+
id: `${PACKAGE}#${SERVICE}/${method}`,
|
|
154
|
+
service: SERVICE,
|
|
155
|
+
namespace: SERVICE,
|
|
156
|
+
method,
|
|
157
|
+
invocation: { kind: 'direct' },
|
|
158
|
+
result: jsonResult,
|
|
159
|
+
parameters,
|
|
160
|
+
})
|
|
161
|
+
const jsonParameter = (name, parameterCodec) => ({ name, wire: name, source: 'json', codec: parameterCodec })
|
|
162
|
+
|
|
163
|
+
const descriptors = [
|
|
164
|
+
methodDescriptor('status', []),
|
|
165
|
+
methodDescriptor('setEnabled', [jsonParameter('enabled', booleanValue)]),
|
|
166
|
+
methodDescriptor('setHosts', [jsonParameter('hosts', hostsValue)]),
|
|
167
|
+
]
|
|
168
|
+
//#endregion
|
|
169
|
+
|
|
170
|
+
export function apply(ctx, config) {
|
|
171
|
+
const configuredProxy = String(config?.proxy ?? DEFAULTS.proxy).trim()
|
|
172
|
+
const bypass = String(config?.bypass ?? DEFAULTS.bypass).trim()
|
|
173
|
+
const state = readState({ enabled: true, hosts: String(config?.hosts ?? DEFAULTS.hosts) })
|
|
174
|
+
|
|
175
|
+
let enabled = state.enabled
|
|
176
|
+
let hosts = state.hosts
|
|
177
|
+
let dispose = undefined
|
|
178
|
+
/** The proxy actually installed, and where it came from. */
|
|
179
|
+
let activeProxy = undefined
|
|
180
|
+
let proxySource = 'none'
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Detection spawns a `reg` child process, so reads are cached briefly:
|
|
184
|
+
* status calls (panel refresh) share one read per TTL, while explicit user
|
|
185
|
+
* actions always force a fresh read.
|
|
186
|
+
*/
|
|
187
|
+
const DETECTION_TTL_MS = 10_000
|
|
188
|
+
let detectionCache = { at: 0, value: undefined }
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Resolve which proxy URL would be installed right now. `auto` follows the
|
|
192
|
+
* operating system's recorded proxy address so swapping proxy software
|
|
193
|
+
* needs no edit here.
|
|
194
|
+
*/
|
|
195
|
+
function resolveProxy(options = {}) {
|
|
196
|
+
if (configuredProxy !== '' && configuredProxy.toLowerCase() !== 'auto') {
|
|
197
|
+
return { url: configuredProxy, source: 'config' }
|
|
198
|
+
}
|
|
199
|
+
const now = Date.now()
|
|
200
|
+
if (!options.fresh && detectionCache.value !== undefined && now - detectionCache.at < DETECTION_TTL_MS) {
|
|
201
|
+
return detectionCache.value
|
|
202
|
+
}
|
|
203
|
+
const detected = detectSystemProxy()
|
|
204
|
+
const value =
|
|
205
|
+
detected === undefined
|
|
206
|
+
? { url: undefined, source: 'none' }
|
|
207
|
+
: { url: detected.url, source: detected.enabled ? 'system' : 'registry' }
|
|
208
|
+
detectionCache = { at: now, value }
|
|
209
|
+
return value
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const install = async () => {
|
|
213
|
+
if (!enabled || dispose !== undefined) return
|
|
214
|
+
// Explicit actions (switch on, host-list save, startup) read fresh.
|
|
215
|
+
const { url, source } = resolveProxy({ fresh: true })
|
|
216
|
+
if (url === undefined) {
|
|
217
|
+
activeProxy = undefined
|
|
218
|
+
proxySource = 'none'
|
|
219
|
+
diag('install skipped: no system proxy detected and no fallback configured')
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
let scheme
|
|
223
|
+
try {
|
|
224
|
+
scheme = new URL(url).protocol
|
|
225
|
+
} catch {
|
|
226
|
+
diag(`install refused: unparseable proxy URL ${JSON.stringify(url)}`)
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
if (scheme !== 'http:' && scheme !== 'https:') {
|
|
230
|
+
diag(`install refused: unsupported ${scheme} proxy`)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
dispose = await installProxyFromEnvironment(
|
|
234
|
+
proxyEnvironment(url, bypass),
|
|
235
|
+
(message) => diag(`policy: ${message}`),
|
|
236
|
+
{ hosts },
|
|
237
|
+
)
|
|
238
|
+
activeProxy = url
|
|
239
|
+
proxySource = source
|
|
240
|
+
diag(`policy installed: ${url} (${source}), hosts=${hosts === '' ? '<all>' : hosts}`)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const uninstall = async () => {
|
|
244
|
+
if (dispose === undefined) return
|
|
245
|
+
const pending = dispose
|
|
246
|
+
dispose = undefined
|
|
247
|
+
await pending()
|
|
248
|
+
diag('policy disposed')
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
ctx.effect(async () => {
|
|
252
|
+
try {
|
|
253
|
+
await install()
|
|
254
|
+
} catch (error) {
|
|
255
|
+
diag(`install failed: ${error?.stack ?? error}`)
|
|
256
|
+
console.warn('[model-proxy] install failed', error)
|
|
257
|
+
}
|
|
258
|
+
return async () => {
|
|
259
|
+
try {
|
|
260
|
+
await uninstall()
|
|
261
|
+
} catch (error) {
|
|
262
|
+
diag(`uninstall failed: ${error?.stack ?? error}`)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Re-read the proxy source and bring the installed policy in line with it.
|
|
269
|
+
* Detection used to run only at install time, so flipping the system proxy
|
|
270
|
+
* afterwards left the panel showing a stale address and label.
|
|
271
|
+
*/
|
|
272
|
+
const reconcile = async () => {
|
|
273
|
+
const resolved = resolveProxy()
|
|
274
|
+
if (!enabled) {
|
|
275
|
+
activeProxy = resolved.url
|
|
276
|
+
proxySource = resolved.source
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
if (dispose !== undefined && resolved.url === activeProxy) {
|
|
280
|
+
// Same address, only the provenance changed (system proxy flipped).
|
|
281
|
+
if (resolved.source !== 'none') proxySource = resolved.source
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if (dispose !== undefined) await uninstall()
|
|
285
|
+
activeProxy = resolved.url
|
|
286
|
+
proxySource = resolved.source
|
|
287
|
+
if (resolved.url !== undefined) await install()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const snapshot = () => ({ enabled, proxy: activeProxy, proxySource, hosts })
|
|
291
|
+
|
|
292
|
+
const service = {
|
|
293
|
+
status: () => {
|
|
294
|
+
diag('remote status()')
|
|
295
|
+
return reconcile().then(snapshot)
|
|
296
|
+
},
|
|
297
|
+
setEnabled(next) {
|
|
298
|
+
diag(`remote setEnabled(${next})`)
|
|
299
|
+
enabled = next
|
|
300
|
+
writeState({ enabled, hosts })
|
|
301
|
+
return (next ? install() : uninstall()).then(snapshot)
|
|
302
|
+
},
|
|
303
|
+
setHosts(next) {
|
|
304
|
+
const trimmed = next.trim()
|
|
305
|
+
diag(`remote setHosts(${JSON.stringify(trimmed)})`)
|
|
306
|
+
hosts = trimmed
|
|
307
|
+
writeState({ enabled, hosts })
|
|
308
|
+
const swap = async () => {
|
|
309
|
+
if (dispose === undefined) return
|
|
310
|
+
await uninstall()
|
|
311
|
+
await install()
|
|
312
|
+
}
|
|
313
|
+
return swap().then(snapshot)
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
ctx.inject(['typert'], (inner) => {
|
|
318
|
+
try {
|
|
319
|
+
// The gateway validates the service by reading its `typertRemote`
|
|
320
|
+
// binding, normally created by extending TypertRemoteService from
|
|
321
|
+
// @deepseek-ai/dsh-typert-protocol. That package is unreachable from a
|
|
322
|
+
// link-installed bundle, so the binding is constructed inline: a frozen
|
|
323
|
+
// { service, serviceKey, namespace } triple is the whole contract.
|
|
324
|
+
Object.assign(service, {
|
|
325
|
+
typertRemote: Object.freeze({ service, serviceKey: SERVICE, namespace: SERVICE }),
|
|
326
|
+
})
|
|
327
|
+
inner.provide(SERVICE, service)
|
|
328
|
+
inner.typert.register({
|
|
329
|
+
package: PACKAGE,
|
|
330
|
+
face: 'host',
|
|
331
|
+
schemas: [],
|
|
332
|
+
model: { services: [], events: [], objects: [] },
|
|
333
|
+
invocations: descriptors,
|
|
334
|
+
})
|
|
335
|
+
diag('typert face registered')
|
|
336
|
+
} catch (error) {
|
|
337
|
+
diag(`typert registration failed: ${error?.stack ?? error}`)
|
|
338
|
+
console.warn('[model-proxy] remote registration failed; the switch keeps its last state', error)
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
}
|
package/locale/en.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "Model Proxy Switch",
|
|
4
|
+
"description": "Sidebar switch: routes model API and model-list requests through a local HTTP proxy (follows the system proxy by default); off restores direct connections."
|
|
5
|
+
},
|
|
6
|
+
"nav": "Proxy",
|
|
7
|
+
"title": "Model proxy",
|
|
8
|
+
"summary": "Routes model API and model-list requests through an HTTP proxy while on; off means direct.",
|
|
9
|
+
"stateOn": "On",
|
|
10
|
+
"stateOff": "Off",
|
|
11
|
+
"label": "Use proxy",
|
|
12
|
+
"proxy": "Proxy URL",
|
|
13
|
+
"sourceSystem": "from system settings",
|
|
14
|
+
"sourceRegistry": "from registry (system proxy off)",
|
|
15
|
+
"sourceConfig": "from plugin config",
|
|
16
|
+
"sourceNone": "no system proxy detected",
|
|
17
|
+
"hostsLabel": "Only proxy these hosts",
|
|
18
|
+
"hostsHint": "Comma-separated; empty means every external address. Example: api.wufannao.cc.cd",
|
|
19
|
+
"save": "Save",
|
|
20
|
+
"saved": "Saved",
|
|
21
|
+
"scope": "Process-wide switch: model discovery, model calls, web search and MCP share this route. Loopback always stays direct.",
|
|
22
|
+
"saving": "Switching…",
|
|
23
|
+
"readFailed": "Could not read the state",
|
|
24
|
+
"writeFailed": "Switch failed"
|
|
25
|
+
}
|
package/locale/zh.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "模型代理开关",
|
|
4
|
+
"description": "侧栏开关:让模型 API、获取模型列表等出网请求走本地 HTTP 代理(默认跟随系统代理设置),关闭即恢复直连。"
|
|
5
|
+
},
|
|
6
|
+
"nav": "代理",
|
|
7
|
+
"title": "模型代理",
|
|
8
|
+
"summary": "让模型 API、获取模型列表等出网请求走 HTTP 代理;关闭即直连。",
|
|
9
|
+
"stateOn": "已开启",
|
|
10
|
+
"stateOff": "已关闭",
|
|
11
|
+
"label": "使用代理",
|
|
12
|
+
"proxy": "代理地址",
|
|
13
|
+
"sourceSystem": "跟随系统代理",
|
|
14
|
+
"sourceRegistry": "注册表记录(系统代理未开启)",
|
|
15
|
+
"sourceConfig": "插件配置",
|
|
16
|
+
"sourceNone": "未检测到系统代理",
|
|
17
|
+
"hostsLabel": "只对这些域名走代理",
|
|
18
|
+
"hostsHint": "逗号分隔,留空表示所有外部地址。例:api.wufannao.cc.cd",
|
|
19
|
+
"save": "保存",
|
|
20
|
+
"saved": "已保存",
|
|
21
|
+
"scope": "开关作用于整个宿主进程的出网请求:获取模型列表、模型调用、网页搜索、MCP 都走同一条链路;本机回环始终直连。",
|
|
22
|
+
"saving": "正在切换…",
|
|
23
|
+
"readFailed": "读取状态失败",
|
|
24
|
+
"writeFailed": "切换失败"
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-proxy-switch",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Sidebar switch that routes model API and model-list requests through an HTTP proxy (follows the system proxy by default); off restores direct connections. Optional per-host allowlist.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"dsh",
|
|
8
|
+
"dsh-plugin",
|
|
9
|
+
"deepseek-harness",
|
|
10
|
+
"cordis-plugin",
|
|
11
|
+
"proxy",
|
|
12
|
+
"http-proxy",
|
|
13
|
+
"model"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "index.js",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./index.js",
|
|
20
|
+
"./client": "./client.js",
|
|
21
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
22
|
+
"./locale/*.json": "./locale/*.json",
|
|
23
|
+
"./icon": "./icon.svg",
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"client.js",
|
|
29
|
+
"cordis.patch.yml",
|
|
30
|
+
"locale/*.json",
|
|
31
|
+
"icon.svg",
|
|
32
|
+
"vendor/",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"icon": "./icon.svg",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22",
|
|
38
|
+
"dsh": ">=0.2.0-0 <0.3.0-0"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/qinghuan50/dsh-model-proxy-switch.git"
|
|
43
|
+
},
|
|
44
|
+
"dsh": {
|
|
45
|
+
"bundle": {
|
|
46
|
+
"patch": "./cordis.patch.yml"
|
|
47
|
+
},
|
|
48
|
+
"client": {
|
|
49
|
+
"platform": "web",
|
|
50
|
+
"immediately": true,
|
|
51
|
+
"inject": [
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
53
|
+
"@deepseek-ai/dsh-client-locale"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|