@@ -442,7 +442,7 @@ def __init__(
442442 options : dict [str , Any ],
443443 api_url : str = _KERNEL_API_URL ,
444444 replay_poll_interval_s : float = 1 ,
445- replay_poll_timeout_s : float = 30 ,
445+ replay_poll_timeout_s : float = 300 ,
446446 ) -> None :
447447 unknown = sorted (set (options ) - _KERNEL_ALLOWED_OPTIONS )
448448 if unknown :
@@ -471,14 +471,14 @@ def __init__(
471471 self .replay_poll_interval_s = replay_poll_interval_s
472472 self .replay_poll_timeout_s = replay_poll_timeout_s
473473
474- def _request (
474+ def _request_response (
475475 self ,
476476 method : str ,
477477 path : str ,
478478 payload : dict [str , Any ] | None = None ,
479479 * ,
480480 accept : str = "application/json" ,
481- ) -> bytes :
481+ ) -> tuple [ int , Any , bytes ] :
482482 assert self .api_key is not None
483483 data = (
484484 json .dumps (payload , separators = ("," , ":" )).encode ()
@@ -497,7 +497,11 @@ def _request(
497497 )
498498 try :
499499 with urllib .request .urlopen (request , timeout = 30 ) as response :
500- return response .read ()
500+ return (
501+ int (getattr (response , "status" , 200 )),
502+ getattr (response , "headers" , {}),
503+ response .read (),
504+ )
501505 except urllib .error .HTTPError as e :
502506 if e .code in {401 , 403 }:
503507 message = "Kernel authentication failed"
@@ -517,6 +521,22 @@ def _request(
517521 reason = reason .replace (secret , "[REDACTED]" )
518522 raise _KernelApiError (f"Kernel API request failed: { reason } " ) from None
519523
524+ def _request (
525+ self ,
526+ method : str ,
527+ path : str ,
528+ payload : dict [str , Any ] | None = None ,
529+ * ,
530+ accept : str = "application/json" ,
531+ ) -> bytes :
532+ _status , _headers , raw = self ._request_response (
533+ method ,
534+ path ,
535+ payload ,
536+ accept = accept ,
537+ )
538+ return raw
539+
520540 def _request_json (
521541 self ,
522542 method : str ,
@@ -669,11 +689,31 @@ def finalize(self, session: BrowserSession, output_dir: Path) -> None:
669689 )
670690 time .sleep (self .replay_poll_interval_s )
671691
672- recording = self ._request (
673- "GET" ,
674- f"/browsers/{ session .session_id } /replays/{ replay_id } " ,
675- accept = "video/mp4" ,
676- )
692+ while True :
693+ status , headers , recording = self ._request_response (
694+ "GET" ,
695+ f"/browsers/{ session .session_id } /replays/{ replay_id } " ,
696+ accept = "video/mp4" ,
697+ )
698+ if status != 202 :
699+ break
700+ if time .monotonic () >= deadline :
701+ raise _KernelApiError (
702+ "Kernel replay did not finish downloading before timeout"
703+ )
704+ retry_after : str | None = (
705+ headers .get ("Retry-After" ) if headers else None
706+ )
707+ if retry_after is None :
708+ delay = self .replay_poll_interval_s
709+ else :
710+ try :
711+ delay = float (retry_after )
712+ except ValueError :
713+ delay = self .replay_poll_interval_s
714+ time .sleep (min (max (0 , delay ), max (0 , deadline - time .monotonic ())))
715+ if status != 200 :
716+ raise _KernelApiError (f"Kernel replay download returned HTTP { status } " )
677717 if not recording :
678718 raise _KernelApiError ("Kernel replay download was empty" )
679719 recording_path = output_dir / "data" / "recording.mp4"
0 commit comments