@@ -33,6 +33,7 @@ def __init__(self, proxy_manager, db=None):
3333 "flow_request_fingerprint" ,
3434 default = None
3535 )
36+ self ._remote_browser_prefill_last_sent : Dict [str , float ] = {}
3637
3738 # Default "real browser" headers (Android Chrome style) to reduce upstream 4xx/5xx instability.
3839 # These will be applied as defaults (won't override caller-provided headers).
@@ -1980,6 +1981,7 @@ async def _notify_browser_captcha_error(
19801981 method = "POST" ,
19811982 path = f"/api/v1/sessions/{ session_id } /error" ,
19821983 json_data = {"error_reason" : error_reason or error_message or "upstream_error" },
1984+ timeout_override = 2 ,
19831985 )
19841986 except Exception as e :
19851987 debug_logger .log_warning (f"[reCAPTCHA RemoteBrowser] 上报 error 失败: { e } " )
@@ -2000,6 +2002,7 @@ async def _notify_browser_captcha_request_finished(self, browser_id: Optional[Un
20002002 method = "POST" ,
20012003 path = f"/api/v1/sessions/{ session_id } /finish" ,
20022004 json_data = {"status" : "success" },
2005+ timeout_override = 2 ,
20032006 )
20042007 except Exception as e :
20052008 debug_logger .log_warning (f"[reCAPTCHA RemoteBrowser] 上报 finish 失败: { e } " )
@@ -2027,6 +2030,17 @@ def _get_remote_browser_service_config(self) -> tuple[str, str, int]:
20272030
20282031 return base_url , api_key , timeout
20292032
2033+ @staticmethod
2034+ def _build_remote_browser_http_timeout (read_timeout : float ) -> httpx .Timeout :
2035+ read_value = max (3.0 , float (read_timeout ))
2036+ write_value = min (10.0 , max (3.0 , read_value ))
2037+ return httpx .Timeout (
2038+ connect = 2.5 ,
2039+ read = read_value ,
2040+ write = write_value ,
2041+ pool = 2.5 ,
2042+ )
2043+
20302044 @staticmethod
20312045 async def _sync_json_http_request (
20322046 method : str ,
@@ -2040,7 +2054,7 @@ async def _sync_json_http_request(
20402054 request_method = (method or "GET" ).upper ()
20412055 request_kwargs : Dict [str , Any ] = {
20422056 "headers" : req_headers ,
2043- "timeout" : timeout ,
2057+ "timeout" : FlowClient . _build_remote_browser_http_timeout ( timeout ) ,
20442058 }
20452059
20462060 if payload is not None :
@@ -2051,7 +2065,7 @@ async def _sync_json_http_request(
20512065 try :
20522066 # remote_browser 控制面只需要稳定传输 JSON,不需要浏览器指纹伪装。
20532067 # 使用 httpx 可以避免 curl_cffi 在当前环境下 POST body 被吞掉。
2054- async with httpx .AsyncClient (follow_redirects = True ) as session :
2068+ async with httpx .AsyncClient (follow_redirects = False , trust_env = False ) as session :
20552069 response = await session .request (
20562070 method = request_method ,
20572071 url = url ,
@@ -2103,25 +2117,72 @@ async def _call_remote_browser_service(
21032117
21042118 return payload
21052119
2120+ async def prefill_remote_browser_pool (
2121+ self ,
2122+ project_id : str ,
2123+ action : str = "IMAGE_GENERATION" ,
2124+ token_id : Optional [int ] = None ,
2125+ * ,
2126+ cooldown_seconds : float = 8.0 ,
2127+ ) -> bool :
2128+ """让本地 remote_browser 服务提前开始补池,尽量把取 token 等待搬到前面。"""
2129+ if config .captcha_method != "remote_browser" :
2130+ return False
2131+
2132+ normalized_project = str (project_id or "" ).strip ()
2133+ normalized_action = str (action or "IMAGE_GENERATION" ).strip () or "IMAGE_GENERATION"
2134+ if not normalized_project :
2135+ return False
2136+
2137+ cache_key = f"{ normalized_project } |{ normalized_action } |{ int (token_id or 0 )} "
2138+ now_value = time .monotonic ()
2139+ last_sent = float (self ._remote_browser_prefill_last_sent .get (cache_key , 0.0 ) or 0.0 )
2140+ if (now_value - last_sent ) < max (0.5 , float (cooldown_seconds )):
2141+ return False
2142+
2143+ try :
2144+ await self ._call_remote_browser_service (
2145+ method = "POST" ,
2146+ path = "/api/v1/prefill" ,
2147+ json_data = {
2148+ "project_id" : normalized_project ,
2149+ "action" : normalized_action ,
2150+ "token_id" : token_id ,
2151+ },
2152+ timeout_override = 3 ,
2153+ )
2154+ self ._remote_browser_prefill_last_sent [cache_key ] = now_value
2155+ return True
2156+ except Exception as e :
2157+ debug_logger .log_warning (f"[reCAPTCHA RemoteBrowser] prefill 失败: { e } " )
2158+ return False
2159+
2160+ async def prefill_remote_browser_for_tokens (self , tokens : List [Any ], action : str = "IMAGE_GENERATION" ) -> int :
2161+ if config .captcha_method != "remote_browser" :
2162+ return 0
2163+
2164+ unique_projects : List [str ] = []
2165+ seen_projects = set ()
2166+ for token in tokens or []:
2167+ project_id = str (getattr (token , "current_project_id" , "" ) or "" ).strip ()
2168+ if not project_id or project_id in seen_projects :
2169+ continue
2170+ seen_projects .add (project_id )
2171+ unique_projects .append (project_id )
2172+
2173+ warmed = 0
2174+ for project_id in unique_projects :
2175+ if await self .prefill_remote_browser_pool (project_id , action = action ):
2176+ warmed += 1
2177+ return warmed
2178+
21062179 def _resolve_remote_browser_solve_timeout (self , action : str ) -> int :
21072180 base_timeout = max (5 , int (config .remote_browser_timeout or 60 ))
21082181 action_name = str (action or "" ).strip ().upper ()
21092182
2110- if action_name == "VIDEO_GENERATION" :
2111- expected = max (
2112- int (getattr (config , "video_timeout" , 1500 ) or 1500 ) + 180 ,
2113- int (getattr (config , "flow_timeout" , 120 ) or 120 ) + 240 ,
2114- 900 ,
2115- )
2116- else :
2117- expected = max (
2118- int (getattr (config , "image_timeout" , 300 ) or 300 ) + 180 ,
2119- int (getattr (config , "upsample_timeout" , 300 ) or 300 ) + 120 ,
2120- int (getattr (config , "flow_timeout" , 120 ) or 120 ) + 180 ,
2121- 300 ,
2122- )
2123-
2124- return max (base_timeout , min (expected , 3600 ))
2183+ # 这里只是拿 reCAPTCHA token,不应该跟整条生成链路共用数百秒级超时。
2184+ target_timeout = 45 if action_name == "VIDEO_GENERATION" else 35
2185+ return max (12 , min (base_timeout , target_timeout ))
21252186
21262187 async def _get_recaptcha_token (
21272188 self ,
0 commit comments