Some checks failed
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / windows node 24 / wine blocking (push) Has been skipped
CI / wine apt cache (push) Successful in 58s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 2m46s
Deploy documentation / deploy (push) Has been skipped
E2E (real DeepSeek API) / e2e (push) Failing after 1m18s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m18s
Landlock Run / Matrix (push) Successful in 13s
Release (vendor) / Pack npm tarballs (push) Failing after 3m43s
Release (dsh) / Pack npm tarballs (push) Failing after 1m53s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m51s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
36 lines
4.1 KiB
Markdown
36 lines
4.1 KiB
Markdown
# Agent Note: Source dsh HTTP proxy overlay
|
||
|
||
Status: implemented
|
||
|
||
[English](2026-08-19-source-dsh-http-proxy-overlay.md) | 中文
|
||
|
||
## 问题
|
||
|
||
环境中只有 SOCKS 代理的机器可以用 curl 访问 OpenRouter,但 `pnpm dsh` 的 HTTPS fetch 仍会失败:Node、undici 和 pi-ai 接受 `http://` / `https://` 代理 URL,并拒绝 `socks5` / `socks5h`。在调用方的 PowerShell 或 bash 会话里设置 `HTTPS_PROXY` 也会让该会话中后续命令走这个 HTTP 代理。产品 `.env` 文件不能提供 `HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY` 或 `NO_PROXY`,因为这些名称只能来自进程启动环境。在 Node 启动之后再往 `process.env` 写入这些名称不会重新绑定 `fetch`:undici 在进程启动时读取 `NODE_USE_ENV_PROXY` 和 `HTTPS_PROXY`。Node `--env-file-if-exists` 同样不会替换已继承的名称,因此用户级的 `HTTPS_PROXY=socks5h://…` 仍然生效。
|
||
|
||
## 决策
|
||
|
||
根目录的 `dsh` 脚本以 `node --import tsx/esm scripts/run-source-dsh.ts` 运行 [`scripts/run-source-dsh.ts`](../../../../scripts/run-source-dsh.ts)。[`applySourceDshHttpProxy`](../../../../scripts/apply-source-dsh-http-proxy.ts) 把 HTTP 代理变量写入 `process.env` 的副本。当被 gitignore 的 [`.dsh-http-proxy.env`](../../../../.gitignore) 或 `DSH_HTTP_PROXY` 提供覆盖层且未设置 `DSH_SOURCE_HTTP_PROXY_APPLIED` 时,包装层用该环境按相同的 Node argv 再拉起一次进程,以便在 `fetch` 初始化之前就存在 `NODE_USE_ENV_PROXY=1` 和 HTTP 代理 URL。该标记防止第二次再拉起。调用方 shell 不变。
|
||
|
||
当 `DSH_HTTP_PROXY` 设为非空的 HTTP(S) URL 时,它会覆盖 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`(两种大小写)并设置 `NODE_USE_ENV_PROXY=1`。当 `NO_PROXY` 与 `no_proxy` 都未设置时,它将它们设为 `localhost,127.0.0.1,::1`。否则先应用覆盖文件中解析出的赋值,再把这六个名称上残留的 SOCKS 值换成文件中的 HTTP URL,以免漏写的 `ALL_PROXY` 仍指向 `socks5h://`。最终生效的覆盖层若为 `socks5` 或 `socks5h`,会在 CLI 启动前失败。两种来源都不存在时,继承的环境保持不变,也不会再拉起进程。包装层在再拉起之前会把生效的 HTTP URL 打印到 stderr 一次。
|
||
|
||
已安装的 `apps/cli/lib/bin.js` 路径不变:它没有检出级覆盖文件。
|
||
|
||
## 考虑过的备选方案
|
||
|
||
**让用户在运行 `pnpm dsh` 之前于 shell 中导出 `HTTPS_PROXY`。**这样可以访问 OpenRouter,但这些变量会留在该会话的后续命令上,包括本应继续使用 SOCKS 代理的工具。
|
||
|
||
**把代理名称放进调用目录的 `.env` 或 `$DSH_HOME/.env`。**被[配置来源归属决策](../architecture/2026-08-04-configuration-source-ownership.md)拒绝:网络启动变量只能来自继承的进程环境。
|
||
|
||
**仅在包装层内、Node 启动之后赋值 `NODE_USE_ENV_PROXY`。** OpenRouter 流量走 `fetch`;Node 在进程启动时绑定 env-proxy dispatcher,随后再写 `process.env` 不会改变它。
|
||
|
||
**使用 Node `--env-file-if-exists` 且不再拉起进程。**该文件不会替换父进程已经设置的名称,因此继承来的 `socks5h://` `HTTPS_PROXY` 仍会到达 `fetch`。
|
||
|
||
## 影响
|
||
|
||
源码路径的 `pnpm dsh` 可以使用 HTTP 覆盖层,而不修改调用方 shell。继承来的 SOCKS `HTTPS_PROXY` 会在再拉起的 Node 进程中被替换,而不是在 shell 中。没有 `DSH_HTTP_PROXY` 或 `.dsh-http-proxy.env` 的贡献者仍看到原来的继承行为,也不会多一个进程。仅有 SOCKS 的覆盖层会立即失败,而不是在访问 OpenRouter 时超时。已发布的 bin 在宿主需要代理时,仍要求调用方传入 HTTP 代理变量。命令行不加 `--use-env-proxy`:CI 中的 Node 22.19 会拒绝该 flag。
|
||
|
||
## 测试
|
||
|
||
`scripts/apply-source-dsh-http-proxy.spec.ts` 覆盖无覆盖层、`DSH_HTTP_PROXY` 覆盖 SOCKS、文件解析、文件与 `DSH_HTTP_PROXY` 的优先级、文件未写出的名称上残留的 SOCKS、SOCKS 拒绝,以及一次性再拉起标记。`apps/cli/tests/source-launch.compat.spec.ts` 将根目录 `dsh` 命令钉在 `scripts/run-source-dsh.ts` 上,并对该启动向量做冒烟测试。
|