feat(coding-agent): add settings http proxy

closes #5790
This commit is contained in:
Armin Ronacher
2026-06-16 18:53:19 +02:00
parent 0680726ac4
commit 910508595a
6 changed files with 83 additions and 4 deletions

View File

@@ -4,6 +4,7 @@
### Added
- Added a global `httpProxy` setting that applies as `HTTP_PROXY` and `HTTPS_PROXY` for Pi-managed HTTP clients ([#5790](https://github.com/earendil-works/pi/issues/5790)).
- Added `auth.json` API key `env` values so provider-specific environment overrides can be scoped to Pi and propagated to inherited provider configuration ([#5728](https://github.com/earendil-works/pi/issues/5728)).
### Changed

View File

@@ -69,6 +69,18 @@ Use `/trust` in interactive mode to save a project trust decision for future ses
Set `PI_SKIP_VERSION_CHECK=1` to disable the Pi version update check. Use `--offline` or `PI_OFFLINE=1` to disable all startup network operations described here, including update checks, package update checks, and install/update telemetry.
### Network
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `httpProxy` | string | - | HTTP proxy URL applied as `HTTP_PROXY` and `HTTPS_PROXY`. Global setting only. |
```json
{
"httpProxy": "http://127.0.0.1:7890"
}
```
### Warnings
| Setting | Type | Default | Description |

View File

@@ -36,6 +36,13 @@ export function formatHttpIdleTimeoutMs(timeoutMs: number): string {
return `${timeoutMs / 1000} sec`;
}
export function applyHttpProxySettings(httpProxy: string | undefined): void {
const proxy = httpProxy?.trim();
if (!proxy) return;
process.env.HTTP_PROXY ??= proxy;
process.env.HTTPS_PROXY ??= proxy;
}
export function configureHttpDispatcher(timeoutMs: number = DEFAULT_HTTP_IDLE_TIMEOUT_MS): void {
const normalizedTimeoutMs = parseHttpIdleTimeoutMs(timeoutMs);
if (normalizedTimeoutMs === undefined) {

View File

@@ -117,6 +117,7 @@ export interface Settings {
markdown?: MarkdownSettings;
warnings?: WarningSettings;
sessionDir?: string; // Custom session storage directory (same format as --session-dir CLI flag)
httpProxy?: string; // Proxy URL applied as HTTP_PROXY and HTTPS_PROXY for Pi-managed HTTP clients
httpIdleTimeoutMs?: number; // HTTP header/body idle timeout in milliseconds; 0 disables it
websocketConnectTimeoutMs?: number; // WebSocket connect/open handshake timeout in milliseconds; 0 disables it
}

View File

@@ -26,7 +26,7 @@ import { formatNoModelsAvailableMessage } from "./core/auth-guidance.ts";
import { AuthStorage } from "./core/auth-storage.ts";
import { exportFromFile } from "./core/export-html/index.ts";
import type { ExtensionFactory } from "./core/extensions/types.ts";
import { configureHttpDispatcher } from "./core/http-dispatcher.ts";
import { applyHttpProxySettings, configureHttpDispatcher } from "./core/http-dispatcher.ts";
import type { ModelRegistry } from "./core/model-registry.ts";
import { resolveCliModel, resolveModelScope, type ScopedModel } from "./core/model-resolver.ts";
import { restoreStdout, takeOverStdout } from "./core/output-guard.ts";
@@ -466,6 +466,12 @@ export async function main(args: string[], options?: MainOptions) {
cleanupWindowsSelfUpdateQuarantine(getPackageDir());
}
const cwd = process.cwd();
const agentDir = getAgentDir();
const bootstrapSettingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
applyHttpProxySettings(bootstrapSettingsManager.getGlobalSettings().httpProxy);
configureHttpDispatcher();
if (await handlePackageCommand(args, { extensionFactories: options?.extensionFactories })) {
const exitCode = process.exitCode ?? 0;
if (process.platform === "win32" && exitCode === 0 && args[0] === "update") {
@@ -529,11 +535,9 @@ export async function main(args: string[], options?: MainOptions) {
validateSessionIdFlags(parsed);
// Run migrations (pass cwd for project-local migrations)
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(cwd);
time("runMigrations");
const cwd = process.cwd();
const agentDir = getAgentDir();
const startupSettingsManager = SettingsManager.create(cwd, agentDir);
reportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, "startup session lookup"));
@@ -726,6 +730,7 @@ export async function main(args: string[], options?: MainOptions) {
time("createAgentSessionRuntime");
const { services, session, modelFallbackMessage } = runtime;
const { settingsManager, modelRegistry, resourceLoader } = services;
applyHttpProxySettings(settingsManager.getGlobalSettings().httpProxy);
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
if (parsed.help) {

View File

@@ -0,0 +1,53 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { applyHttpProxySettings } from "../src/core/http-dispatcher.ts";
const PROXY_ENV_KEYS = ["HTTP_PROXY", "HTTPS_PROXY"] as const;
describe("http proxy settings", () => {
let savedEnv: Record<(typeof PROXY_ENV_KEYS)[number], string | undefined>;
beforeEach(() => {
savedEnv = Object.fromEntries(PROXY_ENV_KEYS.map((key) => [key, process.env[key]])) as Record<
(typeof PROXY_ENV_KEYS)[number],
string | undefined
>;
for (const key of PROXY_ENV_KEYS) {
delete process.env[key];
}
});
afterEach(() => {
for (const key of PROXY_ENV_KEYS) {
const value = savedEnv[key];
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
});
it("applies httpProxy to HTTP_PROXY and HTTPS_PROXY", () => {
applyHttpProxySettings("http://127.0.0.1:7890");
expect(process.env.HTTP_PROXY).toBe("http://127.0.0.1:7890");
expect(process.env.HTTPS_PROXY).toBe("http://127.0.0.1:7890");
});
it("does not override existing proxy env vars", () => {
process.env.HTTP_PROXY = "http://env-http:8080";
process.env.HTTPS_PROXY = "http://env-https:8080";
applyHttpProxySettings("http://settings:7890");
expect(process.env.HTTP_PROXY).toBe("http://env-http:8080");
expect(process.env.HTTPS_PROXY).toBe("http://env-https:8080");
});
it("ignores empty values", () => {
applyHttpProxySettings(" ");
expect(process.env.HTTP_PROXY).toBeUndefined();
expect(process.env.HTTPS_PROXY).toBeUndefined();
});
});