From b89adb280b45a41ab0f0ff28d7947a73d3adbd4c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:39:09 +0900 Subject: [PATCH 1/8] Python: skill name validation improvements (#4530) * Initial plan * Port .NET validation improvements to Python skills: reject consecutive hyphens and enforce directory name match Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> * Fix E501 lint error: split long error message string in _validate_skill_metadata Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> --- .../packages/core/agent_framework/_skills.py | 18 ++++++++++-- .../packages/core/tests/core/test_skills.py | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) 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/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 From 1b95e8585d7e892c42c62313908664b8d7c7e1a8 Mon Sep 17 00:00:00 2001 From: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:20:55 +0900 Subject: [PATCH 2/8] Python: Add `allowed_checkpoint_types` support to `CosmosCheckpointStorage` for parity with `FileCheckpointStorage` (#5202) * Python: Add allowed_checkpoint_types support to CosmosCheckpointStorage (#5200) Add allowed_checkpoint_types parameter to CosmosCheckpointStorage for parity with FileCheckpointStorage. This ensures both providers use the same restricted pickle deserialization by default. Changes: - Accept allowed_checkpoint_types kwarg in __init__, stored as frozenset - Convert _document_to_checkpoint from @staticmethod to instance method - Forward allowed_types to decode_checkpoint_value on all load paths - Update class docstring to describe the new parameter - Add tests covering built-in safe types, app type opt-in/blocking, and all load paths (load, list_checkpoints, get_latest) - Add changelog entry noting the breaking behavior change BREAKING CHANGE: CosmosCheckpointStorage now uses restricted pickle deserialization by default. Checkpoints containing application-defined types will require passing those types via allowed_checkpoint_types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Add `allowed_checkpoint_types` support to `CosmosCheckpointStorage` for parity with `FileCheckpointStorage` Fixes #5200 * Address PR review: add pickle security warning and fix docstring examples - Reintroduce explicit security warning about pickle deserialization risks - Convert Example:: block to .. code-block:: python with imports for consistency with other docstring examples - Note: PR title should be updated to include [BREAKING] prefix per changelog convention (comment #3, requires GitHub UI change) Fixes #5200 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/CHANGELOG.md | 3 + .../_checkpoint_storage.py | 42 +++++- .../tests/test_cosmos_checkpoint_storage.py | 140 ++++++++++++++++++ 3 files changed, 179 insertions(+), 6 deletions(-) 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/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" From 3c31ac28b53562b1db91f2c42a86d083b8d7dd14 Mon Sep 17 00:00:00 2001 From: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:52:03 +0900 Subject: [PATCH 3/8] Python: Fix HandoffBuilder dropping function-level middleware when cloning agents (#5220) * Fix HandoffBuilder dropping function-level middleware when cloning agents (#5173) _clone_chat_agent() was using agent.agent_middleware (agent-level only) instead of agent.middleware (all types), which silently dropped any function middleware registered on the original agent. Changed to use agent.middleware to preserve all middleware types (agent, function, and chat) during cloning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Fix HandoffBuilder dropping function-level middleware when cloning agents Fixes #5173 * Fix false-positive middleware regression test (#5173) The test used isinstance(m, FunctionMiddleware) which matched _AutoHandoffMiddleware (always appended during build) instead of the user's @function_middleware decorator. Assert directly that tracking_middleware is present in the cloned agent's middleware list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback for #5173: Python: [Bug]: HandoffBuilder drops function-level middleware when cloning agents --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../_handoff.py | 2 +- .../orchestrations/tests/test_handoff.py | 43 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py index e71fdd3882..c3e156096c 100644 --- a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py +++ b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py @@ -287,7 +287,7 @@ class HandoffAgentExecutor(AgentExecutor): name=agent.name, description=agent.description, context_providers=agent.context_providers, - middleware=agent.agent_middleware, + middleware=agent.middleware, require_per_service_call_history_persistence=agent.require_per_service_call_history_persistence, default_options=cloned_options, # type: ignore[assignment] ) diff --git a/python/packages/orchestrations/tests/test_handoff.py b/python/packages/orchestrations/tests/test_handoff.py index 225b408422..a512d9b9df 100644 --- a/python/packages/orchestrations/tests/test_handoff.py +++ b/python/packages/orchestrations/tests/test_handoff.py @@ -18,11 +18,16 @@ from agent_framework import ( ResponseStream, WorkflowEvent, WorkflowRunState, + function_middleware, resolve_agent_id, tool, ) from agent_framework._clients import BaseChatClient -from agent_framework._middleware import ChatMiddlewareLayer, FunctionInvocationContext, MiddlewareTermination +from agent_framework._middleware import ( + ChatMiddlewareLayer, + FunctionInvocationContext, + MiddlewareTermination, +) from agent_framework._tools import FunctionInvocationLayer, FunctionTool from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder, HandoffSentEvent from pytest import param @@ -745,6 +750,42 @@ async def test_handoff_clone_preserves_per_service_call_history_persistence() -> assert all(message.role != "tool" for message in stored_messages) +async def test_handoff_clone_preserves_all_middleware_types() -> None: + """Handoff clones should preserve function and agent middleware from the original agent.""" + + @function_middleware + async def tracking_middleware(context: FunctionInvocationContext, call_next): + await call_next() + + agent_a = Agent( + id="agent_a", + name="agent_a", + client=MockChatClient(name="agent_a", handoff_to="agent_b"), + middleware=[tracking_middleware], + require_per_service_call_history_persistence=True, + ) + agent_b = Agent( + id="agent_b", + name="agent_b", + client=MockChatClient(name="agent_b"), + default_options={"tool_choice": "none"}, + require_per_service_call_history_persistence=True, + ) + + workflow = ( + HandoffBuilder(participants=[agent_a, agent_b], termination_condition=lambda _: False) + .with_start_agent(agent_a) + .add_handoff(agent_a, [agent_b]) + .add_handoff(agent_b, [agent_a]) + .build() + ) + + executor = workflow.executors[resolve_agent_id(agent_a)] + assert isinstance(executor, HandoffAgentExecutor) + cloned_middleware = executor._agent.middleware or [] + assert tracking_middleware in cloned_middleware, "User function middleware should be preserved on cloned agent" + + def test_clean_conversation_for_handoff_keeps_text_only_history() -> None: """Tool-control messages must be excluded from persisted handoff history.""" function_call = Content.from_function_call( From f183f888a344031e348f966110498eeb468fe0eb Mon Sep 17 00:00:00 2001 From: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:58:09 +0900 Subject: [PATCH 4/8] Python: AG-UI deterministic state updates from tool results (#5201) * AG-UI deterministic state updates from tool results * fix(ag-ui): address PR #5201 review comments 1. Add missing AGUIEventConverter, AGUIHttpService, __version__ to _IMPORTS in core ag_ui lazy-export list to match the .pyi stub. 2. Coalesce predictive and deterministic state snapshots into a single StateSnapshotEvent when both mechanisms are active on the same tool result, reducing redundant snapshot traffic. 3. Update state_update() docstring to clarify that a predictive snapshot may be emitted before the deterministic one when predict_state_config is active. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ag-ui/agent_framework_ag_ui/__init__.py | 2 + .../agent_framework_ag_ui/_run_common.py | 84 +++++- .../ag-ui/agent_framework_ag_ui/_state.py | 84 ++++++ .../agents/weather_state_agent.py | 92 ++++++ .../server/main.py | 9 + .../test_scenario_deterministic_state.py | 267 ++++++++++++++++++ .../ag_ui/test_agent_wrapper_comprehensive.py | 92 ++++++ .../ag-ui/tests/ag_ui/test_public_exports.py | 17 ++ .../ag-ui/tests/ag_ui/test_run_common.py | 226 +++++++++++++++ .../core/agent_framework/ag_ui/__init__.py | 7 + .../core/agent_framework/ag_ui/__init__.pyi | 2 + 11 files changed, 878 insertions(+), 4 deletions(-) create mode 100644 python/packages/ag-ui/agent_framework_ag_ui/_state.py create mode 100644 python/packages/ag-ui/agent_framework_ag_ui_examples/agents/weather_state_agent.py create mode 100644 python/packages/ag-ui/tests/ag_ui/golden/test_scenario_deterministic_state.py 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/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", ] From 7bb0feca591d63722d520dbc94af76d70b206129 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 14 Apr 2026 00:13:42 -0700 Subject: [PATCH 5/8] Python: Move InMemory history provider injection to the first invocation (#5236) * Move InMemory history provider injection to the first invocation * Add tests --- .../core/agent_framework/_workflows/_agent.py | 24 +++- .../tests/workflow/test_workflow_agent.py | 105 ++++++++++++++++++ 2 files changed, 124 insertions(+), 5 deletions(-) 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/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 From 98e17764a47141739d1a1b24eae9f3c6649f9626 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Tue, 14 Apr 2026 11:27:52 +0200 Subject: [PATCH 6/8] Python: Fix DevUI streaming memory growth and add cross-platform regression coverage (#5221) * fix for memory leak in devui * update async sleep * remove old func --- .../agent_framework_devui/ui/assets/index.js | 242 +++--- python/packages/devui/frontend/src/App.tsx | 60 +- .../components/features/agent/agent-view.tsx | 202 +++-- .../devui/frontend/src/services/api.ts | 112 ++- .../frontend/src/services/streaming-state.ts | 154 ++-- .../tests/devui/test_ui_memory_regression.py | 750 ++++++++++++++++++ 6 files changed, 1211 insertions(+), 309 deletions(-) create mode 100644 python/packages/devui/tests/devui/test_ui_memory_regression.py 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="