Skip to content

fix(proxy): allow opting out of the DeepSeek direct-route default#1507

Merged
esengine merged 1 commit into
mainfrom
fix/proxy-deepseek-bypass-opt-out
May 22, 2026
Merged

fix(proxy): allow opting out of the DeepSeek direct-route default#1507
esengine merged 1 commit into
mainfrom
fix/proxy-deepseek-bypass-opt-out

Conversation

@esengine

Copy link
Copy Markdown
Owner

Summary

Make the DeepSeek direct-route default opt-out so corporate-firewall users can route api.deepseek.com through their proxy. Closes #1497.

The bug

DEFAULT_NO_PROXY in src/net/proxy.ts hardcoded api.deepseek.com + *.deepseek.com alongside loopback. The SelectiveProxyDispatcher therefore always routed those hosts through undici.Agent() direct, bypassing HTTPS_PROXY. Corporate networks that block direct egress saw ConnectTimeoutErrorgetBalance() returned nullreasonix doctor reported api reach … /user/balance returned null — auth failed or network blocked with no escape hatch (--no-proxy doesn't help; it just skips proxy install entirely, and Node's bare fetch ignores HTTPS_PROXY).

The fix

Split the defaults:

  • LOOPBACK_NO_PROXY (localhost, 127.0.0.1, ::1) — non-negotiable; dashboard, MCP stdio sidecar HTTP probes, and doctor reachability checks all need this.
  • DEEPSEEK_NO_PROXY (api.deepseek.com, *.deepseek.com) — opt-out via either knob:
    • Config: proxy.bypassDeepSeekDirect: false in ~/.reasonix/config.json
    • Env: REASONIX_PROXY_DEEPSEEK_DIRECT=0 (also accepts false / no / off; truthy values are 1 / true / yes / on)

Env wins over config when explicitly set; unrecognized env values fall through to config. Default behaviour preserves the original clash/v2ray US-exit-IP-403 fix that put the bypass there in the first place — no behaviour change for users who don't set either knob.

reasonix doctor's proxy routing probe row already prints api.deepseek.com → direct | via proxy, and now reflects the override correctly without any extra detail string.

Why two knobs

Corporate users typically can't ergonomically edit ~/.reasonix/config.json from their workstation (config sync, profile management, etc.) but they own their shell env vars. The env knob is the actual escape hatch; the config knob is for persistent setups.

Scope check — any other places with the same problem?

The bypass machinery is a single chokepoint: SelectiveProxyDispatcher set as undici's global dispatcher. Every fetch() in the codebase (DeepSeek client, web tools, MCP HTTP probes, doctor, dashboard, balance API) flows through it. Searched for other hardcoded "must route direct" hosts:

  • src/qq/bot.ts (bots.qq.com, api.sgroup.qq.com, sandbox.api.sgroup.qq.com) — uses the global dispatcher, no separate bypass list. ✓
  • src/index/semantic/embedding.ts Ollama localhost — covered by loopback bypass which stays mandatory. ✓
  • metaso.cn / tavily.com / searxng — only appear in i18n error strings, no routing override. ✓
  • docs/configuration.html, docs/src/hero.jsx — describe target URLs, don't claim NO_PROXY is non-optional. No edits needed. ✓

One anti-pattern, one fix.

Test Plan

  • npx vitest run tests/proxy.test.ts — 47 tests pass, including:
    • resolveBypassDeepSeekDirect env precedence + all boolean parse forms
    • resolveNoProxy({...}, { bypassDeepSeekDirect: false }) drops DeepSeek but keeps loopback
    • REASONIX_PROXY_DEEPSEEK_DIRECT=0 drops DeepSeek at install time
    • env wins over config when both are set
  • npx vitest run tests/config.test.ts — 73 tests pass, including new proxy.bypassDeepSeekDirect config load + typeguard against non-bool values
  • npx vitest run tests/doctor-json.test.ts — 5 pass
  • npx vitest run tests/comment-policy.test.ts — 9 pass
  • npx tsc --noEmit — clean
  • npx biome check on touched files — clean
  • npm run verify — full suite green via pre-push hook

`DEFAULT_NO_PROXY` hardcoded `api.deepseek.com` / `*.deepseek.com` so
the dispatcher always routed them through `undici.Agent()` direct,
bypassing HTTPS_PROXY. Corporate firewalls that block direct egress
therefore broke `reasonix doctor`'s api-reach check with a
ConnectTimeoutError, with no way for the user to override.

Split the defaults: loopback stays mandatory, DeepSeek becomes opt-out
via `proxy.bypassDeepSeekDirect: false` (config) or
`REASONIX_PROXY_DEEPSEEK_DIRECT=0` (env). Default behaviour preserves
the clash/v2ray US-exit-IP 403 fix that put the bypass there.

Closes #1497
@esengine esengine force-pushed the fix/proxy-deepseek-bypass-opt-out branch from 3ad8533 to 98236b2 Compare May 22, 2026 03:10
@esengine esengine merged commit 45f9a65 into main May 22, 2026
4 checks passed
@esengine esengine deleted the fix/proxy-deepseek-bypass-opt-out branch May 22, 2026 03:14
esengine added a commit that referenced this pull request May 22, 2026
…se (#1565)

* chore(release): 0.49.0 — static-history TUI, queued steers, Bing default, lifecycle plans

Headline themes:
- TUI: Static-history renderer is the only path; virtual-viewport layers removed (#1529 stages 1-4)
- Chat: queued mid-turn steer handling so input mid-render doesn't drop or fight the live frame (#1501)
- Web search: default switches to Bing; dashboard engine switcher; Mojeek dropped (#1558)
- Plans: lifecycle evidence summaries surface why a plan is ready to accept (#1500)
- Desktop: native OS notifications for approvals + completion (#1519)
- i18n: CLI command output (/mcp /sessions /prune /theme) + approval-prompt labels translated (#1524, #1560)
- Security: SSRF block in web_fetch (#1544), edit-snapshot path containment (#1454), shell redirect sandbox (#1457), Task integrity guardrail (#1516)
- Tools: per-turn dispatch-rate limit (#1356); run_command discourages shell-based edits (#1514)
- Client: DeepSeek 429 → concurrency-limit hint (#1526); timeoutMs honored with AbortSignal (#1535); --no-proxy opt-out for direct route (#1507)
- Files: read/edit/restore preserves source encoding (GB18030 / UTF-8 BOM) (#1518)
- Context: pinned constraints survive folds + full tail capture (#1515, #1552)
- Refactor: lifecycle risk policy extracted into its own module (#1557)

See CHANGELOG for the full list.

* fix(context): align fold summary prefix with main agent for cache reuse

The summarizer call was sending a bespoke "You compress conversation
history" system prompt and no tools, guaranteeing a 0% cache hit
against the main agent's just-cached prefix. Reshape the request so
system + tools + head bytes mirror the live agent's last call — the
only novel bytes are the trailing summarize instruction.

Skill-pin handling now collects bodies read-only instead of stubbing
mid-head, so the cache prefix stays unbroken. The summarize
instruction names pinned skills so the model knows not to paraphrase
their bodies (which we append verbatim regardless).

Measured on a real session at 48.7K prompt tokens:
  OLD shape: 0.0% cache hit  → $0.145 per fold
  NEW shape: 99.6% cache hit → $0.015 per fold
  saving: 89.6% per fold

* tools: add fold-cache shape + live benchmarks

bench-fold-cache-shape.mjs replays real session jsonls, simulates
OLD vs NEW summary-call shapes at the fold point, and reports
byte-level shared-prefix with the main agent's preceding request.
Pure local — no API required.

bench-fold-cache-live.mjs sends one priming + two summary calls to
DeepSeek and reports prompt_cache_hit_tokens / cost for each shape.
Used to confirm the shape change actually translates to API-side
cache hits.

---------

Co-authored-by: reasonix <reasonix@deepseek.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reasonix doctor api reach 在企业代理环境下必定失败 — DEFAULT_NO_PROXY 强制绕过代理导致直连超时

1 participant