From 25b5ec55912815a64e53eb7703ee8a4303c2e718 Mon Sep 17 00:00:00 2001 From: Yinghan Ma Date: Mon, 15 Dec 2025 17:07:26 -0800 Subject: [PATCH 1/8] record eval duration --- .vscode/settings.json | 3 ++- .../integrations/tinker_rollout_processor.py | 2 +- eval_protocol/mcp/execution/manager.py | 2 +- eval_protocol/models.py | 13 ++++++++++++- .../pytest/default_agent_rollout_processor.py | 2 +- .../pytest/default_pydantic_ai_rollout_processor.py | 2 +- .../pytest/default_single_turn_rollout_process.py | 2 +- eval_protocol/pytest/evaluation_test_utils.py | 11 +++++++++-- .../pytest/github_action_rollout_processor.py | 8 ++++---- eval_protocol/pytest/openenv_rollout_processor.py | 6 +++--- eval_protocol/pytest/priority_scheduler.py | 12 +++++++++--- eval_protocol/pytest/remote_rollout_processor.py | 2 +- vite-app/dist/assets/index-CuQbfdPD.js | 2 +- vite-app/dist/assets/index-CuQbfdPD.js.map | 2 +- vite-app/src/GlobalState.tsx | 2 +- 15 files changed, 48 insertions(+), 23 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3d7f425e..76e2aa30 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,6 @@ }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "makefile.configureOnOpen": true } diff --git a/eval_protocol/integrations/tinker_rollout_processor.py b/eval_protocol/integrations/tinker_rollout_processor.py index 5edf4cf6..5f2c1197 100644 --- a/eval_protocol/integrations/tinker_rollout_processor.py +++ b/eval_protocol/integrations/tinker_rollout_processor.py @@ -152,7 +152,7 @@ async def process_row(row: EvaluationRow) -> EvaluationRow: # Update row new_messages = list(row.messages) + [Message(role="assistant", content=assistant_content)] row.messages = new_messages - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time # Log usage (approximate since Tinker might not return usage stats in same format) # We can count tokens ourselves diff --git a/eval_protocol/mcp/execution/manager.py b/eval_protocol/mcp/execution/manager.py index 71b7c411..3480d0f7 100644 --- a/eval_protocol/mcp/execution/manager.py +++ b/eval_protocol/mcp/execution/manager.py @@ -150,7 +150,7 @@ async def _execute_with_semaphore(idx): else: evaluation_row.rollout_status = Status.rollout_running() - evaluation_row.execution_metadata.duration_seconds = time.perf_counter() - row_start_time + evaluation_row.execution_metadata.rollout_duration_seconds = time.perf_counter() - row_start_time return evaluation_row diff --git a/eval_protocol/models.py b/eval_protocol/models.py index 73c18093..9477181f 100644 --- a/eval_protocol/models.py +++ b/eval_protocol/models.py @@ -809,9 +809,20 @@ class ExecutionMetadata(BaseModel): cost_metrics: Optional[CostMetrics] = Field(default=None, description="Cost breakdown for LLM API calls.") + # deprecated: use rollout_duration_seconds and eval_duration_seconds instead duration_seconds: Optional[float] = Field( default=None, - description="Processing duration in seconds for this evaluation row. Note that if it gets retried, this will be the duration of the last attempt.", + description="[Deprecated] Processing duration in seconds for this evaluation row. Note that if it gets retried, this will be the duration of the last attempt.", + ) + + rollout_duration_seconds: Optional[float] = Field( + default=None, + description="Processing duration in seconds for the rollout of this evaluation row. Note that if it gets retried, this will be the duration of the last attempt.", + ) + + eval_duration_seconds: Optional[float] = Field( + default=None, + description="Processing duration in seconds for the evaluation of this evaluation row. Note that if it gets retried, this will be the duration of the last attempt.", ) experiment_duration_seconds: Optional[float] = Field( diff --git a/eval_protocol/pytest/default_agent_rollout_processor.py b/eval_protocol/pytest/default_agent_rollout_processor.py index d8d4aada..ec6f983b 100644 --- a/eval_protocol/pytest/default_agent_rollout_processor.py +++ b/eval_protocol/pytest/default_agent_rollout_processor.py @@ -267,7 +267,7 @@ async def process_row(row: EvaluationRow) -> EvaluationRow: total_tokens=agent.usage["total_tokens"], ) - agent.evaluation_row.execution_metadata.duration_seconds = time.perf_counter() - start_time + agent.evaluation_row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time return agent.evaluation_row finally: diff --git a/eval_protocol/pytest/default_pydantic_ai_rollout_processor.py b/eval_protocol/pytest/default_pydantic_ai_rollout_processor.py index 47b7b456..f9618799 100644 --- a/eval_protocol/pytest/default_pydantic_ai_rollout_processor.py +++ b/eval_protocol/pytest/default_pydantic_ai_rollout_processor.py @@ -83,7 +83,7 @@ async def process_row(row: EvaluationRow) -> EvaluationRow: # total_tokens=usage_info.total_tokens or 0, # ) - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time return row diff --git a/eval_protocol/pytest/default_single_turn_rollout_process.py b/eval_protocol/pytest/default_single_turn_rollout_process.py index 665da649..b8e4445d 100644 --- a/eval_protocol/pytest/default_single_turn_rollout_process.py +++ b/eval_protocol/pytest/default_single_turn_rollout_process.py @@ -180,7 +180,7 @@ async def process_row(row: EvaluationRow) -> EvaluationRow: row.messages = messages - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time default_logger.log(row) return row diff --git a/eval_protocol/pytest/evaluation_test_utils.py b/eval_protocol/pytest/evaluation_test_utils.py index 94d6f7fe..48f8a015 100644 --- a/eval_protocol/pytest/evaluation_test_utils.py +++ b/eval_protocol/pytest/evaluation_test_utils.py @@ -42,7 +42,7 @@ async def run_tasks_with_eval_progress( - pointwise_tasks: list[asyncio.Task[EvaluationRow]], run_idx: int + pointwise_tasks: list[asyncio.Task[EvaluationRow]], run_idx: int, disable_tqdm: bool = False ) -> list[EvaluationRow]: """ Run evaluation tasks with a progress bar and proper cancellation handling. @@ -66,6 +66,7 @@ async def run_tasks_with_eval_progress( miniters=1, mininterval=0.1, bar_format="{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]", + disable=disable_tqdm, ) as eval_pbar: async def task_with_progress(task: asyncio.Task[EvaluationRow]) -> EvaluationRow: @@ -88,7 +89,10 @@ async def task_with_progress(task: asyncio.Task[EvaluationRow]) -> EvaluationRow async def run_tasks_with_run_progress( - execute_run_func: Callable[[int, RolloutProcessorConfig], Any], num_runs: int, config: RolloutProcessorConfig + execute_run_func: Callable[[int, RolloutProcessorConfig], Any], + num_runs: int, + config: RolloutProcessorConfig, + disable_tqdm: bool = False, ) -> None: """ Run tasks with a parallel runs progress bar, preserving original logic. @@ -108,6 +112,7 @@ async def run_tasks_with_run_progress( dynamic_ncols=True, miniters=1, bar_format="{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]", + disable=disable_tqdm, ) as run_pbar: async def execute_run_with_progress(run_idx: int, config: RolloutProcessorConfig) -> Any: @@ -330,6 +335,7 @@ async def rollout_processor_with_retry( fresh_dataset: list[EvaluationRow], config: RolloutProcessorConfig, run_idx: int = 0, + disable_tqdm: bool = False, ) -> AsyncGenerator[EvaluationRow, None]: """ Wrapper around rollout_processor that handles retry logic using the Python backoff library. @@ -449,6 +455,7 @@ async def execute_row_with_backoff_and_log( miniters=1, mininterval=0.1, bar_format="{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]", + disable=disable_tqdm, ) as rollout_pbar: # Yield results as they complete for task in asyncio.as_completed(retry_tasks): diff --git a/eval_protocol/pytest/github_action_rollout_processor.py b/eval_protocol/pytest/github_action_rollout_processor.py index e6ca24ae..bbdd8b84 100644 --- a/eval_protocol/pytest/github_action_rollout_processor.py +++ b/eval_protocol/pytest/github_action_rollout_processor.py @@ -162,7 +162,7 @@ def _list_runs(): row.rollout_status = Status.rollout_error( f"Failed to find workflow run in GHA with rollout_id {row.execution_metadata.rollout_id}" ) - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time return row run_id = run.get("id") @@ -170,7 +170,7 @@ def _list_runs(): row.rollout_status = Status.rollout_error( f"Failed to find workflow run in GHA with rollout_id {row.execution_metadata.rollout_id}" ) - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time return row # Poll the specific run until completion @@ -194,10 +194,10 @@ def _get_run() -> Dict[str, Any]: row.rollout_status = Status.rollout_error( f"GitHub Actions run timed out after {self.timeout_seconds} seconds" ) - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time return row - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time def _update_with_trace() -> None: return update_row_with_remote_trace(row, self._output_data_loader, self.model_base_url) diff --git a/eval_protocol/pytest/openenv_rollout_processor.py b/eval_protocol/pytest/openenv_rollout_processor.py index c1ce7769..0f662692 100644 --- a/eval_protocol/pytest/openenv_rollout_processor.py +++ b/eval_protocol/pytest/openenv_rollout_processor.py @@ -411,7 +411,7 @@ async def process_row(row: EvaluationRow) -> EvaluationRow: completion_tokens=usage["completion_tokens"], total_tokens=usage["total_tokens"], ) - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time # Attach per-step rewards and accumulated token IDs to # execution_metadata.extra for downstream integrations @@ -436,14 +436,14 @@ async def process_row(row: EvaluationRow) -> EvaluationRow: logger.info("[OpenEnvRolloutProcessor] Total reward: %.3f", total_reward) logger.info( "[OpenEnvRolloutProcessor] Duration: %.2fs", - row.execution_metadata.duration_seconds, + row.execution_metadata.rollout_duration_seconds, ) logger.debug("[OpenEnvRolloutProcessor] Messages collected: %d", len(messages)) logger.info( f"Rollout complete: {len(step_rewards)} steps, " f"total_reward={total_reward:.2f}, " - f"duration={row.execution_metadata.duration_seconds:.2f}s" + f"duration={row.execution_metadata.rollout_duration_seconds:.2f}s" ) # Final log with complete message history if getattr(config, "logger", None): diff --git a/eval_protocol/pytest/priority_scheduler.py b/eval_protocol/pytest/priority_scheduler.py index eaddacc5..d04ae90a 100644 --- a/eval_protocol/pytest/priority_scheduler.py +++ b/eval_protocol/pytest/priority_scheduler.py @@ -14,6 +14,7 @@ from eval_protocol.human_id import generate_id from eval_protocol.log_utils.rollout_context import rollout_logging_context from eval_protocol.pytest.execution import execute_pytest_with_exception_handling +import time ENABLE_SPECULATION = os.getenv("ENABLE_SPECULATION", "0").strip() == "1" @@ -132,6 +133,8 @@ async def _run_eval(rows_to_eval: Union[EvaluationRow, List[EvaluationRow]]): experiment_id = rows_to_eval[0].execution_metadata.experiment_id if isinstance(rows_to_eval, list) else rows_to_eval.execution_metadata.experiment_id run_id = rows_to_eval[0].execution_metadata.run_id if isinstance(rows_to_eval, list) else rows_to_eval.execution_metadata.run_id eval_res = None + + start_time = time.perf_counter() async with self.eval_sem: async with rollout_logging_context( @@ -151,7 +154,7 @@ async def _run_eval(rows_to_eval: Union[EvaluationRow, List[EvaluationRow]]): evaluation_test_kwargs=self.evaluation_test_kwargs, processed_row=rows_to_eval, ) - + eval_duration = time.perf_counter() - start_time # push result to the output buffer if self.output_buffer: if isinstance(eval_res, list): @@ -163,8 +166,11 @@ async def _run_eval(rows_to_eval: Union[EvaluationRow, List[EvaluationRow]]): await self.output_buffer.add_result(eval_res) if isinstance(eval_res, list): - self.results.extend(eval_res) + for row in eval_res: + row.execution_metadata.eval_duration_seconds = eval_duration + self.results.append(row) else: + eval_res.execution_metadata.eval_duration_seconds = eval_duration self.results.append(eval_res) return eval_res @@ -206,7 +212,7 @@ async def _run_eval(rows_to_eval: Union[EvaluationRow, List[EvaluationRow]]): if current_batch_rows: for idx, row in current_batch_rows: async for result_row in rollout_processor_with_retry( - self.rollout_processor, [row], task.config, idx + self.rollout_processor, [row], task.config, idx, disable_tqdm=True ): batch_results.append(result_row) # in pointwise, we start evaluation immediately diff --git a/eval_protocol/pytest/remote_rollout_processor.py b/eval_protocol/pytest/remote_rollout_processor.py index 23a7d979..ab42bdcd 100644 --- a/eval_protocol/pytest/remote_rollout_processor.py +++ b/eval_protocol/pytest/remote_rollout_processor.py @@ -185,7 +185,7 @@ def _get_status() -> Dict[str, Any]: f"Rollout {row.execution_metadata.rollout_id} timed out after {timeout_seconds} seconds" ) - row.execution_metadata.duration_seconds = time.perf_counter() - start_time + row.execution_metadata.rollout_duration_seconds = time.perf_counter() - start_time def _update_with_trace() -> None: return update_row_with_remote_trace(row, self._output_data_loader, model_base_url) diff --git a/vite-app/dist/assets/index-CuQbfdPD.js b/vite-app/dist/assets/index-CuQbfdPD.js index dcf3d7e0..24021c48 100644 --- a/vite-app/dist/assets/index-CuQbfdPD.js +++ b/vite-app/dist/assets/index-CuQbfdPD.js @@ -10,7 +10,7 @@ Error generating stack: `+f.message+` Please change the parent to .`)}let B=wi(),v;if(t){let Q=typeof t=="string"?Eo(t):t;Qt(d==="/"||Q.pathname?.startsWith(d),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${d}" but pathname "${Q.pathname}" was given in the \`location\` prop.`),v=Q}else v=B;let w=v.pathname||"/",b=w;if(d!=="/"){let Q=d.replace(/^\//,"").split("/");b="/"+w.replace(/^\//,"").split("/").slice(Q.length).join("/")}let x=PQ(e,{pathname:b});sn(p||x!=null,`No routes matched location "${v.pathname}${v.search}${v.hash}" `),sn(x==null||x[x.length-1].route.element!==void 0||x[x.length-1].route.Component!==void 0||x[x.length-1].route.lazy!==void 0,`Matched leaf route at location "${v.pathname}${v.search}${v.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let C=qH(x&&x.map(Q=>Object.assign({},Q,{params:Object.assign({},u,Q.params),pathname:Ar([d,a.encodeLocation?a.encodeLocation(Q.pathname.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:Q.pathname]),pathnameBase:Q.pathnameBase==="/"?d:Ar([d,a.encodeLocation?a.encodeLocation(Q.pathnameBase.replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:Q.pathnameBase])})),o,A,n,i);return t&&C?F.createElement(Cc.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...v},navigationType:"POP"}},C):C}function YH(){let e=iT(),t=zH(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),A=e instanceof Error?e.stack:null,n="rgba(200,200,200, 0.5)",i={padding:"0.5rem",backgroundColor:n},a={padding:"2px 4px",backgroundColor:n},o=null;return console.error("Error handled by React Router default ErrorBoundary:",e),o=F.createElement(F.Fragment,null,F.createElement("p",null,"💿 Hey developer 👋"),F.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",F.createElement("code",{style:a},"ErrorBoundary")," or"," ",F.createElement("code",{style:a},"errorElement")," prop on your route.")),F.createElement(F.Fragment,null,F.createElement("h2",null,"Unexpected Application Error!"),F.createElement("h3",{style:{fontStyle:"italic"}},t),A?F.createElement("pre",{style:i},A):null,o)}var $H=F.createElement(YH,null),WH=class extends F.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!=="idle"&&e.revalidation==="idle"?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error!==void 0?e.error:t.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error("React Router caught the following error during render",e)}render(){return this.state.error!==void 0?F.createElement(vi.Provider,{value:this.props.routeContext},F.createElement(n0.Provider,{value:this.state.error,children:this.props.component})):this.props.children}};function JH({routeContext:e,match:t,children:A}){let n=F.useContext(Fo);return n&&n.static&&n.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(n.staticContext._deepestRenderedBoundaryId=t.route.id),F.createElement(vi.Provider,{value:e},A)}function qH(e,t=[],A=null,n=null,i=null){if(e==null){if(!A)return null;if(A.errors)e=A.matches;else if(t.length===0&&!A.initialized&&A.matches.length>0)e=A.matches;else return null}let a=e,o=A?.errors;if(o!=null){let d=a.findIndex(p=>p.route.id&&o?.[p.route.id]!==void 0);Qt(d>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(o).join(",")}`),a=a.slice(0,Math.min(a.length,d+1))}let c=!1,u=-1;if(A)for(let d=0;d=0?a=a.slice(0,u+1):a=[a[0]];break}}}let h=A&&n?(d,p)=>{n(d,{location:A.location,params:A.matches?.[0]?.params??{},errorInfo:p})}:void 0;return a.reduceRight((d,p,B)=>{let v,w=!1,b=null,x=null;A&&(v=o&&p.route.id?o[p.route.id]:void 0,b=p.route.errorElement||$H,c&&(u<0&&B===0?(qQ("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),w=!0,x=null):u===B&&(w=!0,x=p.route.hydrateFallbackElement||null)));let C=t.concat(a.slice(0,B+1)),Q=()=>{let E;return v?E=b:w?E=x:p.route.Component?E=F.createElement(p.route.Component,null):p.route.element?E=p.route.element:E=d,F.createElement(JH,{match:p,routeContext:{outlet:d,matches:C,isDataRoute:A!=null},children:E})};return A&&(p.route.ErrorBoundary||p.route.errorElement||B===0)?F.createElement(WH,{location:A.location,revalidation:A.revalidation,component:b,error:v,children:Q(),routeContext:{outlet:null,matches:C,isDataRoute:!0},onError:h}):Q()},null)}function i0(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function eT(e){let t=F.useContext(Fo);return Qt(t,i0(e)),t}function tT(e){let t=F.useContext(Zh);return Qt(t,i0(e)),t}function AT(e){let t=F.useContext(vi);return Qt(t,i0(e)),t}function r0(e){let t=AT(e),A=t.matches[t.matches.length-1];return Qt(A.route.id,`${e} can only be used on routes that contain a unique "id"`),A.route.id}function nT(){return r0("useRouteId")}function iT(){let e=F.useContext(n0),t=tT("useRouteError"),A=r0("useRouteError");return e!==void 0?e:t.errors?.[A]}function rT(){let{router:e}=eT("useNavigate"),t=r0("useNavigate"),A=F.useRef(!1);return WQ(()=>{A.current=!0}),F.useCallback(async(i,a={})=>{sn(A.current,$Q),A.current&&(typeof i=="number"?e.navigate(i):await e.navigate(i,{fromRouteId:t,...a}))},[e,t])}var Wb={};function qQ(e,t,A){!t&&!Wb[e]&&(Wb[e]=!0,sn(!1,A))}F.memo(sT);function sT({routes:e,future:t,state:A,unstable_onError:n}){return JQ(e,void 0,A,n,t)}function aT({to:e,replace:t,state:A,relative:n}){Qt(So()," may be used only in the context of a component.");let{static:i}=F.useContext(Gn);sn(!i," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:a}=F.useContext(vi),{pathname:o}=wi(),c=Yh(),u=A0(e,t0(a),o,n==="path"),h=JSON.stringify(u);return F.useEffect(()=>{c(JSON.parse(h),{replace:t,state:A,relative:n})},[c,h,n,t,A]),null}function $f(e){Qt(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function oT({basename:e="/",children:t=null,location:A,navigationType:n="POP",navigator:i,static:a=!1}){Qt(!So(),"You cannot render a inside another . You should never have more than one in your app.");let o=e.replace(/^\/*/,"/"),c=F.useMemo(()=>({basename:o,navigator:i,static:a,future:{}}),[o,i,a]);typeof A=="string"&&(A=Eo(A));let{pathname:u="/",search:h="",hash:d="",state:p=null,key:B="default"}=A,v=F.useMemo(()=>{let w=sr(u,o);return w==null?null:{location:{pathname:w,search:h,hash:d,state:p,key:B},navigationType:n}},[o,u,h,d,p,B,n]);return sn(v!=null,` is not able to match the URL "${u}${h}${d}" because it does not start with the basename, so the won't render anything.`),v==null?null:F.createElement(Gn.Provider,{value:c},F.createElement(Cc.Provider,{children:t,value:v}))}function lT({children:e,location:t}){return ZH(Xm(e),t)}function Xm(e,t=[]){let A=[];return F.Children.forEach(e,(n,i)=>{if(!F.isValidElement(n))return;let a=[...t,i];if(n.type===F.Fragment){A.push.apply(A,Xm(n.props.children,a));return}Qt(n.type===$f,`[${typeof n.type=="string"?n.type:n.type.name}] is not a component. All component children of must be a or `),Qt(!n.props.index||!n.props.children,"An index route cannot have child routes.");let o={id:n.props.id||a.join("-"),caseSensitive:n.props.caseSensitive,element:n.props.element,Component:n.props.Component,index:n.props.index,path:n.props.path,middleware:n.props.middleware,loader:n.props.loader,action:n.props.action,hydrateFallbackElement:n.props.hydrateFallbackElement,HydrateFallback:n.props.HydrateFallback,errorElement:n.props.errorElement,ErrorBoundary:n.props.ErrorBoundary,hasErrorBoundary:n.props.hasErrorBoundary===!0||n.props.ErrorBoundary!=null||n.props.errorElement!=null,shouldRevalidate:n.props.shouldRevalidate,handle:n.props.handle,lazy:n.props.lazy};n.props.children&&(o.children=Xm(n.props.children,a)),A.push(o)}),A}var Wf="get",Jf="application/x-www-form-urlencoded";function $h(e){return e!=null&&typeof e.tagName=="string"}function cT(e){return $h(e)&&e.tagName.toLowerCase()==="button"}function uT(e){return $h(e)&&e.tagName.toLowerCase()==="form"}function fT(e){return $h(e)&&e.tagName.toLowerCase()==="input"}function hT(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function dT(e,t){return e.button===0&&(!t||t==="_self")&&!hT(e)}function Zm(e=""){return new URLSearchParams(typeof e=="string"||Array.isArray(e)||e instanceof URLSearchParams?e:Object.keys(e).reduce((t,A)=>{let n=e[A];return t.concat(Array.isArray(n)?n.map(i=>[A,i]):[[A,n]])},[]))}function gT(e,t){let A=Zm(e);return t&&t.forEach((n,i)=>{A.has(i)||t.getAll(i).forEach(a=>{A.append(i,a)})}),A}var ef=null;function pT(){if(ef===null)try{new FormData(document.createElement("form"),0),ef=!1}catch{ef=!0}return ef}var mT=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Wp(e){return e!=null&&!mT.has(e)?(sn(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Jf}"`),null):e}function BT(e,t){let A,n,i,a,o;if(uT(e)){let c=e.getAttribute("action");n=c?sr(c,t):null,A=e.getAttribute("method")||Wf,i=Wp(e.getAttribute("enctype"))||Jf,a=new FormData(e)}else if(cT(e)||fT(e)&&(e.type==="submit"||e.type==="image")){let c=e.form;if(c==null)throw new Error('Cannot submit a