Skip to content

[Feature] SOCKS5 proxy support for outbound provider calls - and fail fast on unsupported proxy schemes #2894

Description

@nordz0r

Area

Proxy and routing

What are you trying to accomplish?

Two related needs when reaching model upstreams from restricted networks:

  1. Route all provider traffic through an outbound HTTP or SOCKS5 proxy, including SOCKS5 endpoints exposed by local tunnel clients (sing-box, v2ray/Xray, ssh -D, Tor), which is the most common listen mode for those tools.
  2. Route individual providers through different proxies — and others directly. Example (real): Google Antigravity must exit through a US proxy, xAI through a residential/EU proxy, and zai/GLM should go direct from the local network, because a foreign exit adds latency and triggers regional risk controls on a Chinese upstream. One global proxy for everything cannot express this — exactly the split Omniroute has: a common default proxy plus a per-provider override with a "direct / default / custom" choice.

What prevents this today?

  1. config.proxy supports HTTP(S) proxy URLs only. Bun's fetch (the bundled runtime) does not implement SOCKS — socks5:// is not negotiated (upstream Add SOCKS support oven-sh/bun#16812 open), and worse, Bun silently treats the unknown scheme as HTTP instead of failing (Bun treating unknown proxy protocol as HTTP instead of fail oven-sh/bun#11343). The config layer accepts any string without scheme validation, so proxy: "socks5://127.0.0.1:1080" is stored, mirrored into HTTP_PROXY/HTTPS_PROXY by applyProxyEnv(), and only produces confusing network-level errors at request time. A user with only a SOCKS5 endpoint must spin up an extra HTTP listener (privoxy/glider/sing-box mixed port) as a workaround.
  2. proxy exists only at the top level of OcxConfig (src/types/config.ts:506). OcxProviderConfig (src/types/provider.ts:138) has no proxy field of any kind, so per-provider egress is impossible: either everything goes through the single proxy (regional upstreams suffer), or nothing does (restricted upstreams are unreachable).

What should OpenCodex do?

A. Per-provider proxy with a global default (Omniroute-style two-level model):

  • Global proxy/noProxy keep working exactly as today and become the default for every provider.
  • Each provider gains proxy + noProxy with three effective states:
    • unset → inherit the global default;
    • explicit value → use this provider's own proxy (fully overriding the global for that provider);
    • null / "" → force direct egress, exempting the provider from the global proxy.
  • Effective proxy resolution logged observably: ocx inspect (or ocx provider test <name>) should show which proxy each provider resolves to (global / custom / direct), and ocx doctor should probe each configured proxy.
  • Apply symmetrically to OAuth-backed providers (Antigravity, xAI, ...) — token refresh and quota probes must use the same per-provider egress as model calls, or the account looks healthy while model calls fail.

B. SOCKS5 support:

  • Accept socks5:// (local DNS) and ideally socks5h:// (remote DNS — normally what users behind restrictive networks want) in both the global and per-provider fields; RFC 1928 handshake + RFC 1929 user/pass auth.
  • At minimum, fail fast: validate the scheme at config-set/apply time and reject socks5*:// with a clear message ("SOCKS proxies are not supported yet — use the HTTP inbound of your tunnel client") instead of the current silent misrouting.

Example usage or interface

# Global default stays as-is (all providers inherit it):
ocx config set proxy "http://user:pass@proxy.example.com:8080"

# Per-provider overrides (new):
ocx provider edit google-antigravity --proxy "socks5://127.0.0.1:1080"
ocx provider edit xai                --proxy "http://residential.example.com:3128"
ocx provider edit zai                --proxy ""          # force DIRECT, exempt from the global proxy
// ~/.opencodex/config.json
{
  "proxy": "http://user:pass@proxy.example.com:8080",
  "providers": {
    "google-antigravity": {
      "baseUrl": "https://daily-cloudcode-pa.googleapis.com",
      "adapter": "google-antigravity",
      "proxy": "socks5://127.0.0.1:1080"          // inherits user/pass/noProxy only from itself
    },
    "zai": {
      "baseUrl": "https://api.z.ai/api/coding/paas/v4",
      "adapter": "openai-chat",
      "proxy": null                                 // explicit direct: ignores the global proxy
    }
  }
}

Effective-resolution view (illustrative):

$ ocx provider test google-antigravity
  egress: socks5://127.0.0.1:1080 (provider override) — handshake ok, exit IP 45.xx.xx.xx
$ ocx provider test zai
  egress: direct (provider override: null) — exit IP 91.xx.xx.xx

Alternatives or workarounds

  • Run an HTTP listener next to the SOCKS5 endpoint (sing-box mixed inbound / privoxy / glider) and point the global proxy at it — works, but adds a moving part per machine and still cannot split providers onto different exits.
  • Multiple OpenCodex instances with different global proxy values and split catalogs — heavy, doubles the service/dashboard footprint, and clients must know which port to call.
  • Transparent proxy (TUN mode / firewall rules) routing by process or destination — OS-level, brittle, and cannot distinguish providers that share one upstream domain.

Additional context

Checks

  • I searched existing issues and documentation.
  • This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
  • I removed secrets and personal data.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions