diff --git a/python/.env.example b/python/.env.example index e8644ea003..bff78961aa 100644 --- a/python/.env.example +++ b/python/.env.example @@ -38,6 +38,9 @@ COPILOTSTUDIOAGENT__AGENTAPPID="" # Anthropic ANTHROPIC_API_KEY="" ANTHROPIC_MODEL="" +# Google Gemini +GEMINI_API_KEY="" +GEMINI_MODEL="" # Ollama OLLAMA_ENDPOINT="" OLLAMA_MODEL="" diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index 99947710c9..0ae0df1454 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- **agent-framework-azure-cosmos**: [BREAKING] `CosmosCheckpointStorage` now uses restricted pickle deserialization by default, matching `FileCheckpointStorage` behavior. If your checkpoints contain application-defined types, pass them via `allowed_checkpoint_types=["my_app.models:MyState"]`. ([#5200](https://github.com/microsoft/agent-framework/issues/5200)) + ## [1.0.1] - 2026-04-09 ### Added diff --git a/python/PACKAGE_STATUS.md b/python/PACKAGE_STATUS.md index 7a726812ff..e6b5f403ce 100644 --- a/python/PACKAGE_STATUS.md +++ b/python/PACKAGE_STATUS.md @@ -31,6 +31,7 @@ Status is grouped into these buckets: | `agent-framework-durabletask` | `python/packages/durabletask` | `beta` | | `agent-framework-foundry` | `python/packages/foundry` | `released` | | `agent-framework-foundry-local` | `python/packages/foundry_local` | `beta` | +| `agent-framework-gemini` | `python/packages/gemini` | `alpha` | | `agent-framework-github-copilot` | `python/packages/github_copilot` | `beta` | | `agent-framework-lab` | `python/packages/lab` | `beta` | | `agent-framework-mem0` | `python/packages/mem0` | `beta` | diff --git a/python/packages/ag-ui/agent_framework_ag_ui/__init__.py b/python/packages/ag-ui/agent_framework_ag_ui/__init__.py index 7d5bfc951b..c787de5167 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui/__init__.py +++ b/python/packages/ag-ui/agent_framework_ag_ui/__init__.py @@ -9,6 +9,7 @@ from ._client import AGUIChatClient from ._endpoint import add_agent_framework_fastapi_endpoint from ._event_converters import AGUIEventConverter from ._http_service import AGUIHttpService +from ._state import state_update from ._types import AgentState, AGUIChatOptions, AGUIRequest, PredictStateConfig, RunMetadata from ._workflow import AgentFrameworkWorkflow, WorkflowFactory @@ -34,5 +35,6 @@ __all__ = [ "PredictStateConfig", "RunMetadata", "DEFAULT_TAGS", + "state_update", "__version__", ] diff --git a/python/packages/ag-ui/agent_framework_ag_ui/_run_common.py b/python/packages/ag-ui/agent_framework_ag_ui/_run_common.py index 81d5fadbbe..58236cdf0e 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui/_run_common.py +++ b/python/packages/ag-ui/agent_framework_ag_ui/_run_common.py @@ -6,6 +6,7 @@ from __future__ import annotations import json import logging +from collections.abc import Mapping from dataclasses import dataclass, field from typing import Any, cast @@ -31,6 +32,7 @@ from ag_ui.core import ( from agent_framework import Content from ._orchestration._predictive_state import PredictiveStateHandler +from ._state import TOOL_RESULT_STATE_KEY from ._utils import generate_event_id, make_json_safe logger = logging.getLogger(__name__) @@ -233,16 +235,66 @@ def _emit_tool_call( return events +def _extract_tool_result_state(content: Content) -> dict[str, Any] | None: + """Extract a deterministic AG-UI state update from a tool-result ``Content``. + + Tools using :func:`agent_framework_ag_ui.state_update` carry the state + payload in ``additional_properties[TOOL_RESULT_STATE_KEY]`` on the inner + text item produced by ``parse_result``. We also check the outer + function_result content's ``additional_properties`` for robustness. + + If multiple items carry state, they are merged in order so later items + override earlier ones (plain ``dict.update`` semantics). + + Returns: + The merged state dict to apply, or ``None`` if no state update is + present. + """ + merged: dict[str, Any] | None = None + + outer_ap = getattr(content, "additional_properties", None) or {} + outer_state = outer_ap.get(TOOL_RESULT_STATE_KEY) + if isinstance(outer_state, dict): + merged = dict(outer_state) + + for item in content.items or (): + item_ap = getattr(item, "additional_properties", None) or {} + item_state = item_ap.get(TOOL_RESULT_STATE_KEY) + if isinstance(item_state, dict): + if merged is None: + merged = dict(item_state) + else: + merged.update(item_state) + + return merged + + def _emit_tool_result_common( call_id: str, raw_result: Any, flow: FlowState, predictive_handler: PredictiveStateHandler | None = None, + *, + state_update: Mapping[str, Any] | None = None, ) -> list[BaseEvent]: """Shared helper for emitting ToolCallEnd + ToolCallResult events and performing FlowState cleanup. Both ``_emit_tool_result`` (standard function results) and ``_emit_mcp_tool_result`` (MCP server tool results) delegate to this function. + + Args: + call_id: Tool call identifier. + raw_result: The stringified tool result content sent back to the LLM. + flow: Current ``FlowState``. + predictive_handler: Optional predictive state handler driven by + ``predict_state_config``. + state_update: Optional deterministic state snapshot produced by a tool + returning :func:`agent_framework_ag_ui.state_update`. When present, + it is merged into ``flow.current_state`` and a ``StateSnapshotEvent`` + is emitted after the ``ToolCallResult`` event. When both + ``predictive_handler`` and ``state_update`` are active, predictive + updates are applied first, then the deterministic merge, and a + single coalesced ``StateSnapshotEvent`` is emitted. """ events: list[BaseEvent] = [] @@ -271,8 +323,18 @@ def _emit_tool_result_common( if predictive_handler: predictive_handler.apply_pending_updates() - if flow.current_state: - events.append(StateSnapshotEvent(snapshot=flow.current_state)) + + if state_update: + flow.current_state.update(state_update) + logger.debug( + "Emitted deterministic tool-result StateSnapshotEvent for call_id=%s (keys=%s)", + call_id, + list(state_update.keys()), + ) + + # Emit a single coalesced snapshot when either mechanism updated state. + if (predictive_handler or state_update) and flow.current_state: + events.append(StateSnapshotEvent(snapshot=flow.current_state)) flow.tool_call_id = None flow.tool_call_name = None @@ -295,7 +357,14 @@ def _emit_tool_result( if not content.call_id: return [] raw_result = content.result if content.result is not None else "" - return _emit_tool_result_common(content.call_id, raw_result, flow, predictive_handler) + state_update = _extract_tool_result_state(content) + return _emit_tool_result_common( + content.call_id, + raw_result, + flow, + predictive_handler, + state_update=state_update, + ) def _emit_approval_request( @@ -460,7 +529,14 @@ def _emit_mcp_tool_result( logger.warning("MCP tool result content missing call_id, skipping") return [] raw_output = content.output if content.output is not None else "" - return _emit_tool_result_common(content.call_id, raw_output, flow, predictive_handler) + state_update = _extract_tool_result_state(content) + return _emit_tool_result_common( + content.call_id, + raw_output, + flow, + predictive_handler, + state_update=state_update, + ) def _close_reasoning_block(flow: FlowState) -> list[BaseEvent]: diff --git a/python/packages/ag-ui/agent_framework_ag_ui/_state.py b/python/packages/ag-ui/agent_framework_ag_ui/_state.py new file mode 100644 index 0000000000..efce2988fa --- /dev/null +++ b/python/packages/ag-ui/agent_framework_ag_ui/_state.py @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Deterministic tool-driven AG-UI state updates. + +Tools wired into the :mod:`agent_framework_ag_ui` endpoint can push a +deterministic state update by returning :func:`state_update`. Unlike +``predict_state_config`` — which emits ``StateDeltaEvent``s optimistically from +LLM-predicted tool call arguments — ``state_update`` runs *after* the tool +executes, so the AG-UI state always reflects the tool's actual return value. + +See issue https://github.com/microsoft/agent-framework/issues/3167 for the +motivating discussion. +""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from agent_framework import Content + +__all__ = ["TOOL_RESULT_STATE_KEY", "state_update"] + + +TOOL_RESULT_STATE_KEY = "__ag_ui_tool_result_state__" +"""Reserved ``Content.additional_properties`` key used to carry a tool-driven +state snapshot from a tool return value through to the AG-UI emitter.""" + + +def state_update( + text: str = "", + *, + state: Mapping[str, Any], +) -> Content: + """Build a tool return value that deterministically updates AG-UI shared state. + + Return the result of this helper from an agent tool to push a state update + to AG-UI clients using the actual tool output, rather than LLM-predicted + tool arguments. + + When the AG-UI endpoint emits the tool result, it will: + + * Forward ``text`` to the LLM as the normal ``function_result`` content. + * Merge ``state`` into ``FlowState.current_state``. + * Emit a deterministic ``StateSnapshotEvent`` after the ``ToolCallResult`` + event so frontends observe the updated state deterministically. If + predictive state is enabled, a predictive snapshot may be emitted first. + + Example: + .. code-block:: python + + from agent_framework import tool + from agent_framework_ag_ui import state_update + + + @tool + async def get_weather(city: str) -> Content: + data = await _fetch_weather(city) + return state_update( + text=f"Weather in {city}: {data['temp']}°C {data['conditions']}", + state={"weather": {"city": city, **data}}, + ) + + Args: + text: Text passed back to the LLM as the ``function_result`` content. + Defaults to an empty string for tools whose only output is a state + update. + state: A mapping merged into the AG-UI shared state via JSON-compatible + ``dict.update`` semantics. Nested dicts are replaced, not deep-merged. + + Returns: + A ``Content`` object with ``type="text"``. The state payload rides in + ``additional_properties`` under :data:`TOOL_RESULT_STATE_KEY` and is + extracted by the AG-UI emitter. + + Raises: + TypeError: If ``state`` is not a ``Mapping``. + """ + if not isinstance(state, Mapping): + raise TypeError(f"state_update() 'state' must be a Mapping, got {type(state).__name__}") + return Content.from_text( + text, + additional_properties={TOOL_RESULT_STATE_KEY: dict(state)}, + ) diff --git a/python/packages/ag-ui/agent_framework_ag_ui_examples/agents/weather_state_agent.py b/python/packages/ag-ui/agent_framework_ag_ui_examples/agents/weather_state_agent.py new file mode 100644 index 0000000000..f556af3458 --- /dev/null +++ b/python/packages/ag-ui/agent_framework_ag_ui_examples/agents/weather_state_agent.py @@ -0,0 +1,92 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Deterministic tool-driven AG-UI state example. + +This sample demonstrates how a tool can push a *deterministic* state update +to the AG-UI frontend based on its actual return value — in contrast to +``predict_state_config`` which fires optimistically from LLM-predicted tool +call arguments. See issue https://github.com/microsoft/agent-framework/issues/3167. + +The :func:`agent_framework_ag_ui.state_update` helper wraps a text result +together with a state snapshot. When a tool returns one of these, the AG-UI +endpoint merges the snapshot into the shared state and emits a +``StateSnapshotEvent`` after the tool result. +""" + +from __future__ import annotations + +from typing import Any + +from agent_framework import Agent, Content, SupportsChatGetResponse, tool +from agent_framework.ag_ui import AgentFrameworkAgent + +from agent_framework_ag_ui import state_update + +# Simulated weather database — in the issue's motivating example the tool +# would instead call a real weather API. +_WEATHER_DB: dict[str, dict[str, Any]] = { + "seattle": {"temperature": 11, "conditions": "rainy", "humidity": 75}, + "san francisco": {"temperature": 14, "conditions": "foggy", "humidity": 85}, + "new york city": {"temperature": 18, "conditions": "sunny", "humidity": 60}, + "miami": {"temperature": 29, "conditions": "hot and humid", "humidity": 90}, + "chicago": {"temperature": 9, "conditions": "windy", "humidity": 65}, +} + + +@tool +async def get_weather(location: str) -> Content: + """Fetch current weather for a location and push it into AG-UI shared state. + + Unlike ``predict_state_config`` — which derives state optimistically from + LLM-predicted tool call arguments — this tool uses ``state_update`` to + forward the *actual* fetched weather to the frontend. The ``text`` goes + back to the LLM as the normal tool result, and the ``state`` dict is merged + into the AG-UI shared state. + + Args: + location: City name to look up. + + Returns: + A :class:`Content` carrying both the LLM-visible text result and a + deterministic state snapshot. + """ + key = location.lower() + data = _WEATHER_DB.get( + key, + {"temperature": 21, "conditions": "partly cloudy", "humidity": 50}, + ) + weather_record = {"location": location, **data} + return state_update( + text=( + f"The weather in {location} is {data['conditions']} at " + f"{data['temperature']}°C with {data['humidity']}% humidity." + ), + state={"weather": weather_record}, + ) + + +def weather_state_agent(client: SupportsChatGetResponse[Any]) -> AgentFrameworkAgent: + """Create an AG-UI agent with a deterministic tool-driven state tool.""" + agent = Agent[Any]( + name="weather_state_agent", + instructions=( + "You are a weather assistant. When a user asks about the weather " + "in a city, call the get_weather tool and use its output to give a " + "friendly, concise reply. The tool also updates the shared UI state " + "so the frontend can render a weather card from the `weather` key." + ), + client=client, + tools=[get_weather], + ) + + return AgentFrameworkAgent( + agent=agent, + name="WeatherStateAgent", + description="Weather agent that deterministically updates shared state from tool results.", + state_schema={ + "weather": { + "type": "object", + "description": "Last fetched weather record", + }, + }, + ) diff --git a/python/packages/ag-ui/agent_framework_ag_ui_examples/server/main.py b/python/packages/ag-ui/agent_framework_ag_ui_examples/server/main.py index 31a7c47963..4b7d56fba5 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui_examples/server/main.py +++ b/python/packages/ag-ui/agent_framework_ag_ui_examples/server/main.py @@ -24,6 +24,7 @@ from ..agents.subgraphs_agent import subgraphs_agent from ..agents.task_steps_agent import task_steps_agent_wrapped from ..agents.ui_generator_agent import ui_generator_agent from ..agents.weather_agent import weather_agent +from ..agents.weather_state_agent import weather_state_agent AnthropicClient: type[Any] | None try: @@ -141,6 +142,14 @@ add_agent_framework_fastapi_endpoint( path="/subgraphs", ) +# Deterministic Tool-Driven State - tool returns state_update() to push snapshot +# from actual tool output (see issue #3167). +add_agent_framework_fastapi_endpoint( + app=app, + agent=weather_state_agent(client), + path="/deterministic_state", +) + def main(): """Run the server.""" diff --git a/python/packages/ag-ui/tests/ag_ui/golden/test_scenario_deterministic_state.py b/python/packages/ag-ui/tests/ag_ui/golden/test_scenario_deterministic_state.py new file mode 100644 index 0000000000..70bc5c129b --- /dev/null +++ b/python/packages/ag-ui/tests/ag_ui/golden/test_scenario_deterministic_state.py @@ -0,0 +1,267 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Golden event-stream tests for the deterministic tool-driven state scenario. + +Covers issue https://github.com/microsoft/agent-framework/issues/3167 — a tool +returning :func:`agent_framework_ag_ui.state_update` must push a deterministic +``StateSnapshotEvent`` derived from its actual return value, orthogonal to the +optimistic ``predict_state_config`` path. These golden tests pin the user-visible +event stream so additive changes cannot silently regress it. +""" + +from __future__ import annotations + +from typing import Any + +from agent_framework import AgentResponseUpdate, Content +from conftest import StubAgent +from event_stream import EventStream + +from agent_framework_ag_ui import AgentFrameworkAgent, state_update + +STATE_SCHEMA = { + "weather": {"type": "object", "description": "Last fetched weather"}, +} + + +def _build_agent(updates: list[AgentResponseUpdate], **kwargs: Any) -> AgentFrameworkAgent: + stub = StubAgent(updates=updates) + kwargs.setdefault("state_schema", STATE_SCHEMA) + return AgentFrameworkAgent(agent=stub, **kwargs) + + +async def _run(agent: AgentFrameworkAgent, payload: dict[str, Any]) -> EventStream: + return EventStream([event async for event in agent.run(payload)]) + + +PAYLOAD: dict[str, Any] = { + "thread_id": "thread-det-state", + "run_id": "run-det-state", + "messages": [{"role": "user", "content": "What's the weather in SF?"}], + "state": {"weather": {}}, +} + + +def _tool_call(call_id: str, name: str, arguments: str) -> AgentResponseUpdate: + return AgentResponseUpdate( + contents=[Content.from_function_call(name=name, call_id=call_id, arguments=arguments)], + role="assistant", + ) + + +def _tool_result_with_state(call_id: str, text: str, state: dict[str, Any]) -> AgentResponseUpdate: + """Build a function_result update whose inner item carries a state marker. + + This mirrors what the core framework produces when a real ``@tool`` returns + :func:`state_update`: ``parse_result`` keeps the ``Content`` as-is, and + ``Content.from_function_result`` preserves its ``additional_properties`` + inside ``items``. + """ + return AgentResponseUpdate( + contents=[ + Content.from_function_result( + call_id=call_id, + result=[state_update(text=text, state=state)], + ) + ], + role="assistant", + ) + + +# ── Golden stream tests ── + + +async def test_deterministic_state_emits_snapshot_after_tool_result() -> None: + """The happy path: STATE_SNAPSHOT follows TOOL_CALL_RESULT in order.""" + updates = [ + _tool_call("call-1", "get_weather", '{"city": "SF"}'), + _tool_result_with_state( + "call-1", + text="Weather in SF: 14°C foggy", + state={"weather": {"city": "SF", "temp": 14, "conditions": "foggy"}}, + ), + AgentResponseUpdate( + contents=[Content.from_text(text="It's 14°C and foggy in SF.")], + role="assistant", + ), + ] + agent = _build_agent(updates) + stream = await _run(agent, PAYLOAD) + + stream.assert_bookends() + stream.assert_no_run_error() + stream.assert_tool_calls_balanced() + stream.assert_text_messages_balanced() + + # Ordered subsequence: the deterministic STATE_SNAPSHOT must follow the + # TOOL_CALL_RESULT. This is the central contract for #3167. + stream.assert_ordered_types( + [ + "RUN_STARTED", + "TOOL_CALL_START", + "TOOL_CALL_ARGS", + "TOOL_CALL_END", + "TOOL_CALL_RESULT", + "STATE_SNAPSHOT", + "RUN_FINISHED", + ] + ) + + # The final STATE_SNAPSHOT must carry the tool-driven state. + snapshot = stream.snapshot() + assert snapshot["weather"] == {"city": "SF", "temp": 14, "conditions": "foggy"} + + +async def test_deterministic_state_does_not_fire_for_plain_tool_result() -> None: + """Regression guard: tools returning plain strings must NOT emit a new STATE_SNAPSHOT. + + The initial STATE_SNAPSHOT fires once from the schema + initial payload + state. A plain (non-state_update) tool result must not add another one. + """ + updates = [ + _tool_call("call-1", "get_weather", '{"city": "SF"}'), + AgentResponseUpdate( + contents=[Content.from_function_result(call_id="call-1", result="14°C foggy")], + role="assistant", + ), + AgentResponseUpdate( + contents=[Content.from_text(text="It's 14°C and foggy.")], + role="assistant", + ), + ] + agent = _build_agent(updates) + stream = await _run(agent, PAYLOAD) + + stream.assert_bookends() + stream.assert_no_run_error() + + snapshots = stream.get("STATE_SNAPSHOT") + # Only the initial snapshot (from state_schema + payload state) should exist. + # No deterministic snapshot should have been added by the plain tool result. + assert len(snapshots) == 1, ( + f"Expected exactly 1 STATE_SNAPSHOT (initial only) for plain tool result; " + f"got {len(snapshots)}. Snapshots: {[s.snapshot for s in snapshots]}" + ) + + +async def test_deterministic_state_merges_into_initial_state() -> None: + """The tool-driven snapshot must merge into, not replace, pre-existing state keys.""" + payload = dict(PAYLOAD) + payload["state"] = {"weather": {}, "user_preferences": {"unit": "C"}} + + updates = [ + _tool_call("call-1", "get_weather", '{"city": "SF"}'), + _tool_result_with_state( + "call-1", + text="Weather: 14°C", + state={"weather": {"city": "SF", "temp": 14}}, + ), + ] + agent = _build_agent(updates, state_schema={**STATE_SCHEMA, "user_preferences": {"type": "object"}}) + stream = await _run(agent, payload) + + stream.assert_bookends() + stream.assert_no_run_error() + + final_snapshot = stream.snapshot() + assert final_snapshot["weather"] == {"city": "SF", "temp": 14} + assert final_snapshot["user_preferences"] == {"unit": "C"}, ( + "Pre-existing state keys must survive the deterministic merge" + ) + + +async def test_deterministic_state_llm_visible_text_is_clean() -> None: + """The LLM-visible TOOL_CALL_RESULT content must not leak the state marker key.""" + updates = [ + _tool_call("call-1", "get_weather", '{"city": "SF"}'), + _tool_result_with_state( + "call-1", + text="Weather in SF: 14°C foggy", + state={"weather": {"city": "SF", "temp": 14}}, + ), + ] + agent = _build_agent(updates) + stream = await _run(agent, PAYLOAD) + + result = stream.first("TOOL_CALL_RESULT") + assert result.content == "Weather in SF: 14°C foggy" + # The marker key must never appear in the content sent back to the LLM. + assert "__ag_ui_tool_result_state__" not in result.content + assert "weather" not in result.content # not as a raw state dump + + +async def test_deterministic_state_multiple_tools_merge_in_order() -> None: + """Two state-updating tools in one run merge in order; later wins on key collisions.""" + updates = [ + _tool_call("call-a", "get_weather", '{"city": "SF"}'), + _tool_result_with_state( + "call-a", + text="First result", + state={"weather": {"city": "SF", "temp": 14}, "source": "primary"}, + ), + _tool_call("call-b", "get_weather_refined", '{"city": "SF"}'), + _tool_result_with_state( + "call-b", + text="Refined result", + state={"source": "refined"}, + ), + AgentResponseUpdate( + contents=[Content.from_text(text="Here you go.")], + role="assistant", + ), + ] + agent = _build_agent( + updates, + state_schema={**STATE_SCHEMA, "source": {"type": "string"}}, + ) + stream = await _run(agent, PAYLOAD) + + stream.assert_bookends() + stream.assert_tool_calls_balanced() + stream.assert_no_run_error() + + # Two tool-driven snapshots emitted (one per tool) plus the initial snapshot. + snapshots = stream.get("STATE_SNAPSHOT") + assert len(snapshots) >= 2, f"Expected at least 2 STATE_SNAPSHOTs; got {len(snapshots)}" + + final = stream.snapshot() + assert final["weather"] == {"city": "SF", "temp": 14} + # Later tool must override earlier tool on the shared key. + assert final["source"] == "refined" + + +async def test_deterministic_state_coexists_with_predict_state_config() -> None: + """Predictive state and deterministic state must coexist without clobbering each other.""" + predict_config = { + "draft": { + "tool": "write_draft", + "tool_argument": "body", + } + } + updates = [ + # Predictive tool: its argument "body" populates state.draft optimistically. + _tool_call("call-1", "write_draft", '{"body": "Hello world"}'), + # Then a deterministic tool result landing a different key. + _tool_result_with_state( + "call-1", + text="Draft saved", + state={"weather": {"city": "SF", "temp": 14}}, + ), + ] + agent = _build_agent( + updates, + state_schema={**STATE_SCHEMA, "draft": {"type": "string"}}, + predict_state_config=predict_config, + require_confirmation=False, + ) + payload = dict(PAYLOAD) + payload["state"] = {"weather": {}, "draft": ""} + stream = await _run(agent, payload) + + stream.assert_bookends() + stream.assert_no_run_error() + stream.assert_tool_calls_balanced() + + # The final observed state must contain both the deterministic and predictive contributions. + final = stream.snapshot() + assert final["weather"] == {"city": "SF", "temp": 14}, f"Deterministic state missing from final snapshot: {final}" diff --git a/python/packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py b/python/packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py index e6f58ef0fd..5ea284c68d 100644 --- a/python/packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py +++ b/python/packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py @@ -1405,3 +1405,95 @@ async def test_fabricated_rejection_without_pending_approval_is_blocked(streamin for content in msg.contents: if content.type == "function_result" and content.call_id == "fake_reject_001": assert False, "Fabricated rejection response leaked as function_result into LLM messages" + + +async def test_state_update_end_to_end_via_real_tool_invocation(streaming_chat_client_stub): + """End-to-end coverage for issue #3167: a real ``@tool`` returning ``state_update`` must + emit a deterministic STATE_SNAPSHOT through the full pipeline. + + This test exercises the entire chain that a user would hit in production: + ``FunctionInvocationLayer`` executes the tool, ``FunctionTool.parse_result`` + preserves the returned ``Content`` with its ``additional_properties`` marker, + ``Content.from_function_result`` carries the marker through in ``items``, + and the AG-UI emitter extracts it via ``_extract_tool_result_state`` and + emits the snapshot. A regression anywhere in that chain will fail this test. + """ + from agent_framework import tool + from agent_framework.ag_ui import AgentFrameworkAgent + + from agent_framework_ag_ui import state_update + + @tool(name="get_weather", description="Get current weather for a city.") + async def get_weather(city: str) -> Content: + return state_update( + text=f"Weather in {city}: 14°C foggy", + state={"weather": {"city": city, "temperature": 14, "conditions": "foggy"}}, + ) + + call_count = {"n": 0} + + async def stream_fn( + messages: MutableSequence[Message], options: ChatOptions, **kwargs: Any + ) -> AsyncIterator[ChatResponseUpdate]: + """First turn proposes a tool call; second turn (after tool execution) returns text.""" + call_count["n"] += 1 + if call_count["n"] == 1: + yield ChatResponseUpdate( + contents=[ + Content.from_function_call( + name="get_weather", + call_id="call-weather-1", + arguments='{"city": "SF"}', + ) + ] + ) + else: + yield ChatResponseUpdate(contents=[Content.from_text(text="It's 14°C and foggy in SF.")]) + + agent = Agent( + client=streaming_chat_client_stub(stream_fn), + name="weather_agent", + instructions="Answer weather questions.", + tools=[get_weather], + ) + wrapper = AgentFrameworkAgent( + agent=agent, + state_schema={"weather": {"type": "object"}}, + ) + + events: list[Any] = [] + async for event in wrapper.run( + { + "thread_id": "thread-weather", + "run_id": "run-weather", + "messages": [{"role": "user", "content": "What's the weather in SF?"}], + "state": {"weather": {}}, + } + ): + events.append(event) + + types = [e.type for e in events] + + # The tool call must be visible in the stream. + assert "TOOL_CALL_START" in types, f"Missing TOOL_CALL_START in: {types}" + assert "TOOL_CALL_RESULT" in types, f"Missing TOOL_CALL_RESULT in: {types}" + + # A STATE_SNAPSHOT must be emitted after the tool result. + tool_result_idx = types.index("TOOL_CALL_RESULT") + snapshot_indices_after_result = [i for i, t in enumerate(types) if t == "STATE_SNAPSHOT" and i > tool_result_idx] + assert snapshot_indices_after_result, ( + f"Expected a STATE_SNAPSHOT after TOOL_CALL_RESULT (index {tool_result_idx}); got types: {types}" + ) + + # The tool's deterministic snapshot carries the actual fetched weather data. + final_snapshot = events[snapshot_indices_after_result[-1]].snapshot + assert final_snapshot["weather"] == { + "city": "SF", + "temperature": 14, + "conditions": "foggy", + } + + # The LLM-visible tool result must carry the plain text, not the marker key. + tool_result_event = next(e for e in events if e.type == "TOOL_CALL_RESULT") + assert tool_result_event.content == "Weather in SF: 14°C foggy" + assert "__ag_ui_tool_result_state__" not in tool_result_event.content diff --git a/python/packages/ag-ui/tests/ag_ui/test_public_exports.py b/python/packages/ag-ui/tests/ag_ui/test_public_exports.py index 433935fb24..ea570f50a6 100644 --- a/python/packages/ag-ui/tests/ag_ui/test_public_exports.py +++ b/python/packages/ag-ui/tests/ag_ui/test_public_exports.py @@ -18,7 +18,24 @@ def test_core_ag_ui_lazy_exports_include_only_stable_api() -> None: assert hasattr(ag_ui, "AgentFrameworkAgent") assert hasattr(ag_ui, "AGUIChatClient") assert hasattr(ag_ui, "add_agent_framework_fastapi_endpoint") + assert hasattr(ag_ui, "state_update") assert not hasattr(ag_ui, "WorkflowFactory") assert not hasattr(ag_ui, "AGUIRequest") assert not hasattr(ag_ui, "RunMetadata") + + +def test_agent_framework_ag_ui_exports_state_update() -> None: + """Runtime package should export the ``state_update`` helper.""" + from agent_framework_ag_ui import state_update + + assert callable(state_update) + + +def test_core_ag_ui_lazy_exports_include_event_converter_and_http_service() -> None: + """Core facade must expose AGUIEventConverter, AGUIHttpService, and __version__.""" + from agent_framework import ag_ui + + assert hasattr(ag_ui, "AGUIEventConverter") + assert hasattr(ag_ui, "AGUIHttpService") + assert hasattr(ag_ui, "__version__") diff --git a/python/packages/ag-ui/tests/ag_ui/test_run_common.py b/python/packages/ag-ui/tests/ag_ui/test_run_common.py index 526a3c33c1..27294d9171 100644 --- a/python/packages/ag-ui/tests/ag_ui/test_run_common.py +++ b/python/packages/ag-ui/tests/ag_ui/test_run_common.py @@ -2,14 +2,20 @@ """Tests for _run_common.py edge cases.""" +from ag_ui.core import EventType from agent_framework import Content +from agent_framework_ag_ui import state_update +from agent_framework_ag_ui._orchestration._predictive_state import PredictiveStateHandler from agent_framework_ag_ui._run_common import ( FlowState, + _emit_mcp_tool_result, _emit_tool_result, _extract_resume_payload, + _extract_tool_result_state, _normalize_resume_interrupts, ) +from agent_framework_ag_ui._state import TOOL_RESULT_STATE_KEY class TestNormalizeResumeInterrupts: @@ -120,3 +126,223 @@ class TestEmitToolResult: assert "TEXT_MESSAGE_END" in event_types assert flow.message_id is None assert flow.accumulated_text == "" + + +class TestStateUpdateHelper: + """Tests for the public ``state_update`` helper.""" + + def test_builds_text_content_with_state_marker(self): + """state_update returns a text Content carrying state in additional_properties.""" + c = state_update(text="done", state={"weather": {"temp": 14}}) + assert c.type == "text" + assert c.text == "done" + assert c.additional_properties == { + TOOL_RESULT_STATE_KEY: {"weather": {"temp": 14}}, + } + + def test_empty_text_is_allowed(self): + """State-only tools can omit the text argument.""" + c = state_update(state={"steps": ["a", "b"]}) + assert c.text == "" + assert c.additional_properties[TOOL_RESULT_STATE_KEY] == {"steps": ["a", "b"]} + + def test_non_mapping_state_raises(self): + """Passing a non-mapping value for state raises TypeError.""" + import pytest + + with pytest.raises(TypeError): + state_update(text="t", state=["not", "a", "mapping"]) # type: ignore[arg-type] + + def test_state_is_copied_defensively(self): + """Mutating the caller's dict after ``state_update`` must not mutate the content.""" + caller_state = {"weather": {"temp": 14}} + c = state_update(text="ok", state=caller_state) + caller_state["weather"]["temp"] = 99 + # The top-level dict was copied, so replacing the key in caller_state + # would not affect the Content, but nested dicts share references — document + # this by asserting only the top-level copy semantics. + assert TOOL_RESULT_STATE_KEY in c.additional_properties + inner = c.additional_properties[TOOL_RESULT_STATE_KEY] + assert inner is not caller_state + + +class TestExtractToolResultState: + """Tests for ``_extract_tool_result_state``.""" + + def test_returns_none_for_plain_string_result(self): + content = Content.from_function_result(call_id="c1", result="plain") + assert _extract_tool_result_state(content) is None + + def test_extracts_state_from_inner_item(self): + tool_return = state_update(text="hi", state={"k": 1}) + content = Content.from_function_result(call_id="c1", result=[tool_return]) + assert _extract_tool_result_state(content) == {"k": 1} + + def test_extracts_state_from_outer_additional_properties(self): + """Outer function_result content can also carry state (legacy/advanced use).""" + content = Content.from_function_result( + call_id="c1", + result="hi", + additional_properties={TOOL_RESULT_STATE_KEY: {"k": 1}}, + ) + assert _extract_tool_result_state(content) == {"k": 1} + + def test_merges_multiple_items(self): + a = state_update(text="a", state={"k": 1, "shared": "from_a"}) + b = state_update(text="b", state={"shared": "from_b", "extra": True}) + content = Content.from_function_result(call_id="c1", result=[a, b]) + merged = _extract_tool_result_state(content) + assert merged == {"k": 1, "shared": "from_b", "extra": True} + + def test_ignores_non_dict_marker_value(self): + """A garbled marker value must not break extraction (defensive guard).""" + bad = Content.from_text( + "hi", + additional_properties={TOOL_RESULT_STATE_KEY: "not-a-dict"}, + ) + content = Content.from_function_result(call_id="c1", result=[bad]) + assert _extract_tool_result_state(content) is None + + +class TestEmitToolResultWithState: + """Tests for the deterministic state emission in ``_emit_tool_result``.""" + + def test_emits_state_snapshot_after_tool_call_result(self): + """Tool returning state_update produces a StateSnapshotEvent right after the result.""" + tool_return = state_update( + text="Weather: 14°C", + state={"weather": {"temp": 14, "conditions": "foggy"}}, + ) + content = Content.from_function_result(call_id="call_1", result=[tool_return]) + flow = FlowState() + + events = _emit_tool_result(content, flow) + event_types = [e.type for e in events] + + # Expect TOOL_CALL_END, TOOL_CALL_RESULT, STATE_SNAPSHOT in that order. + assert event_types[0] == EventType.TOOL_CALL_END + assert event_types[1] == EventType.TOOL_CALL_RESULT + state_idx = event_types.index(EventType.STATE_SNAPSHOT) + assert state_idx == 2 + assert events[state_idx].snapshot == {"weather": {"temp": 14, "conditions": "foggy"}} + + def test_updates_flow_current_state(self): + tool_return = state_update(text="", state={"a": 1}) + content = Content.from_function_result(call_id="c1", result=[tool_return]) + flow = FlowState(current_state={"existing": "value"}) + + _emit_tool_result(content, flow) + + # Existing keys must survive (merge semantics), new keys must be added. + assert flow.current_state == {"existing": "value", "a": 1} + + def test_merge_overrides_existing_key(self): + tool_return = state_update(text="", state={"existing": "new"}) + content = Content.from_function_result(call_id="c1", result=[tool_return]) + flow = FlowState(current_state={"existing": "old", "other": 1}) + + _emit_tool_result(content, flow) + + assert flow.current_state == {"existing": "new", "other": 1} + + def test_no_state_snapshot_when_result_has_no_state(self): + """Plain tool results must not emit a StateSnapshotEvent.""" + content = Content.from_function_result(call_id="c1", result="plain") + flow = FlowState() + + events = _emit_tool_result(content, flow) + assert all(e.type != EventType.STATE_SNAPSHOT for e in events) + + def test_tool_result_content_text_unchanged(self): + """The text sent to the LLM must not leak the state marker.""" + tool_return = state_update(text="Weather: 14°C", state={"weather": {"temp": 14}}) + content = Content.from_function_result(call_id="c1", result=[tool_return]) + flow = FlowState() + + events = _emit_tool_result(content, flow) + result_events = [e for e in events if e.type == EventType.TOOL_CALL_RESULT] + assert len(result_events) == 1 + assert result_events[0].content == "Weather: 14°C" + assert TOOL_RESULT_STATE_KEY not in result_events[0].content + + def test_coexists_with_active_predictive_state_handler(self): + """Both predictive and deterministic state produce a single coalesced snapshot. + + Predictive state (``predict_state_config``) and deterministic state + (``state_update``) are two independent mechanisms. When both are active, + a single coalesced ``StateSnapshotEvent`` is emitted containing the + merged result of both contributions. + """ + flow = FlowState(current_state={"preexisting": "value"}) + handler = PredictiveStateHandler( + predict_state_config={"draft": {"tool": "write_draft", "tool_argument": "body"}}, + current_state=flow.current_state, + ) + + tool_return = state_update(text="Draft written", state={"draft_final": True}) + content = Content.from_function_result(call_id="c1", result=[tool_return]) + + events = _emit_tool_result(content, flow, predictive_handler=handler) + + # Exactly one coalesced snapshot must be emitted containing all merged keys. + snapshots = [e for e in events if e.type == EventType.STATE_SNAPSHOT] + assert len(snapshots) == 1 + assert snapshots[0].snapshot["draft_final"] is True + assert snapshots[0].snapshot["preexisting"] == "value" + assert flow.current_state["draft_final"] is True + assert flow.current_state["preexisting"] == "value" + + def test_predictive_and_deterministic_emit_single_snapshot(self): + """When both predictive_handler and state_update are active, only one snapshot is emitted.""" + flow = FlowState(current_state={"existing": "yes"}) + handler = PredictiveStateHandler( + predict_state_config={"draft": {"tool": "write_draft", "tool_argument": "body"}}, + current_state=flow.current_state, + ) + + tool_return = state_update(text="ok", state={"new_key": 42}) + content = Content.from_function_result(call_id="c1", result=[tool_return]) + + events = _emit_tool_result(content, flow, predictive_handler=handler) + + snapshots = [e for e in events if e.type == EventType.STATE_SNAPSHOT] + assert len(snapshots) == 1, f"Expected 1 coalesced snapshot, got {len(snapshots)}" + assert snapshots[0].snapshot == {"existing": "yes", "new_key": 42} + + +class TestEmitMcpToolResultWithState: + """MCP tool results should honour the same state_update marker. + + MCP results come from an external MCP server rather than a locally + executed ``@tool`` function, so they do not flow through ``parse_result`` + and ``content.items`` is typically empty. State is instead carried on the + outer content's ``additional_properties`` (e.g. by middleware that + inspects the MCP output and attaches a marker). ``_extract_tool_result_state`` + supports both locations so this path remains usable. + """ + + def test_mcp_tool_result_emits_state_snapshot_from_additional_properties(self): + content = Content.from_mcp_server_tool_result( + call_id="mcp_1", + output="server result", + additional_properties={TOOL_RESULT_STATE_KEY: {"mcp_ok": True}}, + ) + flow = FlowState() + + events = _emit_mcp_tool_result(content, flow) + event_types = [e.type for e in events] + + assert EventType.TOOL_CALL_END in event_types + assert EventType.TOOL_CALL_RESULT in event_types + assert EventType.STATE_SNAPSHOT in event_types + assert flow.current_state == {"mcp_ok": True} + + def test_mcp_tool_result_without_state_emits_no_snapshot(self): + content = Content.from_mcp_server_tool_result( + call_id="mcp_1", + output="server result", + ) + flow = FlowState() + + events = _emit_mcp_tool_result(content, flow) + assert all(e.type != EventType.STATE_SNAPSHOT for e in events) diff --git a/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py b/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py index 1b6257f203..496d95d7c3 100644 --- a/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py +++ b/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py @@ -43,9 +43,34 @@ class CosmosCheckpointStorage: ``FileCheckpointStorage``, allowing full Python object fidelity for complex workflow state while keeping the document structure human-readable. - SECURITY WARNING: Checkpoints use pickle for data serialization. Only load - checkpoints from trusted sources. Loading a malicious checkpoint can execute - arbitrary code. + Security warning: checkpoints use pickle for non-JSON-native values. Loading + checkpoints from untrusted sources is unsafe and can execute arbitrary code + during deserialization. The built-in deserialization restrictions reduce risk, + but they do not make untrusted checkpoints safe to load. Extending + ``allowed_checkpoint_types`` may further increase risk and should only be done + for trusted application types. + + By default, checkpoint deserialization is restricted to a built-in set of safe + Python types (primitives, datetime, uuid, ...) and all ``agent_framework`` + internal types. To allow additional application-specific types, pass them via + the ``allowed_checkpoint_types`` parameter using ``"module:qualname"`` format. + + Example: + + .. code-block:: python + + from azure.identity.aio import DefaultAzureCredential + from agent_framework_azure_cosmos import CosmosCheckpointStorage + + storage = CosmosCheckpointStorage( + endpoint="https://my-account.documents.azure.com:443/", + credential=DefaultAzureCredential(), + database_name="agent-db", + container_name="checkpoints", + allowed_checkpoint_types=[ + "my_app.models:MyState", + ], + ) The database and container are created automatically on first use if they do not already exist. The container uses partition key @@ -97,6 +122,7 @@ class CosmosCheckpointStorage: container_client: ContainerProxy | None = None, env_file_path: str | None = None, env_file_encoding: str | None = None, + allowed_checkpoint_types: list[str] | None = None, ) -> None: """Initialize the Azure Cosmos DB checkpoint storage. @@ -129,10 +155,15 @@ class CosmosCheckpointStorage: container_client: Pre-created Cosmos container client. env_file_path: Path to environment file for loading settings. env_file_encoding: Encoding of the environment file. + allowed_checkpoint_types: Additional types (beyond the built-in safe set + and framework types) that are permitted during checkpoint + deserialization. Each entry should be a ``"module:qualname"`` + string (e.g., ``"my_app.models:MyState"``). """ self._cosmos_client: CosmosClient | None = cosmos_client self._container_proxy: ContainerProxy | None = container_client self._owns_client = False + self._allowed_types: frozenset[str] = frozenset(allowed_checkpoint_types or []) if self._container_proxy is not None: self.database_name: str = database_name or "" @@ -401,8 +432,7 @@ class CosmosCheckpointStorage: partition_key=PartitionKey(path="/workflow_name"), ) - @staticmethod - def _document_to_checkpoint(document: dict[str, Any]) -> WorkflowCheckpoint: + def _document_to_checkpoint(self, document: dict[str, Any]) -> WorkflowCheckpoint: """Convert a Cosmos DB document back to a WorkflowCheckpoint. Strips Cosmos DB system properties (``_rid``, ``_self``, ``_etag``, @@ -413,7 +443,7 @@ class CosmosCheckpointStorage: cosmos_keys = {"id", "_rid", "_self", "_etag", "_attachments", "_ts"} cleaned = {k: v for k, v in document.items() if k not in cosmos_keys} - decoded = decode_checkpoint_value(cleaned) + decoded = decode_checkpoint_value(cleaned, allowed_types=self._allowed_types) return WorkflowCheckpoint.from_dict(decoded) @staticmethod diff --git a/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py b/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py index 52155d0e21..016220e693 100644 --- a/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py +++ b/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py @@ -6,6 +6,7 @@ import os import uuid from collections.abc import AsyncIterator from contextlib import suppress +from dataclasses import dataclass from typing import Any from unittest.mock import AsyncMock, MagicMock, patch @@ -595,3 +596,142 @@ async def test_cosmos_checkpoint_storage_roundtrip_with_emulator() -> None: finally: with suppress(Exception): await cosmos_client.delete_database(database_name) + + +# --- Tests for allowed_checkpoint_types --- + + +@dataclass +class _AppState: + """Application-defined state type used to test allowed_checkpoint_types.""" + + label: str + count: int + + +_APP_STATE_TYPE_KEY = f"{_AppState.__module__}:{_AppState.__qualname__}" + + +def _make_checkpoint_with_state(state: dict[str, Any]) -> WorkflowCheckpoint: + """Create a checkpoint with custom state for serialization tests.""" + return WorkflowCheckpoint( + workflow_name="test-workflow", + graph_signature_hash="abc123", + timestamp="2025-01-01T00:00:00+00:00", + state=state, + iteration_count=1, + ) + + +async def test_init_accepts_allowed_checkpoint_types(mock_container: MagicMock) -> None: + """CosmosCheckpointStorage.__init__ accepts allowed_checkpoint_types.""" + storage = CosmosCheckpointStorage( + container_client=mock_container, + allowed_checkpoint_types=["some.module:SomeType"], + ) + assert storage is not None + + +async def test_load_allows_builtin_safe_types(mock_container: MagicMock) -> None: + """Built-in safe types load without opt-in via allowed_checkpoint_types.""" + from datetime import datetime, timezone + + checkpoint = _make_checkpoint_with_state({ + "ts": datetime(2025, 1, 1, tzinfo=timezone.utc), + "tags": {1, 2, 3}, + }) + doc = _checkpoint_to_cosmos_document(checkpoint) + mock_container.query_items.return_value = _to_async_iter([doc]) + + storage = CosmosCheckpointStorage(container_client=mock_container) + loaded = await storage.load(checkpoint.checkpoint_id) + + assert loaded.state["ts"] == datetime(2025, 1, 1, tzinfo=timezone.utc) + assert loaded.state["tags"] == {1, 2, 3} + + +async def test_load_blocks_unlisted_app_type(mock_container: MagicMock) -> None: + """Application types are blocked when not listed in allowed_checkpoint_types.""" + checkpoint = _make_checkpoint_with_state({"data": _AppState(label="x", count=1)}) + doc = _checkpoint_to_cosmos_document(checkpoint) + mock_container.query_items.return_value = _to_async_iter([doc]) + + storage = CosmosCheckpointStorage(container_client=mock_container) + + with pytest.raises(WorkflowCheckpointException, match="deserialization blocked"): + await storage.load(checkpoint.checkpoint_id) + + +async def test_load_allows_listed_app_type(mock_container: MagicMock) -> None: + """Application types are allowed when listed in allowed_checkpoint_types.""" + checkpoint = _make_checkpoint_with_state({"data": _AppState(label="ok", count=7)}) + doc = _checkpoint_to_cosmos_document(checkpoint) + mock_container.query_items.return_value = _to_async_iter([doc]) + + storage = CosmosCheckpointStorage( + container_client=mock_container, + allowed_checkpoint_types=[_APP_STATE_TYPE_KEY], + ) + loaded = await storage.load(checkpoint.checkpoint_id) + + assert isinstance(loaded.state["data"], _AppState) + assert loaded.state["data"].label == "ok" + assert loaded.state["data"].count == 7 + + +async def test_list_checkpoints_blocks_unlisted_app_type(mock_container: MagicMock) -> None: + """list_checkpoints skips documents with unlisted application types.""" + checkpoint = _make_checkpoint_with_state({"data": _AppState(label="x", count=1)}) + doc = _checkpoint_to_cosmos_document(checkpoint) + mock_container.query_items.return_value = _to_async_iter([doc]) + + storage = CosmosCheckpointStorage(container_client=mock_container) + results = await storage.list_checkpoints(workflow_name="test-workflow") + + # The document is skipped (logged as warning) because the type is blocked + assert len(results) == 0 + + +async def test_list_checkpoints_allows_listed_app_type(mock_container: MagicMock) -> None: + """list_checkpoints decodes documents with listed application types.""" + checkpoint = _make_checkpoint_with_state({"data": _AppState(label="ok", count=3)}) + doc = _checkpoint_to_cosmos_document(checkpoint) + mock_container.query_items.return_value = _to_async_iter([doc]) + + storage = CosmosCheckpointStorage( + container_client=mock_container, + allowed_checkpoint_types=[_APP_STATE_TYPE_KEY], + ) + results = await storage.list_checkpoints(workflow_name="test-workflow") + + assert len(results) == 1 + assert isinstance(results[0].state["data"], _AppState) + + +async def test_get_latest_blocks_unlisted_app_type(mock_container: MagicMock) -> None: + """get_latest raises when the checkpoint contains an unlisted application type.""" + checkpoint = _make_checkpoint_with_state({"data": _AppState(label="x", count=1)}) + doc = _checkpoint_to_cosmos_document(checkpoint) + mock_container.query_items.return_value = _to_async_iter([doc]) + + storage = CosmosCheckpointStorage(container_client=mock_container) + + with pytest.raises(WorkflowCheckpointException, match="deserialization blocked"): + await storage.get_latest(workflow_name="test-workflow") + + +async def test_get_latest_allows_listed_app_type(mock_container: MagicMock) -> None: + """get_latest decodes checkpoints with listed application types.""" + checkpoint = _make_checkpoint_with_state({"data": _AppState(label="latest", count=9)}) + doc = _checkpoint_to_cosmos_document(checkpoint) + mock_container.query_items.return_value = _to_async_iter([doc]) + + storage = CosmosCheckpointStorage( + container_client=mock_container, + allowed_checkpoint_types=[_APP_STATE_TYPE_KEY], + ) + result = await storage.get_latest(workflow_name="test-workflow") + + assert result is not None + assert isinstance(result.state["data"], _AppState) + assert result.state["data"].label == "latest" diff --git a/python/packages/core/agent_framework/_skills.py b/python/packages/core/agent_framework/_skills.py index 5c99dbaa60..d371291b21 100644 --- a/python/packages/core/agent_framework/_skills.py +++ b/python/packages/core/agent_framework/_skills.py @@ -486,8 +486,8 @@ YAML_KV_RE = re.compile( ) # Validates skill names: lowercase letters, numbers, hyphens only; -# must not start or end with a hyphen. -VALID_NAME_RE = re.compile(r"^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$") +# must not start or end with a hyphen, and must not contain consecutive hyphens. +VALID_NAME_RE = re.compile(r"^[a-z0-9]([a-z0-9]*-[a-z0-9])*[a-z0-9]*$") # Default system prompt template for advertising available skills to the model. # Use {skills} as the placeholder for the generated skills XML list. @@ -1156,7 +1156,8 @@ def _validate_skill_metadata( if len(name) > MAX_NAME_LENGTH or not VALID_NAME_RE.match(name): return ( f"Skill from '{source}' has an invalid name '{name}': Must be {MAX_NAME_LENGTH} characters or fewer, " - "using only lowercase letters, numbers, and hyphens, and must not start or end with a hyphen." + "using only lowercase letters, numbers, and hyphens, and must not start or end with a hyphen " + "or contain consecutive hyphens." ) if not description or not description.strip(): @@ -1241,6 +1242,17 @@ def _read_and_parse_skill_file( return None name, description = result + + dir_name = Path(skill_dir_path).name + if name != dir_name: + logger.error( + "SKILL.md at '%s' has frontmatter name '%s' that does not match the directory name '%s'; skipping.", + skill_file, + name, + dir_name, + ) + return None + return name, description, content diff --git a/python/packages/core/agent_framework/_types.py b/python/packages/core/agent_framework/_types.py index 87799d0848..584c1f0110 100644 --- a/python/packages/core/agent_framework/_types.py +++ b/python/packages/core/agent_framework/_types.py @@ -2816,6 +2816,7 @@ class ResponseStream(AsyncIterable[UpdateT], Generic[UpdateT, FinalT]): cleanup_hooks if cleanup_hooks is not None else [] ) self._cleanup_run: bool = False + self._stream_error: Exception | None = None self._inner_stream: ResponseStream[Any, Any] | None = None self._inner_stream_source: ResponseStream[Any, Any] | Awaitable[ResponseStream[Any, Any]] | None = None self._wrap_inner: bool = False @@ -2948,8 +2949,12 @@ class ResponseStream(AsyncIterable[UpdateT], Generic[UpdateT, FinalT]): await self._run_cleanup_hooks() await self.get_final_response() raise - except Exception: - await self._run_cleanup_hooks() + except Exception as exc: + self._stream_error = exc + try: + await self._run_cleanup_hooks() + finally: + self._stream_error = None raise if self._map_update is not None: update = self._map_update(update) # type: ignore[assignment] diff --git a/python/packages/core/agent_framework/_workflows/_agent.py b/python/packages/core/agent_framework/_workflows/_agent.py index 60c5ec3774..2fd3f35213 100644 --- a/python/packages/core/agent_framework/_workflows/_agent.py +++ b/python/packages/core/agent_framework/_workflows/_agent.py @@ -119,15 +119,11 @@ class WorkflowAgent(BaseAgent): if not any(is_type_compatible(list[Message], input_type) for input_type in start_executor.input_types): raise ValueError("Workflow's start executor cannot handle list[Message]") - resolved_context_providers = list(context_providers) if context_providers is not None else [] - if not resolved_context_providers: - resolved_context_providers.append(InMemoryHistoryProvider()) - super().__init__( id=id, name=name, description=description, - context_providers=resolved_context_providers, + context_providers=context_providers, **kwargs, ) self._workflow: Workflow = workflow @@ -261,6 +257,15 @@ class WorkflowAgent(BaseAgent): An AgentResponse representing the workflow execution results. """ input_messages = normalize_messages_input(messages) + + if ( + not any( + provider.load_messages for provider in self.context_providers if isinstance(provider, HistoryProvider) + ) + and session is not None + ): + self.context_providers.append(InMemoryHistoryProvider()) + provider_session = session if provider_session is None and self.context_providers: provider_session = AgentSession() @@ -332,6 +337,15 @@ class WorkflowAgent(BaseAgent): AgentResponseUpdate objects representing the workflow execution progress. """ input_messages = normalize_messages_input(messages) + + if ( + not any( + provider.load_messages for provider in self.context_providers if isinstance(provider, HistoryProvider) + ) + and session is not None + ): + self.context_providers.append(InMemoryHistoryProvider()) + provider_session = session if provider_session is None and self.context_providers: provider_session = AgentSession() diff --git a/python/packages/core/agent_framework/ag_ui/__init__.py b/python/packages/core/agent_framework/ag_ui/__init__.py index 8e1385a26c..91754e01b4 100644 --- a/python/packages/core/agent_framework/ag_ui/__init__.py +++ b/python/packages/core/agent_framework/ag_ui/__init__.py @@ -7,10 +7,13 @@ This module lazily re-exports objects from: Supported classes and functions: - AgentFrameworkAgent +- AgentFrameworkWorkflow - AGUIChatClient - AGUIEventConverter - AGUIHttpService - add_agent_framework_fastapi_endpoint +- state_update +- __version__ """ import importlib @@ -23,6 +26,10 @@ _IMPORTS = [ "AgentFrameworkWorkflow", "add_agent_framework_fastapi_endpoint", "AGUIChatClient", + "AGUIEventConverter", + "AGUIHttpService", + "state_update", + "__version__", ] diff --git a/python/packages/core/agent_framework/ag_ui/__init__.pyi b/python/packages/core/agent_framework/ag_ui/__init__.pyi index 17a5b3a4db..1f6636ae81 100644 --- a/python/packages/core/agent_framework/ag_ui/__init__.pyi +++ b/python/packages/core/agent_framework/ag_ui/__init__.pyi @@ -8,6 +8,7 @@ from agent_framework_ag_ui import ( AGUIHttpService, __version__, add_agent_framework_fastapi_endpoint, + state_update, ) __all__ = [ @@ -18,4 +19,5 @@ __all__ = [ "AgentFrameworkWorkflow", "__version__", "add_agent_framework_fastapi_endpoint", + "state_update", ] diff --git a/python/packages/core/agent_framework/observability.py b/python/packages/core/agent_framework/observability.py index 8d2eb05136..6998e5994f 100644 --- a/python/packages/core/agent_framework/observability.py +++ b/python/packages/core/agent_framework/observability.py @@ -1323,6 +1323,12 @@ class ChatTelemetryLayer(Generic[OptionsCoT]): from ._types import ChatResponse try: + if result_stream._stream_error is not None: # pyright: ignore[reportPrivateUsage] + # Stream errored; skip get_final_response() to avoid firing + # result hooks such as after_run context providers on error + # paths. Capture the error on the span before returning. + capture_exception(span=span, exception=result_stream._stream_error, timestamp=time_ns()) # pyright: ignore[reportPrivateUsage] + return response: ChatResponse[Any] = await result_stream.get_final_response() duration = duration_state.get("duration") response_attributes = _get_response_attributes(attributes, response) @@ -1579,6 +1585,12 @@ class AgentTelemetryLayer: from ._types import AgentResponse try: + if result_stream._stream_error is not None: # pyright: ignore[reportPrivateUsage] + # Stream errored; skip get_final_response() to avoid firing + # result hooks such as after_run context providers on error + # paths. Capture the error on the span before returning. + capture_exception(span=span, exception=result_stream._stream_error, timestamp=time_ns()) # pyright: ignore[reportPrivateUsage] + return response: AgentResponse[Any] = await result_stream.get_final_response() duration = duration_state.get("duration") response_attributes = _get_response_attributes( diff --git a/python/packages/core/pyproject.toml b/python/packages/core/pyproject.toml index e26e1be6c9..e28245d6e7 100644 --- a/python/packages/core/pyproject.toml +++ b/python/packages/core/pyproject.toml @@ -34,14 +34,13 @@ all = [ "mcp>=1.24.0,<2", "agent-framework-a2a", "agent-framework-ag-ui", + "agent-framework-anthropic", "agent-framework-azure-ai-search", "agent-framework-azure-cosmos", - "agent-framework-anthropic", - "agent-framework-openai", - "agent-framework-claude", "agent-framework-azurefunctions", "agent-framework-bedrock", "agent-framework-chatkit", + "agent-framework-claude", "agent-framework-copilotstudio", "agent-framework-declarative", "agent-framework-devui", @@ -52,6 +51,7 @@ all = [ "agent-framework-lab", "agent-framework-mem0", "agent-framework-ollama", + "agent-framework-openai", "agent-framework-orchestrations", "agent-framework-purview", "agent-framework-redis", diff --git a/python/packages/core/tests/core/test_skills.py b/python/packages/core/tests/core/test_skills.py index 16cae57dcd..225b12d9a1 100644 --- a/python/packages/core/tests/core/test_skills.py +++ b/python/packages/core/tests/core/test_skills.py @@ -296,6 +296,15 @@ class TestDiscoverAndLoadSkills: skills = _discover_file_skills([str(tmp_path)]) assert len(skills) == 0 + def test_skips_skill_with_name_directory_mismatch(self, tmp_path: Path) -> None: + skill_dir = tmp_path / "wrong-dir-name" + skill_dir.mkdir() + (skill_dir / "SKILL.md").write_text( + "---\nname: actual-skill-name\ndescription: A skill.\n---\nBody.", encoding="utf-8" + ) + skills = _discover_file_skills([str(tmp_path)]) + assert len(skills) == 0 + def test_deduplicates_skill_names(self, tmp_path: Path) -> None: dir1 = tmp_path / "dir1" dir2 = tmp_path / "dir2" @@ -904,6 +913,11 @@ class TestSkill: provider = SkillsProvider(skills=[invalid_skill]) assert len(provider._skills) == 0 + def test_name_with_consecutive_hyphens_skipped(self) -> None: + invalid_skill = Skill(name="consecutive--hyphens", description="A skill.", content="Body") + provider = SkillsProvider(skills=[invalid_skill]) + assert len(provider._skills) == 0 + def test_name_too_long_skipped(self) -> None: invalid_skill = Skill(name="a" * 65, description="A skill.", content="Body") provider = SkillsProvider(skills=[invalid_skill]) @@ -1421,6 +1435,11 @@ class TestValidateSkillMetadata: assert result is not None assert "invalid name" in result + def test_name_with_consecutive_hyphens(self) -> None: + result = _validate_skill_metadata("consecutive--hyphens", "desc", "source") + assert result is not None + assert "invalid name" in result + def test_single_char_name(self) -> None: assert _validate_skill_metadata("a", "desc", "source") is None @@ -1526,6 +1545,15 @@ class TestReadAndParseSkillFile: result = _read_and_parse_skill_file(str(skill_dir)) assert result is None + def test_name_directory_mismatch_returns_none(self, tmp_path: Path) -> None: + skill_dir = tmp_path / "wrong-dir-name" + skill_dir.mkdir() + (skill_dir / "SKILL.md").write_text( + "---\nname: actual-skill-name\ndescription: A skill.\n---\nBody.", encoding="utf-8" + ) + result = _read_and_parse_skill_file(str(skill_dir)) + assert result is None + # --------------------------------------------------------------------------- # Tests: _create_resource_element diff --git a/python/packages/core/tests/workflow/test_workflow_agent.py b/python/packages/core/tests/workflow/test_workflow_agent.py index dd2100c1ae..9c7a655d23 100644 --- a/python/packages/core/tests/workflow/test_workflow_agent.py +++ b/python/packages/core/tests/workflow/test_workflow_agent.py @@ -14,6 +14,7 @@ from agent_framework import ( AgentSession, Content, Executor, + HistoryProvider, InMemoryHistoryProvider, Message, ResponseStream, @@ -678,6 +679,110 @@ class TestWorkflowAgent: assert agent.context_providers == [explicit_provider] + async def test_no_history_provider_injected_when_session_is_none(self) -> None: + """Test that InMemoryHistoryProvider is NOT injected when session is None.""" + capturing_executor = ConversationHistoryCapturingExecutor(id="no_session_test") + workflow = WorkflowBuilder(start_executor=capturing_executor).build() + agent = WorkflowAgent(workflow=workflow, name="No Session Agent") + + await agent.run("hello") + + assert not any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) + + async def test_no_history_provider_injected_when_session_is_none_streaming(self) -> None: + """Test that InMemoryHistoryProvider is NOT injected when session is None (streaming).""" + capturing_executor = ConversationHistoryCapturingExecutor(id="no_session_stream_test") + workflow = WorkflowBuilder(start_executor=capturing_executor).build() + agent = WorkflowAgent(workflow=workflow, name="No Session Stream Agent") + + async for _ in agent.run("hello", stream=True): + pass + + assert not any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) + + async def test_no_injection_when_history_provider_with_load_messages_exists(self) -> None: + """Test that no InMemoryHistoryProvider is injected when an existing HistoryProvider has load_messages=True.""" + capturing_executor = ConversationHistoryCapturingExecutor(id="existing_provider_test") + workflow = WorkflowBuilder(start_executor=capturing_executor).build() + existing_provider = InMemoryHistoryProvider("custom", load_messages=True) + agent = WorkflowAgent( + workflow=workflow, + name="Existing Provider Agent", + context_providers=[existing_provider], + ) + session = AgentSession() + + await agent.run("hello", session=session) + + # Should still have only the original provider + history_providers = [p for p in agent.context_providers if isinstance(p, HistoryProvider)] + assert len(history_providers) == 1 + assert history_providers[0] is existing_provider + + async def test_injection_when_history_provider_with_load_messages_false(self) -> None: + """Test that InMemoryHistoryProvider IS injected when existing HistoryProvider has load_messages=False.""" + capturing_executor = ConversationHistoryCapturingExecutor(id="no_load_provider_test") + workflow = WorkflowBuilder(start_executor=capturing_executor).build() + audit_provider = InMemoryHistoryProvider("audit", load_messages=False) + agent = WorkflowAgent( + workflow=workflow, + name="Audit Provider Agent", + context_providers=[audit_provider], + ) + session = AgentSession() + + await agent.run("hello", session=session) + + # Should have injected an additional InMemoryHistoryProvider with load_messages=True + history_providers = [p for p in agent.context_providers if isinstance(p, HistoryProvider)] + assert len(history_providers) == 2 + loading_providers = [p for p in history_providers if p.load_messages] + assert len(loading_providers) == 1 + assert isinstance(loading_providers[0], InMemoryHistoryProvider) + + async def test_no_duplicate_injection_on_multiple_runs(self) -> None: + """Test that calling run() multiple times does not keep adding InMemoryHistoryProvider.""" + capturing_executor = ConversationHistoryCapturingExecutor(id="no_dup_test") + workflow = WorkflowBuilder(start_executor=capturing_executor).build() + agent = WorkflowAgent(workflow=workflow, name="No Dup Agent") + session = AgentSession() + + await agent.run("first", session=session) + await agent.run("second", session=session) + await agent.run("third", session=session) + + history_providers = [p for p in agent.context_providers if isinstance(p, InMemoryHistoryProvider)] + assert len(history_providers) == 1 + + async def test_no_duplicate_injection_on_multiple_runs_streaming(self) -> None: + """Test that calling run(stream=True) multiple times does not keep adding InMemoryHistoryProvider.""" + capturing_executor = ConversationHistoryCapturingExecutor(id="no_dup_stream_test") + workflow = WorkflowBuilder(start_executor=capturing_executor).build() + agent = WorkflowAgent(workflow=workflow, name="No Dup Stream Agent") + session = AgentSession() + + async for _ in agent.run("first", stream=True, session=session): + pass + async for _ in agent.run("second", stream=True, session=session): + pass + async for _ in agent.run("third", stream=True, session=session): + pass + + history_providers = [p for p in agent.context_providers if isinstance(p, InMemoryHistoryProvider)] + assert len(history_providers) == 1 + + async def test_injection_with_session_in_streaming_mode(self) -> None: + """Test that InMemoryHistoryProvider is injected when session is provided in streaming mode.""" + capturing_executor = ConversationHistoryCapturingExecutor(id="stream_inject_test") + workflow = WorkflowBuilder(start_executor=capturing_executor).build() + agent = WorkflowAgent(workflow=workflow, name="Stream Inject Agent") + session = AgentSession() + + async for _ in agent.run("hello", stream=True, session=session): + pass + + assert any(isinstance(p, InMemoryHistoryProvider) for p in agent.context_providers) + async def test_checkpoint_storage_passed_to_workflow(self) -> None: """Test that checkpoint_storage parameter is passed through to the workflow.""" from agent_framework import InMemoryCheckpointStorage diff --git a/python/packages/devui/agent_framework_devui/ui/assets/index.js b/python/packages/devui/agent_framework_devui/ui/assets/index.js index fe3c02b6ee..ae7f718d36 100644 --- a/python/packages/devui/agent_framework_devui/ui/assets/index.js +++ b/python/packages/devui/agent_framework_devui/ui/assets/index.js @@ -1,4 +1,4 @@ -function KE(e,n){for(var r=0;ra[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))a(l);new MutationObserver(l=>{for(const c of l)if(c.type==="childList")for(const d of c.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&a(d)}).observe(document,{childList:!0,subtree:!0});function r(l){const c={};return l.integrity&&(c.integrity=l.integrity),l.referrerPolicy&&(c.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?c.credentials="include":l.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function a(l){if(l.ep)return;l.ep=!0;const c=r(l);fetch(l.href,c)}})();function Cp(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var eh={exports:{}},qi={};/** +function XE(e,n){for(var r=0;ra[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))a(l);new MutationObserver(l=>{for(const c of l)if(c.type==="childList")for(const d of c.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&a(d)}).observe(document,{childList:!0,subtree:!0});function r(l){const c={};return l.integrity&&(c.integrity=l.integrity),l.referrerPolicy&&(c.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?c.credentials="include":l.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function a(l){if(l.ep)return;l.ep=!0;const c=r(l);fetch(l.href,c)}})();function _p(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var eh={exports:{}},qi={};/** * @license React * react-jsx-runtime.production.js * @@ -6,7 +6,7 @@ function KE(e,n){for(var r=0;r>>1,C=R[P];if(0>>1;P<$;){var Y=2*(P+1)-1,V=R[Y],J=Y+1,ce=R[J];if(0>l(V,I))Jl(ce,V)?(R[P]=ce,R[J]=I,P=J):(R[P]=V,R[Y]=I,P=Y);else if(Jl(ce,I))R[P]=ce,R[J]=I,P=J;else break e}}return L}function l(R,L){var I=R.sortIndex-L.sortIndex;return I!==0?I:R.id-L.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var c=performance;e.unstable_now=function(){return c.now()}}else{var d=Date,f=d.now();e.unstable_now=function(){return d.now()-f}}var m=[],h=[],g=1,x=null,y=3,b=!1,j=!1,N=!1,S=!1,_=typeof setTimeout=="function"?setTimeout:null,A=typeof clearTimeout=="function"?clearTimeout:null,E=typeof setImmediate<"u"?setImmediate:null;function M(R){for(var L=r(h);L!==null;){if(L.callback===null)a(h);else if(L.startTime<=R)a(h),L.sortIndex=L.expirationTime,n(m,L);else break;L=r(h)}}function T(R){if(N=!1,M(R),!j)if(r(m)!==null)j=!0,D||(D=!0,G());else{var L=r(h);L!==null&&U(T,L.startTime-R)}}var D=!1,z=-1,H=5,q=-1;function X(){return S?!0:!(e.unstable_now()-qR&&X());){var P=x.callback;if(typeof P=="function"){x.callback=null,y=x.priorityLevel;var C=P(x.expirationTime<=R);if(R=e.unstable_now(),typeof C=="function"){x.callback=C,M(R),L=!0;break t}x===r(m)&&a(m),M(R)}else a(m);x=r(m)}if(x!==null)L=!0;else{var $=r(h);$!==null&&U(T,$.startTime-R),L=!1}}break e}finally{x=null,y=I,b=!1}L=void 0}}finally{L?G():D=!1}}}var G;if(typeof E=="function")G=function(){E(W)};else if(typeof MessageChannel<"u"){var ne=new MessageChannel,B=ne.port2;ne.port1.onmessage=W,G=function(){B.postMessage(null)}}else G=function(){_(W,0)};function U(R,L){z=_(function(){R(e.unstable_now())},L)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(R){R.callback=null},e.unstable_forceFrameRate=function(R){0>R||125P?(R.sortIndex=I,n(h,R),r(m)===null&&R===r(h)&&(N?(A(z),z=-1):N=!0,U(T,I-P))):(R.sortIndex=C,n(m,R),j||b||(j=!0,D||(D=!0,G()))),R},e.unstable_shouldYield=X,e.unstable_wrapCallback=function(R){var L=y;return function(){var I=y;y=L;try{return R.apply(this,arguments)}finally{y=I}}}})(rh)),rh}var vv;function nC(){return vv||(vv=1,sh.exports=tC()),sh.exports}var oh={exports:{}},Jt={};/** + */var gv;function QE(){return gv||(gv=1,(function(e){function n(R,L){var I=R.length;R.push(L);e:for(;0>>1,C=R[$];if(0>>1;$l(V,I))eel(ie,V)?(R[$]=ie,R[ee]=I,$=ee):(R[$]=V,R[G]=I,$=G);else if(eel(ie,I))R[$]=ie,R[ee]=I,$=ee;else break e}}return L}function l(R,L){var I=R.sortIndex-L.sortIndex;return I!==0?I:R.id-L.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var c=performance;e.unstable_now=function(){return c.now()}}else{var d=Date,f=d.now();e.unstable_now=function(){return d.now()-f}}var m=[],h=[],g=1,y=null,x=3,b=!1,S=!1,N=!1,j=!1,_=typeof setTimeout=="function"?setTimeout:null,T=typeof clearTimeout=="function"?clearTimeout:null,E=typeof setImmediate<"u"?setImmediate:null;function A(R){for(var L=r(h);L!==null;){if(L.callback===null)a(h);else if(L.startTime<=R)a(h),L.sortIndex=L.expirationTime,n(m,L);else break;L=r(h)}}function M(R){if(N=!1,A(R),!S)if(r(m)!==null)S=!0,D||(D=!0,X());else{var L=r(h);L!==null&&B(M,L.startTime-R)}}var D=!1,z=-1,H=5,q=-1;function Y(){return j?!0:!(e.unstable_now()-qR&&Y());){var $=y.callback;if(typeof $=="function"){y.callback=null,x=y.priorityLevel;var C=$(y.expirationTime<=R);if(R=e.unstable_now(),typeof C=="function"){y.callback=C,A(R),L=!0;break t}y===r(m)&&a(m),A(R)}else a(m);y=r(m)}if(y!==null)L=!0;else{var P=r(h);P!==null&&B(M,P.startTime-R),L=!1}}break e}finally{y=null,x=I,b=!1}L=void 0}}finally{L?X():D=!1}}}var X;if(typeof E=="function")X=function(){E(K)};else if(typeof MessageChannel<"u"){var ne=new MessageChannel,U=ne.port2;ne.port1.onmessage=K,X=function(){U.postMessage(null)}}else X=function(){_(K,0)};function B(R,L){z=_(function(){R(e.unstable_now())},L)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(R){R.callback=null},e.unstable_forceFrameRate=function(R){0>R||125$?(R.sortIndex=I,n(h,R),r(m)===null&&R===r(h)&&(N?(T(z),z=-1):N=!0,B(M,I-$))):(R.sortIndex=C,n(m,R),S||b||(S=!0,D||(D=!0,X()))),R},e.unstable_shouldYield=Y,e.unstable_wrapCallback=function(R){var L=x;return function(){var I=x;x=L;try{return R.apply(this,arguments)}finally{x=I}}}})(rh)),rh}var xv;function JE(){return xv||(xv=1,sh.exports=QE()),sh.exports}var oh={exports:{}},tn={};/** * @license React * react-dom.production.js * @@ -30,7 +30,7 @@ function KE(e,n){for(var r=0;r"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(n){console.error(n)}}return e(),oh.exports=sC(),oh.exports}/** + */var yv;function eC(){if(yv)return tn;yv=1;var e=wl();function n(m){var h="https://react.dev/errors/"+m;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(n){console.error(n)}}return e(),oh.exports=eC(),oh.exports}/** * @license React * react-dom-client.production.js * @@ -38,20 +38,20 @@ function KE(e,n){for(var r=0;rC||(t.current=P[C],P[C]=null,C--)}function V(t,s){C++,P[C]=t.current,t.current=s}var J=$(null),ce=$(null),fe=$(null),ee=$(null);function ie(t,s){switch(V(fe,s),V(ce,t),V(J,null),s.nodeType){case 9:case 11:t=(t=s.documentElement)&&(t=t.namespaceURI)?By(t):0;break;default:if(t=s.tagName,s=s.namespaceURI)s=By(s),t=Vy(s,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}Y(J),V(J,t)}function ge(){Y(J),Y(ce),Y(fe)}function Ee(t){t.memoizedState!==null&&V(ee,t);var s=J.current,i=Vy(s,t.type);s!==i&&(V(ce,t),V(J,i))}function Ne(t){ce.current===t&&(Y(J),Y(ce)),ee.current===t&&(Y(ee),Pi._currentValue=I)}var ve=Object.prototype.hasOwnProperty,ze=e.unstable_scheduleCallback,re=e.unstable_cancelCallback,Q=e.unstable_shouldYield,me=e.unstable_requestPaint,be=e.unstable_now,Ce=e.unstable_getCurrentPriorityLevel,we=e.unstable_ImmediatePriority,Me=e.unstable_UserBlockingPriority,je=e.unstable_NormalPriority,Se=e.unstable_LowPriority,Ke=e.unstable_IdlePriority,tt=e.log,Be=e.unstable_setDisableYieldValue,_e=null,xe=null;function $e(t){if(typeof tt=="function"&&Be(t),xe&&typeof xe.setStrictMode=="function")try{xe.setStrictMode(_e,t)}catch{}}var Ge=Math.clz32?Math.clz32:_o,qt=Math.log,rn=Math.LN2;function _o(t){return t>>>=0,t===0?32:31-(qt(t)/rn|0)|0}var Jn=256,vs=4194304;function pe(t){var s=t&42;if(s!==0)return s;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Ae(t,s,i){var u=t.pendingLanes;if(u===0)return 0;var p=0,v=t.suspendedLanes,k=t.pingedLanes;t=t.warmLanes;var O=u&134217727;return O!==0?(u=O&~v,u!==0?p=pe(u):(k&=O,k!==0?p=pe(k):i||(i=O&~t,i!==0&&(p=pe(i))))):(O=u&~v,O!==0?p=pe(O):k!==0?p=pe(k):i||(i=u&~t,i!==0&&(p=pe(i)))),p===0?0:s!==0&&s!==p&&(s&v)===0&&(v=p&-p,i=s&-s,v>=i||v===32&&(i&4194048)!==0)?s:p}function Ie(t,s){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&s)===0}function Ot(t,s){switch(t){case 1:case 2:case 4:case 8:case 64:return s+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return s+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ft(){var t=Jn;return Jn<<=1,(Jn&4194048)===0&&(Jn=256),t}function Pe(){var t=vs;return vs<<=1,(vs&62914560)===0&&(vs=4194304),t}function ye(t){for(var s=[],i=0;31>i;i++)s.push(t);return s}function dt(t,s){t.pendingLanes|=s,s!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function _t(t,s,i,u,p,v){var k=t.pendingLanes;t.pendingLanes=i,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=i,t.entangledLanes&=i,t.errorRecoveryDisabledLanes&=i,t.shellSuspendCounter=0;var O=t.entanglements,F=t.expirationTimes,se=t.hiddenUpdates;for(i=k&~i;0C||(t.current=$[C],$[C]=null,C--)}function V(t,s){C++,$[C]=t.current,t.current=s}var ee=P(null),ie=P(null),ue=P(null),Q=P(null);function ae(t,s){switch(V(ue,s),V(ie,t),V(ee,null),s.nodeType){case 9:case 11:t=(t=s.documentElement)&&(t=t.namespaceURI)?Hy(t):0;break;default:if(t=s.tagName,s=s.namespaceURI)s=Hy(s),t=Uy(s,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}G(ee),V(ee,t)}function ge(){G(ee),G(ie),G(ue)}function Se(t){t.memoizedState!==null&&V(Q,t);var s=ee.current,i=Uy(s,t.type);s!==i&&(V(ie,t),V(ee,i))}function we(t){ie.current===t&&(G(ee),G(ie)),Q.current===t&&(G(Q),Pi._currentValue=I)}var ve=Object.prototype.hasOwnProperty,Re=e.unstable_scheduleCallback,Le=e.unstable_cancelCallback,nt=e.unstable_shouldYield,le=e.unstable_requestPaint,Ee=e.unstable_now,W=e.unstable_getCurrentPriorityLevel,xe=e.unstable_ImmediatePriority,be=e.unstable_UserBlockingPriority,pe=e.unstable_NormalPriority,ke=e.unstable_LowPriority,Ce=e.unstable_IdlePriority,De=e.log,Ze=e.unstable_setDisableYieldValue,Pe=null,je=null;function Ne(t){if(typeof De=="function"&&Ze(t),je&&typeof je.setStrictMode=="function")try{je.setStrictMode(Pe,t)}catch{}}var _e=Math.clz32?Math.clz32:sn,Ge=Math.log,dt=Math.LN2;function sn(t){return t>>>=0,t===0?32:31-(Ge(t)/dt|0)|0}var Bt=256,bs=4194304;function he(t){var s=t&42;if(s!==0)return s;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Me(t,s,i){var u=t.pendingLanes;if(u===0)return 0;var p=0,v=t.suspendedLanes,k=t.pingedLanes;t=t.warmLanes;var O=u&134217727;return O!==0?(u=O&~v,u!==0?p=he(u):(k&=O,k!==0?p=he(k):i||(i=O&~t,i!==0&&(p=he(i))))):(O=u&~v,O!==0?p=he(O):k!==0?p=he(k):i||(i=u&~t,i!==0&&(p=he(i)))),p===0?0:s!==0&&s!==p&&(s&v)===0&&(v=p&-p,i=s&-s,v>=i||v===32&&(i&4194048)!==0)?s:p}function $e(t,s){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&s)===0}function It(t,s){switch(t){case 1:case 2:case 4:case 8:case 64:return s+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return s+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Gt(){var t=Bt;return Bt<<=1,(Bt&4194048)===0&&(Bt=256),t}function Ue(){var t=bs;return bs<<=1,(bs&62914560)===0&&(bs=4194304),t}function ye(t){for(var s=[],i=0;31>i;i++)s.push(t);return s}function mt(t,s){t.pendingLanes|=s,s!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Ct(t,s,i,u,p,v){var k=t.pendingLanes;t.pendingLanes=i,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=i,t.entangledLanes&=i,t.errorRecoveryDisabledLanes&=i,t.shellSuspendCounter=0;var O=t.entanglements,F=t.expirationTimes,se=t.hiddenUpdates;for(i=k&~i;0)":-1p||F[u]!==se[p]){var ue=` -`+F[u].replace(" at new "," at ");return t.displayName&&ue.includes("")&&(ue=ue.replace("",t.displayName)),ue}while(1<=u&&0<=p);break}}}finally{Xa=!1,Error.prepareStackTrace=i}return(i=t?t.displayName||t.name:"")?js(i):""}function Zd(t){switch(t.tag){case 26:case 27:case 5:return js(t.type);case 16:return js("Lazy");case 13:return js("Suspense");case 19:return js("SuspenseList");case 0:case 15:return Za(t.type,!1);case 11:return Za(t.type.render,!1);case 1:return Za(t.type,!0);case 31:return js("Activity");default:return""}}function Vl(t){try{var s="";do s+=Zd(t),t=t.return;while(t);return s}catch(i){return` +`);for(p=u=0;up||F[u]!==se[p]){var de=` +`+F[u].replace(" at new "," at ");return t.displayName&&de.includes("")&&(de=de.replace("",t.displayName)),de}while(1<=u&&0<=p);break}}}finally{Xa=!1,Error.prepareStackTrace=i}return(i=t?t.displayName||t.name:"")?Ss(i):""}function Zd(t){switch(t.tag){case 26:case 27:case 5:return Ss(t.type);case 16:return Ss("Lazy");case 13:return Ss("Suspense");case 19:return Ss("SuspenseList");case 0:case 15:return Za(t.type,!1);case 11:return Za(t.type.render,!1);case 1:return Za(t.type,!0);case 31:return Ss("Activity");default:return""}}function Vl(t){try{var s="";do s+=Zd(t),t=t.return;while(t);return s}catch(i){return` Error generating stack: `+i.message+` -`+i.stack}}function on(t){switch(typeof t){case"bigint":case"boolean":case"number":case"string":case"undefined":return t;case"object":return t;default:return""}}function ql(t){var s=t.type;return(t=t.nodeName)&&t.toLowerCase()==="input"&&(s==="checkbox"||s==="radio")}function Wd(t){var s=ql(t)?"checked":"value",i=Object.getOwnPropertyDescriptor(t.constructor.prototype,s),u=""+t[s];if(!t.hasOwnProperty(s)&&typeof i<"u"&&typeof i.get=="function"&&typeof i.set=="function"){var p=i.get,v=i.set;return Object.defineProperty(t,s,{configurable:!0,get:function(){return p.call(this)},set:function(k){u=""+k,v.call(this,k)}}),Object.defineProperty(t,s,{enumerable:i.enumerable}),{getValue:function(){return u},setValue:function(k){u=""+k},stopTracking:function(){t._valueTracker=null,delete t[s]}}}}function ko(t){t._valueTracker||(t._valueTracker=Wd(t))}function Wa(t){if(!t)return!1;var s=t._valueTracker;if(!s)return!0;var i=s.getValue(),u="";return t&&(u=ql(t)?t.checked?"true":"false":t.value),t=u,t!==i?(s.setValue(t),!0):!1}function To(t){if(t=t||(typeof document<"u"?document:void 0),typeof t>"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var Kd=/[\n"\\]/g;function an(t){return t.replace(Kd,function(s){return"\\"+s.charCodeAt(0).toString(16)+" "})}function Vr(t,s,i,u,p,v,k,O){t.name="",k!=null&&typeof k!="function"&&typeof k!="symbol"&&typeof k!="boolean"?t.type=k:t.removeAttribute("type"),s!=null?k==="number"?(s===0&&t.value===""||t.value!=s)&&(t.value=""+on(s)):t.value!==""+on(s)&&(t.value=""+on(s)):k!=="submit"&&k!=="reset"||t.removeAttribute("value"),s!=null?Ka(t,k,on(s)):i!=null?Ka(t,k,on(i)):u!=null&&t.removeAttribute("value"),p==null&&v!=null&&(t.defaultChecked=!!v),p!=null&&(t.checked=p&&typeof p!="function"&&typeof p!="symbol"),O!=null&&typeof O!="function"&&typeof O!="symbol"&&typeof O!="boolean"?t.name=""+on(O):t.removeAttribute("name")}function Fl(t,s,i,u,p,v,k,O){if(v!=null&&typeof v!="function"&&typeof v!="symbol"&&typeof v!="boolean"&&(t.type=v),s!=null||i!=null){if(!(v!=="submit"&&v!=="reset"||s!=null))return;i=i!=null?""+on(i):"",s=s!=null?""+on(s):i,O||s===t.value||(t.value=s),t.defaultValue=s}u=u??p,u=typeof u!="function"&&typeof u!="symbol"&&!!u,t.checked=O?t.checked:!!u,t.defaultChecked=!!u,k!=null&&typeof k!="function"&&typeof k!="symbol"&&typeof k!="boolean"&&(t.name=k)}function Ka(t,s,i){s==="number"&&To(t.ownerDocument)===t||t.defaultValue===""+i||(t.defaultValue=""+i)}function Ss(t,s,i,u){if(t=t.options,s){s={};for(var p=0;p"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),nf=!1;if(_s)try{var Ja={};Object.defineProperty(Ja,"passive",{get:function(){nf=!0}}),window.addEventListener("test",Ja,Ja),window.removeEventListener("test",Ja,Ja)}catch{nf=!1}var tr=null,sf=null,Gl=null;function Yg(){if(Gl)return Gl;var t,s=sf,i=s.length,u,p="value"in tr?tr.value:tr.textContent,v=p.length;for(t=0;t=ni),Qg=" ",Jg=!1;function ex(t,s){switch(t){case"keyup":return w_.indexOf(s.keyCode)!==-1;case"keydown":return s.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function tx(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Do=!1;function j_(t,s){switch(t){case"compositionend":return tx(s);case"keypress":return s.which!==32?null:(Jg=!0,Qg);case"textInput":return t=s.data,t===Qg&&Jg?null:t;default:return null}}function S_(t,s){if(Do)return t==="compositionend"||!cf&&ex(t,s)?(t=Yg(),Gl=sf=tr=null,Do=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(s.ctrlKey||s.altKey||s.metaKey)||s.ctrlKey&&s.altKey){if(s.char&&1=s)return{node:i,offset:s-t};t=u}e:{for(;i;){if(i.nextSibling){i=i.nextSibling;break e}i=i.parentNode}i=void 0}i=cx(i)}}function dx(t,s){return t&&s?t===s?!0:t&&t.nodeType===3?!1:s&&s.nodeType===3?dx(t,s.parentNode):"contains"in t?t.contains(s):t.compareDocumentPosition?!!(t.compareDocumentPosition(s)&16):!1:!1}function fx(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var s=To(t.document);s instanceof t.HTMLIFrameElement;){try{var i=typeof s.contentWindow.location.href=="string"}catch{i=!1}if(i)t=s.contentWindow;else break;s=To(t.document)}return s}function ff(t){var s=t&&t.nodeName&&t.nodeName.toLowerCase();return s&&(s==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||s==="textarea"||t.contentEditable==="true")}var R_=_s&&"documentMode"in document&&11>=document.documentMode,Oo=null,mf=null,ai=null,hf=!1;function mx(t,s,i){var u=i.window===i?i.document:i.nodeType===9?i:i.ownerDocument;hf||Oo==null||Oo!==To(u)||(u=Oo,"selectionStart"in u&&ff(u)?u={start:u.selectionStart,end:u.selectionEnd}:(u=(u.ownerDocument&&u.ownerDocument.defaultView||window).getSelection(),u={anchorNode:u.anchorNode,anchorOffset:u.anchorOffset,focusNode:u.focusNode,focusOffset:u.focusOffset}),ai&&oi(ai,u)||(ai=u,u=Lc(mf,"onSelect"),0>=k,p-=k,Cs=1<<32-Ge(s)+p|i<v?v:8;var k=R.T,O={};R.T=O,Jf(t,!1,s,i);try{var F=p(),se=R.S;if(se!==null&&se(O,F),F!==null&&typeof F=="object"&&typeof F.then=="function"){var ue=U_(F,u);wi(t,s,ue,vn(t))}else wi(t,s,u,vn(t))}catch(he){wi(t,s,{then:function(){},status:"rejected",reason:he},vn())}finally{L.p=v,R.T=k}}function Y_(){}function Kf(t,s,i,u){if(t.tag!==5)throw Error(a(476));var p=h0(t).queue;m0(t,p,s,I,i===null?Y_:function(){return p0(t),i(u)})}function h0(t){var s=t.memoizedState;if(s!==null)return s;s={memoizedState:I,baseState:I,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ms,lastRenderedState:I},next:null};var i={};return s.next={memoizedState:i,baseState:i,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ms,lastRenderedState:i},next:null},t.memoizedState=s,t=t.alternate,t!==null&&(t.memoizedState=s),s}function p0(t){var s=h0(t).next.queue;wi(t,s,{},vn())}function Qf(){return Qt(Pi)}function g0(){return Mt().memoizedState}function x0(){return Mt().memoizedState}function G_(t){for(var s=t.return;s!==null;){switch(s.tag){case 24:case 3:var i=vn();t=rr(i);var u=or(s,t,i);u!==null&&(bn(u,s,i),pi(u,s,i)),s={cache:kf()},t.payload=s;return}s=s.return}}function X_(t,s,i){var u=vn();i={lane:u,revertLane:0,action:i,hasEagerState:!1,eagerState:null,next:null},xc(t)?v0(s,i):(i=yf(t,s,i,u),i!==null&&(bn(i,t,u),b0(i,s,u)))}function y0(t,s,i){var u=vn();wi(t,s,i,u)}function wi(t,s,i,u){var p={lane:u,revertLane:0,action:i,hasEagerState:!1,eagerState:null,next:null};if(xc(t))v0(s,p);else{var v=t.alternate;if(t.lanes===0&&(v===null||v.lanes===0)&&(v=s.lastRenderedReducer,v!==null))try{var k=s.lastRenderedState,O=v(k,i);if(p.hasEagerState=!0,p.eagerState=O,hn(O,k))return ec(t,s,p,0),yt===null&&Jl(),!1}catch{}finally{}if(i=yf(t,s,p,u),i!==null)return bn(i,t,u),b0(i,s,u),!0}return!1}function Jf(t,s,i,u){if(u={lane:2,revertLane:Mm(),action:u,hasEagerState:!1,eagerState:null,next:null},xc(t)){if(s)throw Error(a(479))}else s=yf(t,i,u,2),s!==null&&bn(s,t,2)}function xc(t){var s=t.alternate;return t===Qe||s!==null&&s===Qe}function v0(t,s){qo=dc=!0;var i=t.pending;i===null?s.next=s:(s.next=i.next,i.next=s),t.pending=s}function b0(t,s,i){if((i&4194048)!==0){var u=s.lanes;u&=t.pendingLanes,i|=u,s.lanes=i,kn(t,i)}}var yc={readContext:Qt,use:mc,useCallback:Et,useContext:Et,useEffect:Et,useImperativeHandle:Et,useLayoutEffect:Et,useInsertionEffect:Et,useMemo:Et,useReducer:Et,useRef:Et,useState:Et,useDebugValue:Et,useDeferredValue:Et,useTransition:Et,useSyncExternalStore:Et,useId:Et,useHostTransitionStatus:Et,useFormState:Et,useActionState:Et,useOptimistic:Et,useMemoCache:Et,useCacheRefresh:Et},w0={readContext:Qt,use:mc,useCallback:function(t,s){return cn().memoizedState=[t,s===void 0?null:s],t},useContext:Qt,useEffect:r0,useImperativeHandle:function(t,s,i){i=i!=null?i.concat([t]):null,gc(4194308,4,l0.bind(null,s,t),i)},useLayoutEffect:function(t,s){return gc(4194308,4,t,s)},useInsertionEffect:function(t,s){gc(4,2,t,s)},useMemo:function(t,s){var i=cn();s=s===void 0?null:s;var u=t();if(to){$e(!0);try{t()}finally{$e(!1)}}return i.memoizedState=[u,s],u},useReducer:function(t,s,i){var u=cn();if(i!==void 0){var p=i(s);if(to){$e(!0);try{i(s)}finally{$e(!1)}}}else p=s;return u.memoizedState=u.baseState=p,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:p},u.queue=t,t=t.dispatch=X_.bind(null,Qe,t),[u.memoizedState,t]},useRef:function(t){var s=cn();return t={current:t},s.memoizedState=t},useState:function(t){t=Gf(t);var s=t.queue,i=y0.bind(null,Qe,s);return s.dispatch=i,[t.memoizedState,i]},useDebugValue:Zf,useDeferredValue:function(t,s){var i=cn();return Wf(i,t,s)},useTransition:function(){var t=Gf(!1);return t=m0.bind(null,Qe,t.queue,!0,!1),cn().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,s,i){var u=Qe,p=cn();if(ct){if(i===void 0)throw Error(a(407));i=i()}else{if(i=s(),yt===null)throw Error(a(349));(it&124)!==0||Bx(u,s,i)}p.memoizedState=i;var v={value:i,getSnapshot:s};return p.queue=v,r0(qx.bind(null,u,v,t),[t]),u.flags|=2048,Yo(9,pc(),Vx.bind(null,u,v,i,s),null),i},useId:function(){var t=cn(),s=yt.identifierPrefix;if(ct){var i=ks,u=Cs;i=(u&~(1<<32-Ge(u)-1)).toString(32)+i,s="«"+s+"R"+i,i=fc++,0qe?(Vt=He,He=null):Vt=He.sibling;var lt=oe(K,He,te[qe],de);if(lt===null){He===null&&(He=Vt);break}t&&He&<.alternate===null&&s(K,He),Z=v(lt,Z,qe),et===null?Re=lt:et.sibling=lt,et=lt,He=Vt}if(qe===te.length)return i(K,He),ct&&Zr(K,qe),Re;if(He===null){for(;qeqe?(Vt=He,He=null):Vt=He.sibling;var Nr=oe(K,He,lt.value,de);if(Nr===null){He===null&&(He=Vt);break}t&&He&&Nr.alternate===null&&s(K,He),Z=v(Nr,Z,qe),et===null?Re=Nr:et.sibling=Nr,et=Nr,He=Vt}if(lt.done)return i(K,He),ct&&Zr(K,qe),Re;if(He===null){for(;!lt.done;qe++,lt=te.next())lt=he(K,lt.value,de),lt!==null&&(Z=v(lt,Z,qe),et===null?Re=lt:et.sibling=lt,et=lt);return ct&&Zr(K,qe),Re}for(He=u(He);!lt.done;qe++,lt=te.next())lt=ae(He,K,qe,lt.value,de),lt!==null&&(t&<.alternate!==null&&He.delete(lt.key===null?qe:lt.key),Z=v(lt,Z,qe),et===null?Re=lt:et.sibling=lt,et=lt);return t&&He.forEach(function(WE){return s(K,WE)}),ct&&Zr(K,qe),Re}function gt(K,Z,te,de){if(typeof te=="object"&&te!==null&&te.type===j&&te.key===null&&(te=te.props.children),typeof te=="object"&&te!==null){switch(te.$$typeof){case y:e:{for(var Re=te.key;Z!==null;){if(Z.key===Re){if(Re=te.type,Re===j){if(Z.tag===7){i(K,Z.sibling),de=p(Z,te.props.children),de.return=K,K=de;break e}}else if(Z.elementType===Re||typeof Re=="object"&&Re!==null&&Re.$$typeof===H&&j0(Re)===Z.type){i(K,Z.sibling),de=p(Z,te.props),ji(de,te),de.return=K,K=de;break e}i(K,Z);break}else s(K,Z);Z=Z.sibling}te.type===j?(de=Gr(te.props.children,K.mode,de,te.key),de.return=K,K=de):(de=nc(te.type,te.key,te.props,null,K.mode,de),ji(de,te),de.return=K,K=de)}return k(K);case b:e:{for(Re=te.key;Z!==null;){if(Z.key===Re)if(Z.tag===4&&Z.stateNode.containerInfo===te.containerInfo&&Z.stateNode.implementation===te.implementation){i(K,Z.sibling),de=p(Z,te.children||[]),de.return=K,K=de;break e}else{i(K,Z);break}else s(K,Z);Z=Z.sibling}de=wf(te,K.mode,de),de.return=K,K=de}return k(K);case H:return Re=te._init,te=Re(te._payload),gt(K,Z,te,de)}if(U(te))return Fe(K,Z,te,de);if(G(te)){if(Re=G(te),typeof Re!="function")throw Error(a(150));return te=Re.call(te),Ve(K,Z,te,de)}if(typeof te.then=="function")return gt(K,Z,vc(te),de);if(te.$$typeof===E)return gt(K,Z,ac(K,te),de);bc(K,te)}return typeof te=="string"&&te!==""||typeof te=="number"||typeof te=="bigint"?(te=""+te,Z!==null&&Z.tag===6?(i(K,Z.sibling),de=p(Z,te),de.return=K,K=de):(i(K,Z),de=bf(te,K.mode,de),de.return=K,K=de),k(K)):i(K,Z)}return function(K,Z,te,de){try{Ni=0;var Re=gt(K,Z,te,de);return Go=null,Re}catch(He){if(He===mi||He===lc)throw He;var et=pn(29,He,null,K.mode);return et.lanes=de,et.return=K,et}finally{}}}var Xo=S0(!0),_0=S0(!1),Dn=$(null),ns=null;function ir(t){var s=t.alternate;V(It,It.current&1),V(Dn,t),ns===null&&(s===null||Vo.current!==null||s.memoizedState!==null)&&(ns=t)}function E0(t){if(t.tag===22){if(V(It,It.current),V(Dn,t),ns===null){var s=t.alternate;s!==null&&s.memoizedState!==null&&(ns=t)}}else lr()}function lr(){V(It,It.current),V(Dn,Dn.current)}function Rs(t){Y(Dn),ns===t&&(ns=null),Y(It)}var It=$(0);function wc(t){for(var s=t;s!==null;){if(s.tag===13){var i=s.memoizedState;if(i!==null&&(i=i.dehydrated,i===null||i.data==="$?"||Vm(i)))return s}else if(s.tag===19&&s.memoizedProps.revealOrder!==void 0){if((s.flags&128)!==0)return s}else if(s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break;for(;s.sibling===null;){if(s.return===null||s.return===t)return null;s=s.return}s.sibling.return=s.return,s=s.sibling}return null}function em(t,s,i,u){s=t.memoizedState,i=i(u,s),i=i==null?s:g({},s,i),t.memoizedState=i,t.lanes===0&&(t.updateQueue.baseState=i)}var tm={enqueueSetState:function(t,s,i){t=t._reactInternals;var u=vn(),p=rr(u);p.payload=s,i!=null&&(p.callback=i),s=or(t,p,u),s!==null&&(bn(s,t,u),pi(s,t,u))},enqueueReplaceState:function(t,s,i){t=t._reactInternals;var u=vn(),p=rr(u);p.tag=1,p.payload=s,i!=null&&(p.callback=i),s=or(t,p,u),s!==null&&(bn(s,t,u),pi(s,t,u))},enqueueForceUpdate:function(t,s){t=t._reactInternals;var i=vn(),u=rr(i);u.tag=2,s!=null&&(u.callback=s),s=or(t,u,i),s!==null&&(bn(s,t,i),pi(s,t,i))}};function C0(t,s,i,u,p,v,k){return t=t.stateNode,typeof t.shouldComponentUpdate=="function"?t.shouldComponentUpdate(u,v,k):s.prototype&&s.prototype.isPureReactComponent?!oi(i,u)||!oi(p,v):!0}function k0(t,s,i,u){t=s.state,typeof s.componentWillReceiveProps=="function"&&s.componentWillReceiveProps(i,u),typeof s.UNSAFE_componentWillReceiveProps=="function"&&s.UNSAFE_componentWillReceiveProps(i,u),s.state!==t&&tm.enqueueReplaceState(s,s.state,null)}function no(t,s){var i=s;if("ref"in s){i={};for(var u in s)u!=="ref"&&(i[u]=s[u])}if(t=t.defaultProps){i===s&&(i=g({},i));for(var p in t)i[p]===void 0&&(i[p]=t[p])}return i}var Nc=typeof reportError=="function"?reportError:function(t){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var s=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof t=="object"&&t!==null&&typeof t.message=="string"?String(t.message):String(t),error:t});if(!window.dispatchEvent(s))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",t);return}console.error(t)};function T0(t){Nc(t)}function A0(t){console.error(t)}function M0(t){Nc(t)}function jc(t,s){try{var i=t.onUncaughtError;i(s.value,{componentStack:s.stack})}catch(u){setTimeout(function(){throw u})}}function R0(t,s,i){try{var u=t.onCaughtError;u(i.value,{componentStack:i.stack,errorBoundary:s.tag===1?s.stateNode:null})}catch(p){setTimeout(function(){throw p})}}function nm(t,s,i){return i=rr(i),i.tag=3,i.payload={element:null},i.callback=function(){jc(t,s)},i}function D0(t){return t=rr(t),t.tag=3,t}function O0(t,s,i,u){var p=i.type.getDerivedStateFromError;if(typeof p=="function"){var v=u.value;t.payload=function(){return p(v)},t.callback=function(){R0(s,i,u)}}var k=i.stateNode;k!==null&&typeof k.componentDidCatch=="function"&&(t.callback=function(){R0(s,i,u),typeof p!="function"&&(hr===null?hr=new Set([this]):hr.add(this));var O=u.stack;this.componentDidCatch(u.value,{componentStack:O!==null?O:""})})}function W_(t,s,i,u,p){if(i.flags|=32768,u!==null&&typeof u=="object"&&typeof u.then=="function"){if(s=i.alternate,s!==null&&ui(s,i,p,!0),i=Dn.current,i!==null){switch(i.tag){case 13:return ns===null?Em():i.alternate===null&&St===0&&(St=3),i.flags&=-257,i.flags|=65536,i.lanes=p,u===Mf?i.flags|=16384:(s=i.updateQueue,s===null?i.updateQueue=new Set([u]):s.add(u),km(t,u,p)),!1;case 22:return i.flags|=65536,u===Mf?i.flags|=16384:(s=i.updateQueue,s===null?(s={transitions:null,markerInstances:null,retryQueue:new Set([u])},i.updateQueue=s):(i=s.retryQueue,i===null?s.retryQueue=new Set([u]):i.add(u)),km(t,u,p)),!1}throw Error(a(435,i.tag))}return km(t,u,p),Em(),!1}if(ct)return s=Dn.current,s!==null?((s.flags&65536)===0&&(s.flags|=256),s.flags|=65536,s.lanes=p,u!==Sf&&(t=Error(a(422),{cause:u}),ci(Tn(t,i)))):(u!==Sf&&(s=Error(a(423),{cause:u}),ci(Tn(s,i))),t=t.current.alternate,t.flags|=65536,p&=-p,t.lanes|=p,u=Tn(u,i),p=nm(t.stateNode,u,p),Of(t,p),St!==4&&(St=2)),!1;var v=Error(a(520),{cause:u});if(v=Tn(v,i),Ai===null?Ai=[v]:Ai.push(v),St!==4&&(St=2),s===null)return!0;u=Tn(u,i),i=s;do{switch(i.tag){case 3:return i.flags|=65536,t=p&-p,i.lanes|=t,t=nm(i.stateNode,u,t),Of(i,t),!1;case 1:if(s=i.type,v=i.stateNode,(i.flags&128)===0&&(typeof s.getDerivedStateFromError=="function"||v!==null&&typeof v.componentDidCatch=="function"&&(hr===null||!hr.has(v))))return i.flags|=65536,p&=-p,i.lanes|=p,p=D0(p),O0(p,t,i,u),Of(i,p),!1}i=i.return}while(i!==null);return!1}var z0=Error(a(461)),Ut=!1;function Yt(t,s,i,u){s.child=t===null?_0(s,null,i,u):Xo(s,t.child,i,u)}function I0(t,s,i,u,p){i=i.render;var v=s.ref;if("ref"in u){var k={};for(var O in u)O!=="ref"&&(k[O]=u[O])}else k=u;return Jr(s),u=Pf(t,s,i,k,v,p),O=Hf(),t!==null&&!Ut?(Uf(t,s,p),Ds(t,s,p)):(ct&&O&&Nf(s),s.flags|=1,Yt(t,s,u,p),s.child)}function L0(t,s,i,u,p){if(t===null){var v=i.type;return typeof v=="function"&&!vf(v)&&v.defaultProps===void 0&&i.compare===null?(s.tag=15,s.type=v,$0(t,s,v,u,p)):(t=nc(i.type,null,u,s,s.mode,p),t.ref=s.ref,t.return=s,s.child=t)}if(v=t.child,!um(t,p)){var k=v.memoizedProps;if(i=i.compare,i=i!==null?i:oi,i(k,u)&&t.ref===s.ref)return Ds(t,s,p)}return s.flags|=1,t=Es(v,u),t.ref=s.ref,t.return=s,s.child=t}function $0(t,s,i,u,p){if(t!==null){var v=t.memoizedProps;if(oi(v,u)&&t.ref===s.ref)if(Ut=!1,s.pendingProps=u=v,um(t,p))(t.flags&131072)!==0&&(Ut=!0);else return s.lanes=t.lanes,Ds(t,s,p)}return sm(t,s,i,u,p)}function P0(t,s,i){var u=s.pendingProps,p=u.children,v=t!==null?t.memoizedState:null;if(u.mode==="hidden"){if((s.flags&128)!==0){if(u=v!==null?v.baseLanes|i:i,t!==null){for(p=s.child=t.child,v=0;p!==null;)v=v|p.lanes|p.childLanes,p=p.sibling;s.childLanes=v&~u}else s.childLanes=0,s.child=null;return H0(t,s,u,i)}if((i&536870912)!==0)s.memoizedState={baseLanes:0,cachePool:null},t!==null&&ic(s,v!==null?v.cachePool:null),v!==null?$x(s,v):If(),E0(s);else return s.lanes=s.childLanes=536870912,H0(t,s,v!==null?v.baseLanes|i:i,i)}else v!==null?(ic(s,v.cachePool),$x(s,v),lr(),s.memoizedState=null):(t!==null&&ic(s,null),If(),lr());return Yt(t,s,p,i),s.child}function H0(t,s,i,u){var p=Af();return p=p===null?null:{parent:zt._currentValue,pool:p},s.memoizedState={baseLanes:i,cachePool:p},t!==null&&ic(s,null),If(),E0(s),t!==null&&ui(t,s,u,!0),null}function Sc(t,s){var i=s.ref;if(i===null)t!==null&&t.ref!==null&&(s.flags|=4194816);else{if(typeof i!="function"&&typeof i!="object")throw Error(a(284));(t===null||t.ref!==i)&&(s.flags|=4194816)}}function sm(t,s,i,u,p){return Jr(s),i=Pf(t,s,i,u,void 0,p),u=Hf(),t!==null&&!Ut?(Uf(t,s,p),Ds(t,s,p)):(ct&&u&&Nf(s),s.flags|=1,Yt(t,s,i,p),s.child)}function U0(t,s,i,u,p,v){return Jr(s),s.updateQueue=null,i=Hx(s,u,i,p),Px(t),u=Hf(),t!==null&&!Ut?(Uf(t,s,v),Ds(t,s,v)):(ct&&u&&Nf(s),s.flags|=1,Yt(t,s,i,v),s.child)}function B0(t,s,i,u,p){if(Jr(s),s.stateNode===null){var v=$o,k=i.contextType;typeof k=="object"&&k!==null&&(v=Qt(k)),v=new i(u,v),s.memoizedState=v.state!==null&&v.state!==void 0?v.state:null,v.updater=tm,s.stateNode=v,v._reactInternals=s,v=s.stateNode,v.props=u,v.state=s.memoizedState,v.refs={},Rf(s),k=i.contextType,v.context=typeof k=="object"&&k!==null?Qt(k):$o,v.state=s.memoizedState,k=i.getDerivedStateFromProps,typeof k=="function"&&(em(s,i,k,u),v.state=s.memoizedState),typeof i.getDerivedStateFromProps=="function"||typeof v.getSnapshotBeforeUpdate=="function"||typeof v.UNSAFE_componentWillMount!="function"&&typeof v.componentWillMount!="function"||(k=v.state,typeof v.componentWillMount=="function"&&v.componentWillMount(),typeof v.UNSAFE_componentWillMount=="function"&&v.UNSAFE_componentWillMount(),k!==v.state&&tm.enqueueReplaceState(v,v.state,null),xi(s,u,v,p),gi(),v.state=s.memoizedState),typeof v.componentDidMount=="function"&&(s.flags|=4194308),u=!0}else if(t===null){v=s.stateNode;var O=s.memoizedProps,F=no(i,O);v.props=F;var se=v.context,ue=i.contextType;k=$o,typeof ue=="object"&&ue!==null&&(k=Qt(ue));var he=i.getDerivedStateFromProps;ue=typeof he=="function"||typeof v.getSnapshotBeforeUpdate=="function",O=s.pendingProps!==O,ue||typeof v.UNSAFE_componentWillReceiveProps!="function"&&typeof v.componentWillReceiveProps!="function"||(O||se!==k)&&k0(s,v,u,k),sr=!1;var oe=s.memoizedState;v.state=oe,xi(s,u,v,p),gi(),se=s.memoizedState,O||oe!==se||sr?(typeof he=="function"&&(em(s,i,he,u),se=s.memoizedState),(F=sr||C0(s,i,F,u,oe,se,k))?(ue||typeof v.UNSAFE_componentWillMount!="function"&&typeof v.componentWillMount!="function"||(typeof v.componentWillMount=="function"&&v.componentWillMount(),typeof v.UNSAFE_componentWillMount=="function"&&v.UNSAFE_componentWillMount()),typeof v.componentDidMount=="function"&&(s.flags|=4194308)):(typeof v.componentDidMount=="function"&&(s.flags|=4194308),s.memoizedProps=u,s.memoizedState=se),v.props=u,v.state=se,v.context=k,u=F):(typeof v.componentDidMount=="function"&&(s.flags|=4194308),u=!1)}else{v=s.stateNode,Df(t,s),k=s.memoizedProps,ue=no(i,k),v.props=ue,he=s.pendingProps,oe=v.context,se=i.contextType,F=$o,typeof se=="object"&&se!==null&&(F=Qt(se)),O=i.getDerivedStateFromProps,(se=typeof O=="function"||typeof v.getSnapshotBeforeUpdate=="function")||typeof v.UNSAFE_componentWillReceiveProps!="function"&&typeof v.componentWillReceiveProps!="function"||(k!==he||oe!==F)&&k0(s,v,u,F),sr=!1,oe=s.memoizedState,v.state=oe,xi(s,u,v,p),gi();var ae=s.memoizedState;k!==he||oe!==ae||sr||t!==null&&t.dependencies!==null&&oc(t.dependencies)?(typeof O=="function"&&(em(s,i,O,u),ae=s.memoizedState),(ue=sr||C0(s,i,ue,u,oe,ae,F)||t!==null&&t.dependencies!==null&&oc(t.dependencies))?(se||typeof v.UNSAFE_componentWillUpdate!="function"&&typeof v.componentWillUpdate!="function"||(typeof v.componentWillUpdate=="function"&&v.componentWillUpdate(u,ae,F),typeof v.UNSAFE_componentWillUpdate=="function"&&v.UNSAFE_componentWillUpdate(u,ae,F)),typeof v.componentDidUpdate=="function"&&(s.flags|=4),typeof v.getSnapshotBeforeUpdate=="function"&&(s.flags|=1024)):(typeof v.componentDidUpdate!="function"||k===t.memoizedProps&&oe===t.memoizedState||(s.flags|=4),typeof v.getSnapshotBeforeUpdate!="function"||k===t.memoizedProps&&oe===t.memoizedState||(s.flags|=1024),s.memoizedProps=u,s.memoizedState=ae),v.props=u,v.state=ae,v.context=F,u=ue):(typeof v.componentDidUpdate!="function"||k===t.memoizedProps&&oe===t.memoizedState||(s.flags|=4),typeof v.getSnapshotBeforeUpdate!="function"||k===t.memoizedProps&&oe===t.memoizedState||(s.flags|=1024),u=!1)}return v=u,Sc(t,s),u=(s.flags&128)!==0,v||u?(v=s.stateNode,i=u&&typeof i.getDerivedStateFromError!="function"?null:v.render(),s.flags|=1,t!==null&&u?(s.child=Xo(s,t.child,null,p),s.child=Xo(s,null,i,p)):Yt(t,s,i,p),s.memoizedState=v.state,t=s.child):t=Ds(t,s,p),t}function V0(t,s,i,u){return li(),s.flags|=256,Yt(t,s,i,u),s.child}var rm={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function om(t){return{baseLanes:t,cachePool:Ax()}}function am(t,s,i){return t=t!==null?t.childLanes&~i:0,s&&(t|=On),t}function q0(t,s,i){var u=s.pendingProps,p=!1,v=(s.flags&128)!==0,k;if((k=v)||(k=t!==null&&t.memoizedState===null?!1:(It.current&2)!==0),k&&(p=!0,s.flags&=-129),k=(s.flags&32)!==0,s.flags&=-33,t===null){if(ct){if(p?ir(s):lr(),ct){var O=jt,F;if(F=O){e:{for(F=O,O=ts;F.nodeType!==8;){if(!O){O=null;break e}if(F=Vn(F.nextSibling),F===null){O=null;break e}}O=F}O!==null?(s.memoizedState={dehydrated:O,treeContext:Xr!==null?{id:Cs,overflow:ks}:null,retryLane:536870912,hydrationErrors:null},F=pn(18,null,null,0),F.stateNode=O,F.return=s,s.child=F,tn=s,jt=null,F=!0):F=!1}F||Kr(s)}if(O=s.memoizedState,O!==null&&(O=O.dehydrated,O!==null))return Vm(O)?s.lanes=32:s.lanes=536870912,null;Rs(s)}return O=u.children,u=u.fallback,p?(lr(),p=s.mode,O=_c({mode:"hidden",children:O},p),u=Gr(u,p,i,null),O.return=s,u.return=s,O.sibling=u,s.child=O,p=s.child,p.memoizedState=om(i),p.childLanes=am(t,k,i),s.memoizedState=rm,u):(ir(s),im(s,O))}if(F=t.memoizedState,F!==null&&(O=F.dehydrated,O!==null)){if(v)s.flags&256?(ir(s),s.flags&=-257,s=lm(t,s,i)):s.memoizedState!==null?(lr(),s.child=t.child,s.flags|=128,s=null):(lr(),p=u.fallback,O=s.mode,u=_c({mode:"visible",children:u.children},O),p=Gr(p,O,i,null),p.flags|=2,u.return=s,p.return=s,u.sibling=p,s.child=u,Xo(s,t.child,null,i),u=s.child,u.memoizedState=om(i),u.childLanes=am(t,k,i),s.memoizedState=rm,s=p);else if(ir(s),Vm(O)){if(k=O.nextSibling&&O.nextSibling.dataset,k)var se=k.dgst;k=se,u=Error(a(419)),u.stack="",u.digest=k,ci({value:u,source:null,stack:null}),s=lm(t,s,i)}else if(Ut||ui(t,s,i,!1),k=(i&t.childLanes)!==0,Ut||k){if(k=yt,k!==null&&(u=i&-i,u=(u&42)!==0?1:mn(u),u=(u&(k.suspendedLanes|i))!==0?0:u,u!==0&&u!==F.retryLane))throw F.retryLane=u,Lo(t,u),bn(k,t,u),z0;O.data==="$?"||Em(),s=lm(t,s,i)}else O.data==="$?"?(s.flags|=192,s.child=t.child,s=null):(t=F.treeContext,jt=Vn(O.nextSibling),tn=s,ct=!0,Wr=null,ts=!1,t!==null&&(Mn[Rn++]=Cs,Mn[Rn++]=ks,Mn[Rn++]=Xr,Cs=t.id,ks=t.overflow,Xr=s),s=im(s,u.children),s.flags|=4096);return s}return p?(lr(),p=u.fallback,O=s.mode,F=t.child,se=F.sibling,u=Es(F,{mode:"hidden",children:u.children}),u.subtreeFlags=F.subtreeFlags&65011712,se!==null?p=Es(se,p):(p=Gr(p,O,i,null),p.flags|=2),p.return=s,u.return=s,u.sibling=p,s.child=u,u=p,p=s.child,O=t.child.memoizedState,O===null?O=om(i):(F=O.cachePool,F!==null?(se=zt._currentValue,F=F.parent!==se?{parent:se,pool:se}:F):F=Ax(),O={baseLanes:O.baseLanes|i,cachePool:F}),p.memoizedState=O,p.childLanes=am(t,k,i),s.memoizedState=rm,u):(ir(s),i=t.child,t=i.sibling,i=Es(i,{mode:"visible",children:u.children}),i.return=s,i.sibling=null,t!==null&&(k=s.deletions,k===null?(s.deletions=[t],s.flags|=16):k.push(t)),s.child=i,s.memoizedState=null,i)}function im(t,s){return s=_c({mode:"visible",children:s},t.mode),s.return=t,t.child=s}function _c(t,s){return t=pn(22,t,null,s),t.lanes=0,t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},t}function lm(t,s,i){return Xo(s,t.child,null,i),t=im(s,s.pendingProps.children),t.flags|=2,s.memoizedState=null,t}function F0(t,s,i){t.lanes|=s;var u=t.alternate;u!==null&&(u.lanes|=s),Ef(t.return,s,i)}function cm(t,s,i,u,p){var v=t.memoizedState;v===null?t.memoizedState={isBackwards:s,rendering:null,renderingStartTime:0,last:u,tail:i,tailMode:p}:(v.isBackwards=s,v.rendering=null,v.renderingStartTime=0,v.last=u,v.tail=i,v.tailMode=p)}function Y0(t,s,i){var u=s.pendingProps,p=u.revealOrder,v=u.tail;if(Yt(t,s,u.children,i),u=It.current,(u&2)!==0)u=u&1|2,s.flags|=128;else{if(t!==null&&(t.flags&128)!==0)e:for(t=s.child;t!==null;){if(t.tag===13)t.memoizedState!==null&&F0(t,i,s);else if(t.tag===19)F0(t,i,s);else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===s)break e;for(;t.sibling===null;){if(t.return===null||t.return===s)break e;t=t.return}t.sibling.return=t.return,t=t.sibling}u&=1}switch(V(It,u),p){case"forwards":for(i=s.child,p=null;i!==null;)t=i.alternate,t!==null&&wc(t)===null&&(p=i),i=i.sibling;i=p,i===null?(p=s.child,s.child=null):(p=i.sibling,i.sibling=null),cm(s,!1,p,i,v);break;case"backwards":for(i=null,p=s.child,s.child=null;p!==null;){if(t=p.alternate,t!==null&&wc(t)===null){s.child=p;break}t=p.sibling,p.sibling=i,i=p,p=t}cm(s,!0,i,null,v);break;case"together":cm(s,!1,null,null,void 0);break;default:s.memoizedState=null}return s.child}function Ds(t,s,i){if(t!==null&&(s.dependencies=t.dependencies),mr|=s.lanes,(i&s.childLanes)===0)if(t!==null){if(ui(t,s,i,!1),(i&s.childLanes)===0)return null}else return null;if(t!==null&&s.child!==t.child)throw Error(a(153));if(s.child!==null){for(t=s.child,i=Es(t,t.pendingProps),s.child=i,i.return=s;t.sibling!==null;)t=t.sibling,i=i.sibling=Es(t,t.pendingProps),i.return=s;i.sibling=null}return s.child}function um(t,s){return(t.lanes&s)!==0?!0:(t=t.dependencies,!!(t!==null&&oc(t)))}function K_(t,s,i){switch(s.tag){case 3:ie(s,s.stateNode.containerInfo),nr(s,zt,t.memoizedState.cache),li();break;case 27:case 5:Ee(s);break;case 4:ie(s,s.stateNode.containerInfo);break;case 10:nr(s,s.type,s.memoizedProps.value);break;case 13:var u=s.memoizedState;if(u!==null)return u.dehydrated!==null?(ir(s),s.flags|=128,null):(i&s.child.childLanes)!==0?q0(t,s,i):(ir(s),t=Ds(t,s,i),t!==null?t.sibling:null);ir(s);break;case 19:var p=(t.flags&128)!==0;if(u=(i&s.childLanes)!==0,u||(ui(t,s,i,!1),u=(i&s.childLanes)!==0),p){if(u)return Y0(t,s,i);s.flags|=128}if(p=s.memoizedState,p!==null&&(p.rendering=null,p.tail=null,p.lastEffect=null),V(It,It.current),u)break;return null;case 22:case 23:return s.lanes=0,P0(t,s,i);case 24:nr(s,zt,t.memoizedState.cache)}return Ds(t,s,i)}function G0(t,s,i){if(t!==null)if(t.memoizedProps!==s.pendingProps)Ut=!0;else{if(!um(t,i)&&(s.flags&128)===0)return Ut=!1,K_(t,s,i);Ut=(t.flags&131072)!==0}else Ut=!1,ct&&(s.flags&1048576)!==0&&jx(s,rc,s.index);switch(s.lanes=0,s.tag){case 16:e:{t=s.pendingProps;var u=s.elementType,p=u._init;if(u=p(u._payload),s.type=u,typeof u=="function")vf(u)?(t=no(u,t),s.tag=1,s=B0(null,s,u,t,i)):(s.tag=0,s=sm(null,s,u,t,i));else{if(u!=null){if(p=u.$$typeof,p===M){s.tag=11,s=I0(null,s,u,t,i);break e}else if(p===z){s.tag=14,s=L0(null,s,u,t,i);break e}}throw s=B(u)||u,Error(a(306,s,""))}}return s;case 0:return sm(t,s,s.type,s.pendingProps,i);case 1:return u=s.type,p=no(u,s.pendingProps),B0(t,s,u,p,i);case 3:e:{if(ie(s,s.stateNode.containerInfo),t===null)throw Error(a(387));u=s.pendingProps;var v=s.memoizedState;p=v.element,Df(t,s),xi(s,u,null,i);var k=s.memoizedState;if(u=k.cache,nr(s,zt,u),u!==v.cache&&Cf(s,[zt],i,!0),gi(),u=k.element,v.isDehydrated)if(v={element:u,isDehydrated:!1,cache:k.cache},s.updateQueue.baseState=v,s.memoizedState=v,s.flags&256){s=V0(t,s,u,i);break e}else if(u!==p){p=Tn(Error(a(424)),s),ci(p),s=V0(t,s,u,i);break e}else{switch(t=s.stateNode.containerInfo,t.nodeType){case 9:t=t.body;break;default:t=t.nodeName==="HTML"?t.ownerDocument.body:t}for(jt=Vn(t.firstChild),tn=s,ct=!0,Wr=null,ts=!0,i=_0(s,null,u,i),s.child=i;i;)i.flags=i.flags&-3|4096,i=i.sibling}else{if(li(),u===p){s=Ds(t,s,i);break e}Yt(t,s,u,i)}s=s.child}return s;case 26:return Sc(t,s),t===null?(i=Ky(s.type,null,s.pendingProps,null))?s.memoizedState=i:ct||(i=s.type,t=s.pendingProps,u=Pc(fe.current).createElement(i),u[Ht]=s,u[Kt]=t,Xt(u,i,t),Tt(u),s.stateNode=u):s.memoizedState=Ky(s.type,t.memoizedProps,s.pendingProps,t.memoizedState),null;case 27:return Ee(s),t===null&&ct&&(u=s.stateNode=Xy(s.type,s.pendingProps,fe.current),tn=s,ts=!0,p=jt,xr(s.type)?(qm=p,jt=Vn(u.firstChild)):jt=p),Yt(t,s,s.pendingProps.children,i),Sc(t,s),t===null&&(s.flags|=4194304),s.child;case 5:return t===null&&ct&&((p=u=jt)&&(u=_E(u,s.type,s.pendingProps,ts),u!==null?(s.stateNode=u,tn=s,jt=Vn(u.firstChild),ts=!1,p=!0):p=!1),p||Kr(s)),Ee(s),p=s.type,v=s.pendingProps,k=t!==null?t.memoizedProps:null,u=v.children,Hm(p,v)?u=null:k!==null&&Hm(p,k)&&(s.flags|=32),s.memoizedState!==null&&(p=Pf(t,s,V_,null,null,i),Pi._currentValue=p),Sc(t,s),Yt(t,s,u,i),s.child;case 6:return t===null&&ct&&((t=i=jt)&&(i=EE(i,s.pendingProps,ts),i!==null?(s.stateNode=i,tn=s,jt=null,t=!0):t=!1),t||Kr(s)),null;case 13:return q0(t,s,i);case 4:return ie(s,s.stateNode.containerInfo),u=s.pendingProps,t===null?s.child=Xo(s,null,u,i):Yt(t,s,u,i),s.child;case 11:return I0(t,s,s.type,s.pendingProps,i);case 7:return Yt(t,s,s.pendingProps,i),s.child;case 8:return Yt(t,s,s.pendingProps.children,i),s.child;case 12:return Yt(t,s,s.pendingProps.children,i),s.child;case 10:return u=s.pendingProps,nr(s,s.type,u.value),Yt(t,s,u.children,i),s.child;case 9:return p=s.type._context,u=s.pendingProps.children,Jr(s),p=Qt(p),u=u(p),s.flags|=1,Yt(t,s,u,i),s.child;case 14:return L0(t,s,s.type,s.pendingProps,i);case 15:return $0(t,s,s.type,s.pendingProps,i);case 19:return Y0(t,s,i);case 31:return u=s.pendingProps,i=s.mode,u={mode:u.mode,children:u.children},t===null?(i=_c(u,i),i.ref=s.ref,s.child=i,i.return=s,s=i):(i=Es(t.child,u),i.ref=s.ref,s.child=i,i.return=s,s=i),s;case 22:return P0(t,s,i);case 24:return Jr(s),u=Qt(zt),t===null?(p=Af(),p===null&&(p=yt,v=kf(),p.pooledCache=v,v.refCount++,v!==null&&(p.pooledCacheLanes|=i),p=v),s.memoizedState={parent:u,cache:p},Rf(s),nr(s,zt,p)):((t.lanes&i)!==0&&(Df(t,s),xi(s,null,null,i),gi()),p=t.memoizedState,v=s.memoizedState,p.parent!==u?(p={parent:u,cache:u},s.memoizedState=p,s.lanes===0&&(s.memoizedState=s.updateQueue.baseState=p),nr(s,zt,u)):(u=v.cache,nr(s,zt,u),u!==p.cache&&Cf(s,[zt],i,!0))),Yt(t,s,s.pendingProps.children,i),s.child;case 29:throw s.pendingProps}throw Error(a(156,s.tag))}function Os(t){t.flags|=4}function X0(t,s){if(s.type!=="stylesheet"||(s.state.loading&4)!==0)t.flags&=-16777217;else if(t.flags|=16777216,!nv(s)){if(s=Dn.current,s!==null&&((it&4194048)===it?ns!==null:(it&62914560)!==it&&(it&536870912)===0||s!==ns))throw hi=Mf,Mx;t.flags|=8192}}function Ec(t,s){s!==null&&(t.flags|=4),t.flags&16384&&(s=t.tag!==22?Pe():536870912,t.lanes|=s,Qo|=s)}function Si(t,s){if(!ct)switch(t.tailMode){case"hidden":s=t.tail;for(var i=null;s!==null;)s.alternate!==null&&(i=s),s=s.sibling;i===null?t.tail=null:i.sibling=null;break;case"collapsed":i=t.tail;for(var u=null;i!==null;)i.alternate!==null&&(u=i),i=i.sibling;u===null?s||t.tail===null?t.tail=null:t.tail.sibling=null:u.sibling=null}}function Nt(t){var s=t.alternate!==null&&t.alternate.child===t.child,i=0,u=0;if(s)for(var p=t.child;p!==null;)i|=p.lanes|p.childLanes,u|=p.subtreeFlags&65011712,u|=p.flags&65011712,p.return=t,p=p.sibling;else for(p=t.child;p!==null;)i|=p.lanes|p.childLanes,u|=p.subtreeFlags,u|=p.flags,p.return=t,p=p.sibling;return t.subtreeFlags|=u,t.childLanes=i,s}function Q_(t,s,i){var u=s.pendingProps;switch(jf(s),s.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Nt(s),null;case 1:return Nt(s),null;case 3:return i=s.stateNode,u=null,t!==null&&(u=t.memoizedState.cache),s.memoizedState.cache!==u&&(s.flags|=2048),As(zt),ge(),i.pendingContext&&(i.context=i.pendingContext,i.pendingContext=null),(t===null||t.child===null)&&(ii(s)?Os(s):t===null||t.memoizedState.isDehydrated&&(s.flags&256)===0||(s.flags|=1024,Ex())),Nt(s),null;case 26:return i=s.memoizedState,t===null?(Os(s),i!==null?(Nt(s),X0(s,i)):(Nt(s),s.flags&=-16777217)):i?i!==t.memoizedState?(Os(s),Nt(s),X0(s,i)):(Nt(s),s.flags&=-16777217):(t.memoizedProps!==u&&Os(s),Nt(s),s.flags&=-16777217),null;case 27:Ne(s),i=fe.current;var p=s.type;if(t!==null&&s.stateNode!=null)t.memoizedProps!==u&&Os(s);else{if(!u){if(s.stateNode===null)throw Error(a(166));return Nt(s),null}t=J.current,ii(s)?Sx(s):(t=Xy(p,u,i),s.stateNode=t,Os(s))}return Nt(s),null;case 5:if(Ne(s),i=s.type,t!==null&&s.stateNode!=null)t.memoizedProps!==u&&Os(s);else{if(!u){if(s.stateNode===null)throw Error(a(166));return Nt(s),null}if(t=J.current,ii(s))Sx(s);else{switch(p=Pc(fe.current),t){case 1:t=p.createElementNS("http://www.w3.org/2000/svg",i);break;case 2:t=p.createElementNS("http://www.w3.org/1998/Math/MathML",i);break;default:switch(i){case"svg":t=p.createElementNS("http://www.w3.org/2000/svg",i);break;case"math":t=p.createElementNS("http://www.w3.org/1998/Math/MathML",i);break;case"script":t=p.createElement("div"),t.innerHTML="