Просмотр исходного кода

fix: configure default connectTimeout to 30s with FETCH_CONNECT_TIMEOUT env var

- Add connectTimeout configuration to global undici Agent (default 30s)
- Add connectTimeout configuration to ProxyAgent for HTTP/HTTPS proxies
- Read timeout value from FETCH_CONNECT_TIMEOUT environment variable
- Document FETCH_CONNECT_TIMEOUT in .env.example

This allows faster failover when providers are unresponsive or under attack,
while still being configurable for unstable network environments.

Closes #477

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
claude[bot] 1 месяц назад
Родитель
Сommit
7aaaef1af8
2 измененных файлов с 18 добавлено и 1 удалено
  1. 9 0
      .env.example
  2. 9 1
      src/lib/proxy-agent.ts

+ 9 - 0
.env.example

@@ -53,6 +53,15 @@ STORE_SESSION_MESSAGES=false            # 是否存储请求 messages 到 Redis
 # - 默认关闭:适用于网络不稳定环境(如使用代理),避免因临时网络抖动触发熔断器
 # - 启用:适用于网络稳定环境,连续网络错误也应触发熔断保护,避免持续请求不可达的供应商
 ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS=false
+
+# Fetch 连接超时配置
+# 功能说明:控制 TCP 连接建立超时时间(包括 DNS 查询、TCP 握手、TLS 握手)
+# - 默认值:30000 毫秒(30 秒)
+# - 取值范围:建议 5000-120000 毫秒(5-120 秒)
+# 使用场景:
+# - 缩短此值可快速切换到备用供应商,当供应商被攻击或无响应时
+# - 增加此值适用于网络不稳定环境,避免因网络抖动导致连接失败
+FETCH_CONNECT_TIMEOUT=30000
 MAX_RETRY_ATTEMPTS_DEFAULT=2                # 单供应商最大尝试次数(含首次调用),范围 1-10,留空使用默认值 2
 
 # 智能探测配置

+ 9 - 1
src/lib/proxy-agent.ts

@@ -2,6 +2,7 @@ import { SocksProxyAgent } from "socks-proxy-agent";
 import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
 import type { Provider } from "@/types/provider";
 import { logger } from "./logger";
+import { getEnvConfig } from "./config/env.schema";
 
 /**
  * undici 全局超时配置
@@ -15,18 +16,23 @@ import { logger } from "./logger";
  */
 const UNDICI_TIMEOUT_MS = 600_000; // 600 秒 = 10 分钟,LLM 服务最大超时时间
 
+// 从环境变量读取 TCP 连接超时(默认 30 秒)
+const { FETCH_CONNECT_TIMEOUT: connectTimeout } = getEnvConfig();
+
 /**
  * 设置 undici 全局 Agent,覆盖默认的 300 秒超时
  * 此配置对所有 fetch() 调用生效(无论是否使用代理)
  */
 setGlobalDispatcher(
   new Agent({
+    connectTimeout,
     headersTimeout: UNDICI_TIMEOUT_MS,
     bodyTimeout: UNDICI_TIMEOUT_MS,
   })
 );
 
 logger.info("undici global dispatcher configured", {
+  connectTimeout,
   headersTimeout: UNDICI_TIMEOUT_MS,
   bodyTimeout: UNDICI_TIMEOUT_MS,
   note: "覆盖 undici 默认 300s 超时,匹配 LLM 最大响应时间",
@@ -126,10 +132,11 @@ export function createProxyAgentForProvider(
     } else if (parsedProxy.protocol === "http:" || parsedProxy.protocol === "https:") {
       // HTTP/HTTPS 代理(使用 undici)
       // 支持 HTTP/2:通过 allowH2 选项启用 ALPN 协商
-      // ⭐ 配置 600 秒超时,覆盖 undici 默认的 300 秒,匹配 LLM 最大响应时间
+      // ⭐ 配置超时,覆盖 undici 默认值,匹配 LLM 最大响应时间
       agent = new ProxyAgent({
         uri: proxyUrl,
         allowH2: enableHttp2,
+        connectTimeout,
         headersTimeout: UNDICI_TIMEOUT_MS, // 等待响应头的超时
         bodyTimeout: UNDICI_TIMEOUT_MS, // 等待响应体的超时
       });
@@ -142,6 +149,7 @@ export function createProxyAgentForProvider(
         proxyPort: parsedProxy.port,
         targetUrl: new URL(targetUrl).origin,
         http2Enabled: enableHttp2,
+        connectTimeout,
         headersTimeout: UNDICI_TIMEOUT_MS,
         bodyTimeout: UNDICI_TIMEOUT_MS,
       });