Files
deepseek-harness/scripts/apply-source-dsh-http-proxy.ts
Coder ed152416d5
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
feat: add searxng web-search provider, openrouter cost balance UI, offline scripts; update source-launch and docs
2026-08-20 13:01:40 +07:00

176 lines
6.1 KiB
TypeScript

/**
* Overlay HTTP proxy variables onto the source `dsh` Node process only.
* Bootstrap proxy names cannot come from `.env` files; this overlay is the
* checkout-local way to give OpenRouter (and other HTTPS fetches) an HTTP
* proxy without changing the calling shell.
* @module
*/
import { existsSync, readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
/** Repo-root file the source launcher reads when present; gitignored. */
export const SOURCE_DSH_HTTP_PROXY_FILE = '.dsh-http-proxy.env'
/** Process variables Node, undici, and pi-ai consult for HTTP(S) proxies. */
export const SOURCE_DSH_PROXY_VAR_NAMES = [
'HTTPS_PROXY',
'HTTP_PROXY',
'ALL_PROXY',
'https_proxy',
'http_proxy',
'all_proxy',
] as const
const PROXY_VAR_NAME_SET = new Set<string>(SOURCE_DSH_PROXY_VAR_NAMES)
const LOCAL_NO_PROXY = 'localhost,127.0.0.1,::1'
/**
* Write one HTTP(S) proxy URL onto every name Node and pi-ai read, including
* leftover inherited SOCKS values the overlay file did not mention.
*/
function installHttpProxy(env: NodeJS.ProcessEnv, url: string): void {
for (const name of SOURCE_DSH_PROXY_VAR_NAMES) {
env[name] = url
}
env.NODE_USE_ENV_PROXY = '1'
if (!env.NO_PROXY && !env.no_proxy) {
env.NO_PROXY = LOCAL_NO_PROXY
env.no_proxy = LOCAL_NO_PROXY
}
}
/**
* First non-SOCKS proxy URL among the names Node consults.
* @param env - environment after overlay assignments.
*/
function firstHttpProxyUrl(env: NodeJS.ProcessEnv): string | undefined {
for (const name of SOURCE_DSH_PROXY_VAR_NAMES) {
const value = env[name]?.trim()
if (value !== undefined && value !== '' && !isSocksProxyUrl(value)) return value
}
return undefined
}
/**
* Set on the child after this wrapper respawns so `fetch` sees the HTTP overlay
* at process start. Node `--env-file` does not replace an inherited SOCKS
* `HTTPS_PROXY`, and `NODE_USE_ENV_PROXY` assigned after start is ignored.
*/
export const SOURCE_DSH_PROXY_REEXEC_MARKER = 'DSH_SOURCE_HTTP_PROXY_APPLIED'
/** @returns whether `value` is a SOCKS URL Node cannot use for fetch/pnpm. */
export function isSocksProxyUrl(value: string): boolean {
return /^socks5h?:\/\//i.test(value.trim())
}
/**
* Parse a dotenv-like overlay file into assignments.
* @param text - file contents.
* @returns name → value; empty lines and `#` comments are skipped.
*/
export function parseSourceDshHttpProxyFile(text: string): Record<string, string> {
const out: Record<string, string> = {}
for (const raw of text.split(/\r?\n/)) {
const line = raw.trim()
if (line === '' || line.startsWith('#')) continue
const body = line.startsWith('export ') ? line.slice('export '.length).trim() : line
const eq = body.indexOf('=')
if (eq <= 0) continue
const name = body.slice(0, eq).trim()
let value = body.slice(eq + 1).trim()
if (
(value.startsWith('"') && value.endsWith('"'))
|| (value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1)
}
if (name !== '') out[name] = value
}
return out
}
/**
* Inputs for {@link applySourceDshHttpProxy}. Tests pass a fake env and file text.
*/
export interface ApplySourceDshHttpProxyInput {
/** Mutable environment of this Node process (normally `process.env`). */
env: NodeJS.ProcessEnv
/** Contents of `.dsh-http-proxy.env`; `null` when the file is absent. */
fileText: string | null
/** Optional explicit HTTP overlay URL (`DSH_HTTP_PROXY`), which outranks the file. */
overlayUrl?: string | undefined
}
/**
* Apply an HTTP proxy overlay onto `env` for this process only.
* @param input - env, optional file, optional `DSH_HTTP_PROXY`.
* @throws when the winning overlay is a SOCKS URL.
*/
export function applySourceDshHttpProxy(input: ApplySourceDshHttpProxyInput): void {
const overlayUrl = input.overlayUrl?.trim()
if (overlayUrl) {
if (isSocksProxyUrl(overlayUrl)) {
throw new Error(
`DSH_HTTP_PROXY=${overlayUrl}. Node cannot fetch through SOCKS. Use an HTTP proxy (for example http://127.0.0.1:3067).`,
)
}
installHttpProxy(input.env, overlayUrl)
return
}
if (input.fileText === null) return
const assignments = parseSourceDshHttpProxyFile(input.fileText)
for (const [name, value] of Object.entries(assignments)) {
if (PROXY_VAR_NAME_SET.has(name) && isSocksProxyUrl(value)) {
throw new Error(
`${name}=${value}. Node cannot fetch through SOCKS. Use an HTTP proxy (for example http://127.0.0.1:3067).`,
)
}
input.env[name] = value
}
const httpUrl = firstHttpProxyUrl(input.env)
if (httpUrl !== undefined) installHttpProxy(input.env, httpUrl)
}
/**
* HTTP overlay URL now on `env`, if any.
* @param env - environment after {@link applySourceDshHttpProxy}.
*/
export function sourceDshAppliedHttpProxyUrl(env: NodeJS.ProcessEnv): string | undefined {
return firstHttpProxyUrl(env)
}
/** Absolute path of the gitignored overlay file beside this repository root. */
export function sourceDshHttpProxyFilePath(): string {
return fileURLToPath(new URL(`../${SOURCE_DSH_HTTP_PROXY_FILE}`, import.meta.url))
}
/**
* Read the overlay file when it exists.
* @param path - absolute path; defaults to the repo-root gitignored file.
* @returns file text, or `null` when absent.
*/
export function readSourceDshHttpProxyFile(path = sourceDshHttpProxyFilePath()): string | null {
if (!existsSync(path)) return null
return readFileSync(path, 'utf8')
}
/**
* Whether an overlay source is present (file or `DSH_HTTP_PROXY`).
* @param overlayUrl - `DSH_HTTP_PROXY` value.
* @param fileText - overlay file contents, or `null` when absent.
*/
export function sourceDshHttpProxyOverlayPresent(overlayUrl: string | undefined, fileText: string | null): boolean {
return Boolean(overlayUrl?.trim()) || fileText !== null
}
/**
* Whether this process must respawn so `fetch` binds the overlay at start.
* @param env - the environment after {@link applySourceDshHttpProxy}.
* @param overlayPresent - {@link sourceDshHttpProxyOverlayPresent}.
*/
export function shouldReexecSourceDshForFetchProxy(env: NodeJS.ProcessEnv, overlayPresent: boolean): boolean {
return overlayPresent && env[SOURCE_DSH_PROXY_REEXEC_MARKER] !== '1'
}