@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Added
|
### 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)).
|
- 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
|
### Changed
|
||||||
|
|||||||
@@ -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.
|
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
|
### Warnings
|
||||||
|
|
||||||
| Setting | Type | Default | Description |
|
| Setting | Type | Default | Description |
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ export function formatHttpIdleTimeoutMs(timeoutMs: number): string {
|
|||||||
return `${timeoutMs / 1000} sec`;
|
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 {
|
export function configureHttpDispatcher(timeoutMs: number = DEFAULT_HTTP_IDLE_TIMEOUT_MS): void {
|
||||||
const normalizedTimeoutMs = parseHttpIdleTimeoutMs(timeoutMs);
|
const normalizedTimeoutMs = parseHttpIdleTimeoutMs(timeoutMs);
|
||||||
if (normalizedTimeoutMs === undefined) {
|
if (normalizedTimeoutMs === undefined) {
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export interface Settings {
|
|||||||
markdown?: MarkdownSettings;
|
markdown?: MarkdownSettings;
|
||||||
warnings?: WarningSettings;
|
warnings?: WarningSettings;
|
||||||
sessionDir?: string; // Custom session storage directory (same format as --session-dir CLI flag)
|
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
|
httpIdleTimeoutMs?: number; // HTTP header/body idle timeout in milliseconds; 0 disables it
|
||||||
websocketConnectTimeoutMs?: number; // WebSocket connect/open handshake timeout in milliseconds; 0 disables it
|
websocketConnectTimeoutMs?: number; // WebSocket connect/open handshake timeout in milliseconds; 0 disables it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { formatNoModelsAvailableMessage } from "./core/auth-guidance.ts";
|
|||||||
import { AuthStorage } from "./core/auth-storage.ts";
|
import { AuthStorage } from "./core/auth-storage.ts";
|
||||||
import { exportFromFile } from "./core/export-html/index.ts";
|
import { exportFromFile } from "./core/export-html/index.ts";
|
||||||
import type { ExtensionFactory } from "./core/extensions/types.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 type { ModelRegistry } from "./core/model-registry.ts";
|
||||||
import { resolveCliModel, resolveModelScope, type ScopedModel } from "./core/model-resolver.ts";
|
import { resolveCliModel, resolveModelScope, type ScopedModel } from "./core/model-resolver.ts";
|
||||||
import { restoreStdout, takeOverStdout } from "./core/output-guard.ts";
|
import { restoreStdout, takeOverStdout } from "./core/output-guard.ts";
|
||||||
@@ -466,6 +466,12 @@ export async function main(args: string[], options?: MainOptions) {
|
|||||||
cleanupWindowsSelfUpdateQuarantine(getPackageDir());
|
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 })) {
|
if (await handlePackageCommand(args, { extensionFactories: options?.extensionFactories })) {
|
||||||
const exitCode = process.exitCode ?? 0;
|
const exitCode = process.exitCode ?? 0;
|
||||||
if (process.platform === "win32" && exitCode === 0 && args[0] === "update") {
|
if (process.platform === "win32" && exitCode === 0 && args[0] === "update") {
|
||||||
@@ -529,11 +535,9 @@ export async function main(args: string[], options?: MainOptions) {
|
|||||||
validateSessionIdFlags(parsed);
|
validateSessionIdFlags(parsed);
|
||||||
|
|
||||||
// Run migrations (pass cwd for project-local migrations)
|
// Run migrations (pass cwd for project-local migrations)
|
||||||
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());
|
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(cwd);
|
||||||
time("runMigrations");
|
time("runMigrations");
|
||||||
|
|
||||||
const cwd = process.cwd();
|
|
||||||
const agentDir = getAgentDir();
|
|
||||||
const startupSettingsManager = SettingsManager.create(cwd, agentDir);
|
const startupSettingsManager = SettingsManager.create(cwd, agentDir);
|
||||||
reportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, "startup session lookup"));
|
reportDiagnostics(collectSettingsDiagnostics(startupSettingsManager, "startup session lookup"));
|
||||||
|
|
||||||
@@ -726,6 +730,7 @@ export async function main(args: string[], options?: MainOptions) {
|
|||||||
time("createAgentSessionRuntime");
|
time("createAgentSessionRuntime");
|
||||||
const { services, session, modelFallbackMessage } = runtime;
|
const { services, session, modelFallbackMessage } = runtime;
|
||||||
const { settingsManager, modelRegistry, resourceLoader } = services;
|
const { settingsManager, modelRegistry, resourceLoader } = services;
|
||||||
|
applyHttpProxySettings(settingsManager.getGlobalSettings().httpProxy);
|
||||||
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
|
configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
|
||||||
|
|
||||||
if (parsed.help) {
|
if (parsed.help) {
|
||||||
|
|||||||
53
packages/coding-agent/test/http-dispatcher.test.ts
Normal file
53
packages/coding-agent/test/http-dispatcher.test.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user