From ed3cbf4cc39275fa0c8712289cdee2e1aedec984 Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Fri, 12 Dec 2025 08:50:33 +0900 Subject: [PATCH 1/9] Further support for declarative python workflows --- .gitignore | 1 + .../core/agent_framework/_workflows/_edge.py | 63 +- .../_workflows/_edge_runner.py | 30 +- .../agent_framework/_workflows/_runner.py | 2 +- .../_workflows/_workflow_builder.py | 6 +- .../agent_framework/openai/_chat_client.py | 6 +- .../agent_framework_declarative/__init__.py | 12 +- .../agent_framework_declarative/_models.py | 8 +- .../_workflows/__init__.py | 30 + .../_workflows/_actions_agents.py | 497 ++ .../_workflows/_actions_basic.py | 575 ++ .../_workflows/_actions_control_flow.py | 397 ++ .../_workflows/_actions_error.py | 130 + .../_workflows/_factory.py | 450 ++ .../_workflows/_graph/__init__.py | 113 + .../_workflows/_graph/_base.py | 668 +++ .../_workflows/_graph/_builder.py | 1043 ++++ .../_workflows/_graph/_executors_agents.py | 778 +++ .../_workflows/_graph/_executors_basic.py | 274 + .../_graph/_executors_control_flow.py | 299 + .../_graph/_executors_human_input.py | 260 + .../_workflows/_handlers.py | 211 + .../_workflows/_human_input.py | 317 + .../_workflows/_powerfx_functions.py | 464 ++ .../_workflows/_state.py | 548 ++ .../tests/test_additional_handlers.py | 346 ++ .../tests/test_declarative_models.py | 19 + .../declarative/tests/test_graph_executors.py | 537 ++ .../tests/test_graph_workflow_integration.py | 332 ++ .../tests/test_human_input_handlers.py | 286 + .../tests/test_powerfx_functions.py | 242 + .../tests/test_workflow_factory.py | 279 + .../tests/test_workflow_handlers.py | 424 ++ .../test_workflow_samples_integration.py | 268 + .../declarative/tests/test_workflow_state.py | 225 + .../devui/agent_framework_devui/_executor.py | 2 + .../devui/agent_framework_devui/_mapper.py | 7 +- .../devui/agent_framework_devui/_server.py | 59 +- .../devui/agent_framework_devui/_utils.py | 32 +- .../agent_framework_devui/ui/assets/index.js | 4 +- .../packages/devui/frontend/package-lock.json | 5285 +++++++++++++++++ .../features/workflow/workflow-input-form.tsx | 5 +- .../frontend/src/utils/workflow-utils.ts | 8 + python/packages/devui/frontend/yarn.lock | 438 +- .../getting_started/workflows/README.md | 13 + .../workflows/declarative/README.md | 93 + .../workflows/declarative/__init__.py | 3 + .../conditional_workflow/README.md | 23 + .../declarative/conditional_workflow/main.py | 52 + .../conditional_workflow/serve_workflow.py | 31 + .../conditional_workflow/workflow.yaml | 69 + .../declarative/customer_support/__init__.py | 1 + .../declarative/customer_support/main.py | 338 ++ .../customer_support/ticketing_plugin.py | 79 + .../customer_support/workflow.yaml | 164 + .../declarative/deep_research/__init__.py | 1 + .../declarative/deep_research/main.py | 205 + .../declarative/function_tools/README.md | 90 + .../declarative/function_tools/main.py | 120 + .../declarative/function_tools/workflow.yaml | 22 + .../declarative/human_in_loop/README.md | 59 + .../declarative/human_in_loop/main.py | 84 + .../declarative/human_in_loop/workflow.yaml | 75 + .../workflows/declarative/marketing/README.md | 76 + .../workflows/declarative/marketing/main.py | 97 + .../declarative/marketing/workflow.yaml | 30 + .../declarative/multi_agent/README.md | 67 + .../workflows/declarative/multi_agent/main.py | 98 + .../declarative/multi_agent/workflow.yaml | 71 + .../declarative/simple_workflow/README.md | 24 + .../declarative/simple_workflow/main.py | 40 + .../declarative/simple_workflow/workflow.yaml | 38 + .../declarative/student_teacher/README.md | 61 + .../declarative/student_teacher/main.py | 94 + .../declarative/student_teacher/workflow.yaml | 108 + python/uv.lock | 198 +- 76 files changed, 17956 insertions(+), 548 deletions(-) create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/__init__.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_actions_agents.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_actions_basic.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_actions_control_flow.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_actions_error.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_factory.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_graph/__init__.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_graph/_base.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_graph/_builder.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_agents.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_basic.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_control_flow.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_human_input.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_handlers.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_human_input.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_powerfx_functions.py create mode 100644 python/packages/declarative/agent_framework_declarative/_workflows/_state.py create mode 100644 python/packages/declarative/tests/test_additional_handlers.py create mode 100644 python/packages/declarative/tests/test_graph_executors.py create mode 100644 python/packages/declarative/tests/test_graph_workflow_integration.py create mode 100644 python/packages/declarative/tests/test_human_input_handlers.py create mode 100644 python/packages/declarative/tests/test_powerfx_functions.py create mode 100644 python/packages/declarative/tests/test_workflow_factory.py create mode 100644 python/packages/declarative/tests/test_workflow_handlers.py create mode 100644 python/packages/declarative/tests/test_workflow_samples_integration.py create mode 100644 python/packages/declarative/tests/test_workflow_state.py create mode 100644 python/packages/devui/frontend/package-lock.json create mode 100644 python/samples/getting_started/workflows/declarative/README.md create mode 100644 python/samples/getting_started/workflows/declarative/__init__.py create mode 100644 python/samples/getting_started/workflows/declarative/conditional_workflow/README.md create mode 100644 python/samples/getting_started/workflows/declarative/conditional_workflow/main.py create mode 100644 python/samples/getting_started/workflows/declarative/conditional_workflow/serve_workflow.py create mode 100644 python/samples/getting_started/workflows/declarative/conditional_workflow/workflow.yaml create mode 100644 python/samples/getting_started/workflows/declarative/customer_support/__init__.py create mode 100644 python/samples/getting_started/workflows/declarative/customer_support/main.py create mode 100644 python/samples/getting_started/workflows/declarative/customer_support/ticketing_plugin.py create mode 100644 python/samples/getting_started/workflows/declarative/customer_support/workflow.yaml create mode 100644 python/samples/getting_started/workflows/declarative/deep_research/__init__.py create mode 100644 python/samples/getting_started/workflows/declarative/deep_research/main.py create mode 100644 python/samples/getting_started/workflows/declarative/function_tools/README.md create mode 100644 python/samples/getting_started/workflows/declarative/function_tools/main.py create mode 100644 python/samples/getting_started/workflows/declarative/function_tools/workflow.yaml create mode 100644 python/samples/getting_started/workflows/declarative/human_in_loop/README.md create mode 100644 python/samples/getting_started/workflows/declarative/human_in_loop/main.py create mode 100644 python/samples/getting_started/workflows/declarative/human_in_loop/workflow.yaml create mode 100644 python/samples/getting_started/workflows/declarative/marketing/README.md create mode 100644 python/samples/getting_started/workflows/declarative/marketing/main.py create mode 100644 python/samples/getting_started/workflows/declarative/marketing/workflow.yaml create mode 100644 python/samples/getting_started/workflows/declarative/multi_agent/README.md create mode 100644 python/samples/getting_started/workflows/declarative/multi_agent/main.py create mode 100644 python/samples/getting_started/workflows/declarative/multi_agent/workflow.yaml create mode 100644 python/samples/getting_started/workflows/declarative/simple_workflow/README.md create mode 100644 python/samples/getting_started/workflows/declarative/simple_workflow/main.py create mode 100644 python/samples/getting_started/workflows/declarative/simple_workflow/workflow.yaml create mode 100644 python/samples/getting_started/workflows/declarative/student_teacher/README.md create mode 100644 python/samples/getting_started/workflows/declarative/student_teacher/main.py create mode 100644 python/samples/getting_started/workflows/declarative/student_teacher/workflow.yaml diff --git a/.gitignore b/.gitignore index f0f8c09495..bd3f727a0c 100644 --- a/.gitignore +++ b/.gitignore @@ -226,3 +226,4 @@ local.settings.json # Database files *.db +python/dotnet-ref diff --git a/python/packages/core/agent_framework/_workflows/_edge.py b/python/packages/core/agent_framework/_workflows/_edge.py index 2d144657fe..3b880a9395 100644 --- a/python/packages/core/agent_framework/_workflows/_edge.py +++ b/python/packages/core/agent_framework/_workflows/_edge.py @@ -71,6 +71,11 @@ class Edge(DictConvertible): serialising the edge down to primitives we can reconstruct the topology of a workflow irrespective of the original Python process. + Edges support two types of conditions: + - Sync conditions: `condition(data) -> bool` - evaluated against message data only + - Async conditions: `async_condition(data, shared_state) -> bool` - evaluated against + message data AND shared state, allowing state-aware routing decisions + Examples: .. code-block:: python @@ -85,6 +90,7 @@ class Edge(DictConvertible): target_id: str condition_name: str | None _condition: Callable[[Any], bool] | None = field(default=None, repr=False, compare=False) + _async_condition: Callable[[Any, Any], Any] | None = field(default=None, repr=False, compare=False) def __init__( self, @@ -93,6 +99,7 @@ def __init__( condition: Callable[[Any], bool] | None = None, *, condition_name: str | None = None, + async_condition: Callable[[Any, Any], Any] | None = None, ) -> None: """Initialize a fully-specified edge between two workflow executors. @@ -110,6 +117,10 @@ def __init__( Optional override that pins a human-friendly name for the condition when the callable cannot be introspected (for example after deserialization). + async_condition: + Optional async predicate that receives (message_data, shared_state) and + returns `True` when the edge should be traversed. This is evaluated + instead of `condition` when present, allowing state-aware routing. Examples: .. code-block:: python @@ -125,7 +136,14 @@ def __init__( self.source_id = source_id self.target_id = target_id self._condition = condition - self.condition_name = _extract_function_name(condition) if condition is not None else condition_name + self._async_condition = async_condition + # Use async_condition name if provided, otherwise sync condition name + if async_condition is not None: + self.condition_name = _extract_function_name(async_condition) if condition_name is None else condition_name + elif condition is not None: + self.condition_name = _extract_function_name(condition) if condition_name is None else condition_name + else: + self.condition_name = condition_name @property def id(self) -> str: @@ -144,6 +162,15 @@ def id(self) -> str: """ return f"{self.source_id}{self.ID_SEPARATOR}{self.target_id}" + @property + def has_async_condition(self) -> bool: + """Check if this edge has an async state-aware condition. + + Returns True if the edge was configured with an async_condition that + requires shared state for evaluation. + """ + return self._async_condition is not None + def should_route(self, data: Any) -> bool: """Evaluate the edge predicate against an incoming payload. @@ -153,6 +180,9 @@ def should_route(self, data: Any) -> bool: this edge. Any exception raised by the callable is deliberately allowed to surface to the caller to avoid masking logic bugs. + Note: If the edge has an async_condition, this method returns True and + the actual routing decision must be made via should_route_async(). + Examples: .. code-block:: python @@ -160,10 +190,31 @@ def should_route(self, data: Any) -> bool: assert edge.should_route({"score": 0.9}) is True assert edge.should_route({"score": 0.4}) is False """ + # If there's an async condition, defer to should_route_async + if self._async_condition is not None: + return True # Let the async check handle it if self._condition is None: return True return self._condition(data) + async def should_route_async(self, data: Any, shared_state: Any) -> bool: + """Evaluate the async edge predicate against payload and shared state. + + This method is used for edges with async_condition that need access to + shared state for routing decisions (e.g., declarative workflow conditions). + + Args: + data: The message payload + shared_state: The workflow's shared state + + Returns: + True if the edge should be traversed, False otherwise. + """ + if self._async_condition is not None: + return bool(await self._async_condition(data, shared_state)) + # Fall back to sync condition + return self.should_route(data) + def to_dict(self) -> dict[str, Any]: """Produce a JSON-serialisable view of the edge metadata. @@ -445,17 +496,25 @@ def __init__( target_id: str, condition: Callable[[Any], bool] | None = None, *, + async_condition: Callable[[Any, Any], Any] | None = None, id: str | None = None, ) -> None: """Create a one-to-one edge group between two executors. + Args: + source_id: The source executor ID. + target_id: The target executor ID. + condition: Optional sync condition function (data) -> bool. + async_condition: Optional async condition function (data, shared_state) -> bool. + id: Optional explicit ID for the edge group. + Examples: .. code-block:: python group = SingleEdgeGroup("ingest", "validate") assert group.edges[0].source_id == "ingest" """ - edge = Edge(source_id=source_id, target_id=target_id, condition=condition) + edge = Edge(source_id=source_id, target_id=target_id, condition=condition, async_condition=async_condition) super().__init__([edge], id=id, type=self.__class__.__name__) diff --git a/python/packages/core/agent_framework/_workflows/_edge_runner.py b/python/packages/core/agent_framework/_workflows/_edge_runner.py index 0aa4139c48..3b1004ae84 100644 --- a/python/packages/core/agent_framework/_workflows/_edge_runner.py +++ b/python/packages/core/agent_framework/_workflows/_edge_runner.py @@ -112,7 +112,13 @@ async def send_message(self, message: Message, shared_state: SharedState, ctx: R return False if self._can_handle(self._edge.target_id, message): - if self._edge.should_route(message.data): + # Use async routing if edge has async condition, otherwise sync + if self._edge.has_async_condition: + route_result = await self._edge.should_route_async(message.data, shared_state) + else: + route_result = self._edge.should_route(message.data) + + if route_result: span.set_attributes({ OtelAttr.EDGE_GROUP_DELIVERED: True, OtelAttr.EDGE_GROUP_DELIVERY_STATUS: EdgeGroupDeliveryStatus.DELIVERED.value, @@ -162,8 +168,8 @@ def __init__(self, edge_group: FanOutEdgeGroup, executors: dict[str, Executor]) async def send_message(self, message: Message, shared_state: SharedState, ctx: RunnerContext) -> bool: """Send a message through all edges in the fan-out edge group.""" - deliverable_edges = [] - single_target_edge = None + deliverable_edges: list[Edge] = [] + single_target_edge: Edge | None = None # Process routing logic within span with create_edge_group_processing_span( self._edge_group.__class__.__name__, @@ -192,7 +198,13 @@ async def send_message(self, message: Message, shared_state: SharedState, ctx: R if message.target_id in selection_results: edge = self._target_map.get(message.target_id) if edge and self._can_handle(edge.target_id, message): - if edge.should_route(message.data): + # Use async routing if edge has async condition + if edge.has_async_condition: + route_result = await edge.should_route_async(message.data, shared_state) + else: + route_result = edge.should_route(message.data) + + if route_result: span.set_attributes({ OtelAttr.EDGE_GROUP_DELIVERED: True, OtelAttr.EDGE_GROUP_DELIVERY_STATUS: EdgeGroupDeliveryStatus.DELIVERED.value, @@ -223,8 +235,14 @@ async def send_message(self, message: Message, shared_state: SharedState, ctx: R # If no target ID, send the message to the selected targets for target_id in selection_results: edge = self._target_map[target_id] - if self._can_handle(edge.target_id, message) and edge.should_route(message.data): - deliverable_edges.append(edge) + if self._can_handle(edge.target_id, message): + # Use async routing if edge has async condition + if edge.has_async_condition: + route_result = await edge.should_route_async(message.data, shared_state) + else: + route_result = edge.should_route(message.data) + if route_result: + deliverable_edges.append(edge) if len(deliverable_edges) > 0: span.set_attributes({ diff --git a/python/packages/core/agent_framework/_workflows/_runner.py b/python/packages/core/agent_framework/_workflows/_runner.py index 8cc01c23cf..983b711b75 100644 --- a/python/packages/core/agent_framework/_workflows/_runner.py +++ b/python/packages/core/agent_framework/_workflows/_runner.py @@ -168,7 +168,7 @@ def _normalize_message_payload(message: Message) -> None: # Route all messages through normal workflow edges associated_edge_runners = self._edge_runner_map.get(source_executor_id, []) if not associated_edge_runners: - logger.warning(f"No outgoing edges found for executor {source_executor_id}; dropping messages.") + logger.debug("No outgoing edges found for executor %s; dropping messages.", source_executor_id) return for message in messages: diff --git a/python/packages/core/agent_framework/_workflows/_workflow_builder.py b/python/packages/core/agent_framework/_workflows/_workflow_builder.py index 70f8747ec9..72d2a5cb2b 100644 --- a/python/packages/core/agent_framework/_workflows/_workflow_builder.py +++ b/python/packages/core/agent_framework/_workflows/_workflow_builder.py @@ -225,6 +225,7 @@ def add_edge( source: Executor | AgentProtocol, target: Executor | AgentProtocol, condition: Callable[[Any], bool] | None = None, + async_condition: Callable[[Any, Any], Any] | None = None, ) -> Self: """Add a directed edge between two executors. @@ -236,6 +237,9 @@ def add_edge( target: The target executor of the edge. condition: An optional condition function that determines whether the edge should be traversed based on the message type. + async_condition: An optional async condition function that receives + (message_data, shared_state) and determines whether the edge + should be traversed. Takes precedence over condition when present. Returns: Self: The WorkflowBuilder instance for method chaining. @@ -282,7 +286,7 @@ def only_large_numbers(msg: int) -> bool: target_exec = self._maybe_wrap_agent(target) source_id = self._add_executor(source_exec) target_id = self._add_executor(target_exec) - self._edge_groups.append(SingleEdgeGroup(source_id, target_id, condition)) # type: ignore[call-arg] + self._edge_groups.append(SingleEdgeGroup(source_id, target_id, condition, async_condition=async_condition)) # type: ignore[call-arg] return self def add_fan_out_edges( diff --git a/python/packages/core/agent_framework/openai/_chat_client.py b/python/packages/core/agent_framework/openai/_chat_client.py index 73605fadef..62c9a4c958 100644 --- a/python/packages/core/agent_framework/openai/_chat_client.py +++ b/python/packages/core/agent_framework/openai/_chat_client.py @@ -378,8 +378,10 @@ def _openai_chat_message_parser(self, message: ChatMessage) -> list[dict[str, An args["tool_calls"] = [self._openai_content_parser(content)] # type: ignore case FunctionResultContent(): args["tool_call_id"] = content.call_id - if content.result is not None: - args["content"] = prepare_function_call_results(content.result) + # Always include content for tool results, even if None (API requires it) + args["content"] = ( + prepare_function_call_results(content.result) if content.result is not None else "" + ) case _: if "content" not in args: args["content"] = [] diff --git a/python/packages/declarative/agent_framework_declarative/__init__.py b/python/packages/declarative/agent_framework_declarative/__init__.py index bfc1bdffdc..c7594b58c7 100644 --- a/python/packages/declarative/agent_framework_declarative/__init__.py +++ b/python/packages/declarative/agent_framework_declarative/__init__.py @@ -3,10 +3,20 @@ from importlib import metadata from ._loader import AgentFactory, DeclarativeLoaderError, ProviderLookupError, ProviderTypeMapping +from ._workflows import DeclarativeWorkflowError, WorkflowFactory, WorkflowState try: __version__ = metadata.version(__name__) except metadata.PackageNotFoundError: __version__ = "0.0.0" # Fallback for development mode -__all__ = ["AgentFactory", "DeclarativeLoaderError", "ProviderLookupError", "ProviderTypeMapping", "__version__"] +__all__ = [ + "AgentFactory", + "DeclarativeLoaderError", + "DeclarativeWorkflowError", + "ProviderLookupError", + "ProviderTypeMapping", + "WorkflowFactory", + "WorkflowState", + "__version__", +] diff --git a/python/packages/declarative/agent_framework_declarative/_models.py b/python/packages/declarative/agent_framework_declarative/_models.py index aaba468bdf..bd33cb9212 100644 --- a/python/packages/declarative/agent_framework_declarative/_models.py +++ b/python/packages/declarative/agent_framework_declarative/_models.py @@ -11,7 +11,9 @@ from powerfx import Engine engine = Engine() -except ImportError: +except (ImportError, RuntimeError): + # ImportError: powerfx package not installed + # RuntimeError: .NET runtime not available or misconfigured engine = None if sys.version_info >= (3, 11): @@ -52,9 +54,9 @@ def _try_powerfx_eval(value: str | None, log_value: bool = True) -> str | None: return engine.eval(value[1:], symbols={"Env": dict(os.environ)}) except Exception as exc: if log_value: - logger.debug("PowerFx evaluation failed for value '%s': %s", value, exc) + logger.debug(f"PowerFx evaluation failed for value '{value}': {exc}") else: - logger.debug("PowerFx evaluation failed for value (first five characters shown) '%s': %s", value[:5], exc) + logger.debug(f"PowerFx evaluation failed for value (first five characters shown) '{value[:5]}': {exc}") return value diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/__init__.py b/python/packages/declarative/agent_framework_declarative/_workflows/__init__.py new file mode 100644 index 0000000000..62387032a7 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/__init__.py @@ -0,0 +1,30 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Declarative workflow support for agent-framework. + +This module provides the ability to create executable Workflow objects from YAML definitions, +enabling multi-agent orchestration patterns like Foreach, conditionals, and agent invocations. +""" + +from ._factory import DeclarativeWorkflowError, WorkflowFactory +from ._handlers import ActionHandler, action_handler, get_action_handler +from ._human_input import ( + ExternalInputRequest, + ExternalLoopEvent, + process_external_loop, + validate_input_response, +) +from ._state import WorkflowState + +__all__ = [ + "ActionHandler", + "DeclarativeWorkflowError", + "ExternalInputRequest", + "ExternalLoopEvent", + "WorkflowFactory", + "WorkflowState", + "action_handler", + "get_action_handler", + "process_external_loop", + "validate_input_response", +] diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_actions_agents.py b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_agents.py new file mode 100644 index 0000000000..48745d4886 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_agents.py @@ -0,0 +1,497 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Agent invocation action handlers for declarative workflows. + +This module implements handlers for: +- InvokeAzureAgent: Invoke a hosted Azure AI agent +- InvokePromptAgent: Invoke a local prompt-based agent +""" + +import json +from collections.abc import AsyncGenerator +from typing import Any, cast + +from agent_framework import get_logger +from agent_framework._types import ChatMessage + +from ._handlers import ( + ActionContext, + AgentResponseEvent, + AgentStreamingChunkEvent, + WorkflowEvent, + action_handler, +) +from ._human_input import ExternalInputRequest, ExternalLoopEvent + +logger = get_logger("agent_framework.declarative.workflows.actions") + + +def _build_messages_from_state(ctx: ActionContext) -> list[ChatMessage]: + """Build the message list to send to an agent. + + This collects messages from: + 1. Conversation history + 2. Current input (if first agent call) + 3. Additional context from instructions + + Args: + ctx: The action context + + Returns: + List of ChatMessage objects to send to the agent + """ + messages: list[ChatMessage] = [] + + # Get conversation history + history = ctx.state.get("conversation.messages", []) + if history: + messages.extend(history) + + return messages + + +def _extract_text_from_response(response: Any) -> str | None: + """Extract text content from an agent response. + + Args: + response: The agent response object + + Returns: + The text content, or None if not available + """ + # Handle various response types + if hasattr(response, "text"): + text_val = response.text + return str(text_val) if text_val is not None else None + if hasattr(response, "content"): + content = response.content + if isinstance(content, str): + return content + if hasattr(content, "text"): + content_text = content.text + return str(content_text) if content_text is not None else None + if isinstance(response, str): + return response + return None + + +@action_handler("InvokeAzureAgent") +async def handle_invoke_azure_agent(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Invoke a hosted Azure AI agent. + + Supports both Python-style and .NET-style YAML schemas: + + Python-style schema: + kind: InvokeAzureAgent + agent: agentName + input: =expression or literal input + outputPath: turn.response + + .NET-style schema: + kind: InvokeAzureAgent + agent: + name: AgentName + conversationId: =System.ConversationId + input: + arguments: + param1: value1 + messages: =expression + output: + messages: Local.Response + responseObject: Local.StructuredResponse + """ + # Get agent name - support both formats + agent_config: dict[str, Any] | str | None = ctx.action.get("agent") + agent_name: str | None = None + if isinstance(agent_config, dict): + agent_name = str(agent_config.get("name")) if agent_config.get("name") else None + # Support dynamic agent name from expression + if agent_name and isinstance(agent_name, str) and agent_name.startswith("="): + evaluated = ctx.state.eval_if_expression(agent_name) + agent_name = str(evaluated) if evaluated is not None else None + elif isinstance(agent_config, str): + agent_name = agent_config + + if not agent_name: + logger.warning("InvokeAzureAgent action missing 'agent' or 'agent.name' property") + return + + # Get input configuration + input_config: dict[str, Any] | Any = ctx.action.get("input", {}) + input_arguments: dict[str, Any] = {} + input_messages: Any = None + external_loop_when: str | None = None + if isinstance(input_config, dict): + input_config_typed = cast(dict[str, Any], input_config) + input_arguments = cast(dict[str, Any], input_config_typed.get("arguments") or {}) + input_messages = input_config_typed.get("messages") + # Extract external loop configuration + external_loop = input_config_typed.get("externalLoop") + if isinstance(external_loop, dict): + external_loop_typed = cast(dict[str, Any], external_loop) + external_loop_when = str(external_loop_typed.get("when")) if external_loop_typed.get("when") else None + else: + input_messages = input_config # Treat as message directly + + # Get output configuration (.NET style) + output_config: dict[str, Any] | Any = ctx.action.get("output", {}) + output_messages_var: str | None = None + output_response_obj_var: str | None = None + if isinstance(output_config, dict): + output_config_typed = cast(dict[str, Any], output_config) + output_messages_var = str(output_config_typed.get("messages")) if output_config_typed.get("messages") else None + output_response_obj_var = ( + str(output_config_typed.get("responseObject")) if output_config_typed.get("responseObject") else None + ) + # auto_send is defined but not used currently + _auto_send: bool = bool(output_config_typed.get("autoSend", True)) + + # Legacy Python style output path + output_path = ctx.action.get("outputPath") + + # Other properties + conversation_id = ctx.action.get("conversationId") + instructions = ctx.action.get("instructions") + tools_config: list[dict[str, Any]] = ctx.action.get("tools", []) + + # Get the agent from registry + agent = ctx.agents.get(agent_name) + if agent is None: + logger.error(f"InvokeAzureAgent: agent '{agent_name}' not found in registry") + return + + # Evaluate conversation ID + if conversation_id: + evaluated_conv_id = ctx.state.eval_if_expression(conversation_id) + ctx.state.set("system.ConversationId", evaluated_conv_id) + + # Evaluate instructions (unused currently but may be used for prompting) + _ = ctx.state.eval_if_expression(instructions) if instructions else None + + # Build messages + messages = _build_messages_from_state(ctx) + + # Handle input messages from .NET style + if input_messages: + evaluated_input = ctx.state.eval_if_expression(input_messages) + if evaluated_input: + if isinstance(evaluated_input, str): + messages.append(ChatMessage(role="user", text=evaluated_input)) + elif isinstance(evaluated_input, list): + for msg_item in evaluated_input: + if isinstance(msg_item, str): + messages.append(ChatMessage(role="user", text=msg_item)) + elif isinstance(msg_item, ChatMessage): + messages.append(msg_item) + elif isinstance(msg_item, dict) and "content" in msg_item: + item_dict = cast(dict[str, Any], msg_item) + role: str = str(item_dict.get("role", "user")) + content: str = str(item_dict.get("content", "")) + if role == "user": + messages.append(ChatMessage(role="user", text=content)) + elif role == "assistant": + messages.append(ChatMessage(role="assistant", text=content)) + elif role == "system": + messages.append(ChatMessage(role="system", text=content)) + + # Evaluate and include input arguments + evaluated_args: dict[str, Any] = {} + for arg_key, arg_value in input_arguments.items(): + evaluated_args[arg_key] = ctx.state.eval_if_expression(arg_value) + + # Prepare tool bindings + tool_bindings: dict[str, dict[str, Any]] = {} + for tool_config in tools_config: + tool_name: str | None = str(tool_config.get("name")) if tool_config.get("name") else None + bindings: list[dict[str, Any]] = list(tool_config.get("bindings", [])) # type: ignore[arg-type] + if tool_name and bindings: + tool_bindings[tool_name] = { + str(b.get("name")): ctx.state.eval_if_expression(b.get("input")) for b in bindings if b.get("name") + } + + logger.debug(f"InvokeAzureAgent: calling '{agent_name}' with {len(messages)} messages") + + # External loop iteration counter + iteration = 0 + max_iterations = 100 # Safety limit + + # Start external loop if configured + while True: + # Invoke the agent + try: + # Check if agent supports streaming + if hasattr(agent, "run_stream"): + full_text = "" + all_messages: list[Any] = [] + tool_calls: list[Any] = [] + + async for chunk in agent.run_stream(messages): + # Handle different chunk types + if hasattr(chunk, "text") and chunk.text: + full_text += chunk.text + yield AgentStreamingChunkEvent( + agent_name=str(agent_name), + chunk=chunk.text, + ) + + # Collect messages and tool calls + if hasattr(chunk, "messages"): + all_messages.extend(chunk.messages) + if hasattr(chunk, "tool_calls"): + tool_calls.extend(chunk.tool_calls) + + # Update state with result + ctx.state.set_agent_result( + text=full_text, + messages=all_messages, + tool_calls=tool_calls if tool_calls else None, + ) + + # Add to conversation history + if full_text: + ctx.state.add_conversation_message(ChatMessage(role="assistant", text=full_text)) + + # Store in output variables (.NET style) + if output_messages_var: + output_path_mapped = _map_variable_to_path(output_messages_var) + ctx.state.set(output_path_mapped, all_messages if all_messages else full_text) + + if output_response_obj_var: + output_path_mapped = _map_variable_to_path(output_response_obj_var) + # Try to parse as JSON if it looks like structured output + try: + parsed = json.loads(full_text) if full_text else None + ctx.state.set(output_path_mapped, parsed) + except (json.JSONDecodeError, TypeError): + ctx.state.set(output_path_mapped, full_text) + + # Store in output path (Python style) + if output_path: + ctx.state.set(output_path, full_text) + + yield AgentResponseEvent( + agent_name=str(agent_name), + text=full_text, + messages=all_messages, + tool_calls=tool_calls if tool_calls else None, + ) + + elif hasattr(agent, "run"): + # Non-streaming invocation + response = await agent.run(messages) + + text = _extract_text_from_response(response) + response_messages_attr = getattr(response, "messages", None) + response_messages: list[Any] = [] + if response_messages_attr: + response_messages = list(response_messages_attr) + response_tool_calls: list[Any] | None = getattr(response, "tool_calls", None) + + # Update state with result + ctx.state.set_agent_result( + text=text, + messages=response_messages, + tool_calls=response_tool_calls, + ) + + # Add to conversation history + if text: + ctx.state.add_conversation_message(ChatMessage(role="assistant", text=text)) + + # Store in output variables (.NET style) + if output_messages_var: + output_path_mapped = _map_variable_to_path(output_messages_var) + ctx.state.set(output_path_mapped, response_messages if response_messages else text) + + if output_response_obj_var: + output_path_mapped = _map_variable_to_path(output_response_obj_var) + try: + parsed = json.loads(text) if text else None + ctx.state.set(output_path_mapped, parsed) + except (json.JSONDecodeError, TypeError): + ctx.state.set(output_path_mapped, text) + + # Store in output path (Python style) + if output_path: + ctx.state.set(output_path, text) + + yield AgentResponseEvent( + agent_name=str(agent_name), + text=text, + messages=response_messages, + tool_calls=response_tool_calls, + ) + else: + logger.error(f"InvokeAzureAgent: agent '{agent_name}' has no run or run_stream method") + break + + except Exception as e: + logger.error(f"InvokeAzureAgent: error invoking agent '{agent_name}': {e}") + raise + + # Check external loop condition + if external_loop_when: + # Evaluate the loop condition + should_continue = ctx.state.eval(external_loop_when) + should_continue = bool(should_continue) if should_continue is not None else False + + logger.debug( + f"InvokeAzureAgent: external loop condition '{str(external_loop_when)[:50]}' = " + f"{should_continue} (iteration {iteration})" + ) + + if should_continue and iteration < max_iterations: + # Emit event to signal waiting for external input + action_id: str = str(ctx.action.get("id", f"agent_{agent_name}")) + yield ExternalLoopEvent( + action_id=action_id, + iteration=iteration, + condition_expression=str(external_loop_when), + ) + + # The workflow executor should: + # 1. Pause execution + # 2. Wait for external input + # 3. Update state with input + # 4. Resume this generator + + # For now, we request input via ExternalInputRequest + yield ExternalInputRequest( + request_id=f"{action_id}_input_{iteration}", + prompt="Waiting for user input...", + variable="turn.userInput", + ) + + iteration += 1 + + # Clear messages for next iteration (start fresh with conversation) + messages = _build_messages_from_state(ctx) + continue + elif iteration >= max_iterations: + logger.warning(f"InvokeAzureAgent: external loop exceeded max iterations ({max_iterations})") + + # No external loop or condition is false - exit + break + + +def _map_variable_to_path(variable: str) -> str: + """Map .NET-style variable names to state paths. + + Args: + variable: Variable name like 'Local.X' or 'System.ConversationId' + + Returns: + State path like 'turn.X' or 'system.ConversationId' + """ + if variable.startswith("Local."): + return "turn." + variable[6:] + if variable.startswith("System."): + return "system." + variable[7:] + if variable.startswith("Workflow."): + return "workflow." + variable[9:] + if "." in variable: + return variable + return "turn." + variable + + +@action_handler("InvokePromptAgent") +async def handle_invoke_prompt_agent(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Invoke a local prompt-based agent (similar to InvokeAzureAgent but for local agents). + + Action schema: + kind: InvokePromptAgent + agent: agentName # name of the agent in the agents registry + input: =expression or literal input + instructions: =expression or literal prompt/instructions + outputPath: turn.response # optional path to store result + """ + # Implementation is similar to InvokeAzureAgent + # The difference is primarily in how the agent is configured + agent_name_raw = ctx.action.get("agent") + if not isinstance(agent_name_raw, str): + logger.warning("InvokePromptAgent action missing 'agent' property") + return + agent_name: str = agent_name_raw + input_expr = ctx.action.get("input") + instructions = ctx.action.get("instructions") + output_path = ctx.action.get("outputPath") + + # Get the agent from registry + agent = ctx.agents.get(agent_name) + if agent is None: + logger.error(f"InvokePromptAgent: agent '{agent_name}' not found in registry") + return + + # Evaluate input + input_value = ctx.state.eval_if_expression(input_expr) if input_expr else None + + # Evaluate instructions (unused currently but may be used for prompting) + _ = ctx.state.eval_if_expression(instructions) if instructions else None + + # Build messages + messages = _build_messages_from_state(ctx) + + # Add input as user message if provided + if input_value: + if isinstance(input_value, str): + messages.append(ChatMessage(role="user", text=input_value)) + elif isinstance(input_value, ChatMessage): + messages.append(input_value) + + logger.debug(f"InvokePromptAgent: calling '{agent_name}' with {len(messages)} messages") + + # Invoke the agent + try: + if hasattr(agent, "run_stream"): + full_text = "" + all_messages: list[Any] = [] + + async for chunk in agent.run_stream(messages): + if hasattr(chunk, "text") and chunk.text: + full_text += chunk.text + yield AgentStreamingChunkEvent( + agent_name=agent_name, + chunk=chunk.text, + ) + + if hasattr(chunk, "messages"): + all_messages.extend(chunk.messages) + + ctx.state.set_agent_result(text=full_text, messages=all_messages) + + if full_text: + ctx.state.add_conversation_message(ChatMessage(role="assistant", text=full_text)) + + if output_path: + ctx.state.set(output_path, full_text) + + yield AgentResponseEvent( + agent_name=agent_name, + text=full_text, + messages=all_messages, + ) + + elif hasattr(agent, "run"): + response = await agent.run(messages) + text = _extract_text_from_response(response) + response_messages = getattr(response, "messages", []) + + ctx.state.set_agent_result(text=text, messages=response_messages) + + if text: + ctx.state.add_conversation_message(ChatMessage(role="assistant", text=text)) + + if output_path: + ctx.state.set(output_path, text) + + yield AgentResponseEvent( + agent_name=agent_name, + text=text, + messages=response_messages, + ) + else: + logger.error(f"InvokePromptAgent: agent '{agent_name}' has no run or run_stream method") + + except Exception as e: + logger.error(f"InvokePromptAgent: error invoking agent '{agent_name}': {e}") + raise diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_actions_basic.py b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_basic.py new file mode 100644 index 0000000000..0d942fdd1d --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_basic.py @@ -0,0 +1,575 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Basic action handlers for variable manipulation and output. + +This module implements handlers for: +- SetValue: Set a variable in the workflow state +- AppendValue: Append a value to a list variable +- SendActivity: Send text or attachments to the user +- EmitEvent: Emit a custom workflow event + +Note: All handlers are defined as async generators (AsyncGenerator[WorkflowEvent, None]) +for consistency with the ActionHandler protocol, even when they don't perform async +operations. This uniform interface allows the workflow executor to consume all handlers +the same way, and some handlers (like InvokeAzureAgent) genuinely require async for +network calls. The `return; yield` pattern makes a function an async generator without +actually yielding any events. +""" + +from collections.abc import AsyncGenerator +from typing import TYPE_CHECKING, Any, cast + +from agent_framework import get_logger + +from ._handlers import ( + ActionContext, + AttachmentOutputEvent, + CustomEvent, + TextOutputEvent, + WorkflowEvent, + action_handler, +) + +if TYPE_CHECKING: + from ._state import WorkflowState + +logger = get_logger("agent_framework.declarative.workflows.actions") + + +@action_handler("SetValue") +async def handle_set_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Set a value in the workflow state. + + Action schema: + kind: SetValue + path: turn.variableName # or workflow.outputs.result + value: =expression or literal value + """ + path = ctx.action.get("path") + value = ctx.action.get("value") + + if not path: + logger.warning("SetValue action missing 'path' property") + return + + # Evaluate the value if it's an expression + evaluated_value = ctx.state.eval_if_expression(value) + + logger.debug(f"SetValue: {path} = {evaluated_value}") + ctx.state.set(path, evaluated_value) + + return + yield # Make it a generator + + +@action_handler("SetVariable") +async def handle_set_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Set a variable in the workflow state (.NET workflow format). + + This is an alias for SetValue with 'variable' instead of 'path'. + + Action schema: + kind: SetVariable + variable: Local.variableName + value: =expression or literal value + """ + variable = ctx.action.get("variable") + value = ctx.action.get("value") + + if not variable: + logger.warning("SetVariable action missing 'variable' property") + return + + # Evaluate the value if it's an expression + evaluated_value = ctx.state.eval_if_expression(value) + + # Map .NET-style variable names to Python state paths + # Local.X -> turn.X, System.X -> system.X, etc. + path = _map_variable_name_to_path(variable) + + logger.debug(f"SetVariable: {variable} ({path}) = {evaluated_value}") + ctx.state.set(path, evaluated_value) + + return + yield # Make it a generator + + +def _map_variable_name_to_path(variable: str) -> str: + """Map .NET-style variable names to state paths. + + Args: + variable: Variable name like 'Local.X' or 'System.ConversationId' + + Returns: + State path like 'turn.X' or 'system.ConversationId' + """ + if variable.startswith("Local."): + return "turn." + variable[6:] + if variable.startswith("System."): + return "system." + variable[7:] + if variable.startswith("Workflow."): + return "workflow." + variable[9:] + if "." in variable: + # Already has a namespace + return variable + # Default to turn scope + return "turn." + variable + + +@action_handler("AppendValue") +async def handle_append_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Append a value to a list in the workflow state. + + Action schema: + kind: AppendValue + path: turn.results + value: =expression or literal value + """ + path = ctx.action.get("path") + value = ctx.action.get("value") + + if not path: + logger.warning("AppendValue action missing 'path' property") + return + + # Evaluate the value if it's an expression + evaluated_value = ctx.state.eval_if_expression(value) + + logger.debug(f"AppendValue: {path} += {evaluated_value}") + ctx.state.append(path, evaluated_value) + + return + yield # Make it a generator + + +@action_handler("SendActivity") +async def handle_send_activity(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Send text or attachments to the user. + + Action schema (object form): + kind: SendActivity + activity: + text: =expression or literal text + attachments: + - content: ... + contentType: text/plain + + Action schema (simple form): + kind: SendActivity + activity: =expression or literal text + """ + activity = ctx.action.get("activity", {}) + + # Handle simple string form + if isinstance(activity, str): + evaluated_text = ctx.state.eval_if_expression(activity) + if evaluated_text: + logger.debug( + "SendActivity: text = %s", evaluated_text[:100] if len(str(evaluated_text)) > 100 else evaluated_text + ) + yield TextOutputEvent(text=str(evaluated_text)) + return + + # Handle object form - text output + text = activity.get("text") + if text: + evaluated_text = ctx.state.eval_if_expression(text) + if evaluated_text: + logger.debug( + "SendActivity: text = %s", evaluated_text[:100] if len(str(evaluated_text)) > 100 else evaluated_text + ) + yield TextOutputEvent(text=str(evaluated_text)) + + # Handle attachments + attachments = activity.get("attachments", []) + for attachment in attachments: + content = attachment.get("content") + content_type = attachment.get("contentType", "application/octet-stream") + + if content: + evaluated_content = ctx.state.eval_if_expression(content) + logger.debug(f"SendActivity: attachment type={content_type}") + yield AttachmentOutputEvent(content=evaluated_content, content_type=content_type) + + +@action_handler("EmitEvent") +async def handle_emit_event(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Emit a custom workflow event. + + Action schema: + kind: EmitEvent + event: + name: eventName + data: =expression or literal data + """ + event_def = ctx.action.get("event", {}) + name = event_def.get("name") + data = event_def.get("data") + + if not name: + logger.warning("EmitEvent action missing 'event.name' property") + return + + # Evaluate data if it's an expression + evaluated_data = ctx.state.eval_if_expression(data) + + logger.debug(f"EmitEvent: {name} = {evaluated_data}") + yield CustomEvent(name=name, data=evaluated_data) + + +def _evaluate_dict_values(d: dict[str, Any], state: "WorkflowState") -> dict[str, Any]: + """Recursively evaluate PowerFx expressions in a dictionary. + + Args: + d: Dictionary that may contain expression values + state: The workflow state for expression evaluation + + Returns: + Dictionary with all expressions evaluated + """ + result: dict[str, Any] = {} + for key, value in d.items(): + if isinstance(value, str): + result[key] = state.eval_if_expression(value) + elif isinstance(value, dict): + result[key] = _evaluate_dict_values(cast(dict[str, Any], value), state) + elif isinstance(value, list): + evaluated_list: list[Any] = [] + for list_item in value: + if isinstance(list_item, dict): + evaluated_list.append(_evaluate_dict_values(cast(dict[str, Any], list_item), state)) + elif isinstance(list_item, str): + evaluated_list.append(state.eval_if_expression(list_item)) + else: + evaluated_list.append(list_item) + result[key] = evaluated_list + else: + result[key] = value + return result + + +@action_handler("SetTextVariable") +async def handle_set_text_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Set a text variable with string interpolation support. + + This is similar to SetVariable but supports multi-line text with + {Local.Variable} style interpolation. + + Action schema: + kind: SetTextVariable + variable: Local.myText + value: |- + Multi-line text with {Local.Variable} interpolation + and more content here. + """ + variable = ctx.action.get("variable") + value = ctx.action.get("value") + + if not variable: + logger.warning("SetTextVariable action missing 'variable' property") + return + + # Evaluate the value - handle string interpolation + if isinstance(value, str): + evaluated_value = _interpolate_string(value, ctx.state) + else: + evaluated_value = ctx.state.eval_if_expression(value) + + path = _map_variable_name_to_path(variable) + + logger.debug(f"SetTextVariable: {variable} ({path}) = {str(evaluated_value)[:100]}") + ctx.state.set(path, evaluated_value) + + return + yield # Make it a generator + + +@action_handler("SetMultipleVariables") +async def handle_set_multiple_variables(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Set multiple variables at once. + + Action schema: + kind: SetMultipleVariables + variables: + - variable: Local.var1 + value: value1 + - variable: Local.var2 + value: =expression + """ + variables = ctx.action.get("variables", []) + + for var_def in variables: + variable = var_def.get("variable") + value = var_def.get("value") + + if not variable: + logger.warning("SetMultipleVariables: variable entry missing 'variable' property") + continue + + evaluated_value = ctx.state.eval_if_expression(value) + path = _map_variable_name_to_path(variable) + + logger.debug(f"SetMultipleVariables: {variable} ({path}) = {evaluated_value}") + ctx.state.set(path, evaluated_value) + + return + yield # Make it a generator + + +@action_handler("ResetVariable") +async def handle_reset_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Reset a variable to its default/blank state. + + Action schema: + kind: ResetVariable + variable: Local.variableName + """ + variable = ctx.action.get("variable") + + if not variable: + logger.warning("ResetVariable action missing 'variable' property") + return + + path = _map_variable_name_to_path(variable) + + logger.debug(f"ResetVariable: {variable} ({path}) = None") + ctx.state.set(path, None) + + return + yield # Make it a generator + + +@action_handler("ClearAllVariables") +async def handle_clear_all_variables(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Clear all turn-scoped variables. + + Action schema: + kind: ClearAllVariables + """ + logger.debug("ClearAllVariables: clearing turn scope") + ctx.state.reset_turn() + + return + yield # Make it a generator + + +@action_handler("CreateConversation") +async def handle_create_conversation(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Create a new conversation context. + + Action schema (.NET style): + kind: CreateConversation + conversationId: Local.myConversationId # Variable to store the generated ID + + The conversationId parameter is the OUTPUT variable where the generated + conversation ID will be stored. This matches .NET behavior where: + - A unique conversation ID is always auto-generated + - The conversationId parameter specifies where to store it + """ + import uuid + + conversation_id_var = ctx.action.get("conversationId") + + # Always generate a unique ID (.NET behavior) + generated_id = str(uuid.uuid4()) + + # Store conversation in state + conversations: dict[str, Any] = ctx.state.get("system.conversations") or {} + conversations[generated_id] = { + "id": generated_id, + "messages": [], + "created_at": None, # Could add timestamp + } + ctx.state.set("system.conversations", conversations) + + logger.debug(f"CreateConversation: created {generated_id}") + + # Store the generated ID in the specified variable (.NET style output binding) + if conversation_id_var: + output_path = _map_variable_name_to_path(conversation_id_var) + ctx.state.set(output_path, generated_id) + logger.debug(f"CreateConversation: bound to {output_path} = {generated_id}") + + # Also handle legacy output binding for backwards compatibility + output = ctx.action.get("output", {}) + output_var = output.get("conversationId") + if output_var: + output_path = _map_variable_name_to_path(output_var) + ctx.state.set(output_path, generated_id) + logger.debug(f"CreateConversation: legacy output bound to {output_path}") + + return + yield # Make it a generator + + +@action_handler("AddConversationMessage") +async def handle_add_conversation_message(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Add a message to a conversation. + + Action schema: + kind: AddConversationMessage + conversationId: =expression or variable reference + message: + role: user | assistant | system + content: =expression or literal text + """ + conversation_id = ctx.action.get("conversationId") + message_def = ctx.action.get("message", {}) + + if not conversation_id: + logger.warning("AddConversationMessage missing 'conversationId' property") + return + + # Evaluate conversation ID + evaluated_id = ctx.state.eval_if_expression(conversation_id) + + # Evaluate message content + role = message_def.get("role", "user") + content = message_def.get("content", "") + + evaluated_content = ctx.state.eval_if_expression(content) + if isinstance(evaluated_content, str): + evaluated_content = _interpolate_string(evaluated_content, ctx.state) + + # Get or create conversation + conversations: dict[str, Any] = ctx.state.get("system.conversations") or {} + if evaluated_id not in conversations: + conversations[evaluated_id] = {"id": evaluated_id, "messages": []} + + # Add message + message: dict[str, Any] = {"role": role, "content": evaluated_content} + conv_entry: dict[str, Any] = dict(conversations[evaluated_id]) + messages_list: list[Any] = list(conv_entry.get("messages", [])) + messages_list.append(message) + conv_entry["messages"] = messages_list + conversations[evaluated_id] = conv_entry + ctx.state.set("system.conversations", conversations) + + # Also add to global conversation state + ctx.state.add_conversation_message(message) + + logger.debug(f"AddConversationMessage: added {role} message to {evaluated_id}") + + return + yield # Make it a generator + + +@action_handler("CopyConversationMessages") +async def handle_copy_conversation_messages(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Copy messages from one conversation to another. + + Action schema: + kind: CopyConversationMessages + sourceConversationId: =expression + targetConversationId: =expression + count: 10 # optional, number of messages to copy + """ + source_id = ctx.action.get("sourceConversationId") + target_id = ctx.action.get("targetConversationId") + count = ctx.action.get("count") + + if not source_id or not target_id: + logger.warning("CopyConversationMessages missing source or target conversation ID") + return + + # Evaluate IDs + evaluated_source = ctx.state.eval_if_expression(source_id) + evaluated_target = ctx.state.eval_if_expression(target_id) + + # Get conversations + conversations: dict[str, Any] = ctx.state.get("system.conversations") or {} + + source_conv: dict[str, Any] = conversations.get(evaluated_source, {}) + source_messages: list[Any] = source_conv.get("messages", []) + + # Limit messages if count specified + if count is not None: + source_messages = source_messages[-count:] + + # Get or create target conversation + if evaluated_target not in conversations: + conversations[evaluated_target] = {"id": evaluated_target, "messages": []} + + # Copy messages + target_entry: dict[str, Any] = dict(conversations[evaluated_target]) + target_messages: list[Any] = list(target_entry.get("messages", [])) + target_messages.extend(source_messages) + target_entry["messages"] = target_messages + conversations[evaluated_target] = target_entry + ctx.state.set("system.conversations", conversations) + + logger.debug( + "CopyConversationMessages: copied %d messages from %s to %s", + len(source_messages), + evaluated_source, + evaluated_target, + ) + + return + yield # Make it a generator + + +@action_handler("RetrieveConversationMessages") +async def handle_retrieve_conversation_messages(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Retrieve messages from a conversation and store in a variable. + + Action schema: + kind: RetrieveConversationMessages + conversationId: =expression + output: + messages: Local.myMessages + count: 10 # optional + """ + conversation_id = ctx.action.get("conversationId") + output = ctx.action.get("output", {}) + count = ctx.action.get("count") + + if not conversation_id: + logger.warning("RetrieveConversationMessages missing 'conversationId' property") + return + + # Evaluate conversation ID + evaluated_id = ctx.state.eval_if_expression(conversation_id) + + # Get messages + conversations: dict[str, Any] = ctx.state.get("system.conversations") or {} + conv: dict[str, Any] = conversations.get(evaluated_id, {}) + messages: list[Any] = conv.get("messages", []) + + # Limit messages if count specified + if count is not None: + messages = messages[-count:] + + # Handle output binding + output_var = output.get("messages") + if output_var: + output_path = _map_variable_name_to_path(output_var) + ctx.state.set(output_path, messages) + logger.debug(f"RetrieveConversationMessages: bound {len(messages)} messages to {output_path}") + + return + yield # Make it a generator + + +def _interpolate_string(text: str, state: "WorkflowState") -> str: + """Interpolate {Variable.Path} references in a string. + + Args: + text: Text that may contain {Variable.Path} references + state: The workflow state for variable lookup + + Returns: + Text with variables interpolated + """ + import re + + def replace_var(match: re.Match[str]) -> str: + var_path: str = match.group(1) + # Map .NET style to Python style + path = _map_variable_name_to_path(var_path) + value = state.get(path) + return str(value) if value is not None else "" + + # Match {Variable.Path} patterns + pattern = r"\{([A-Za-z][A-Za-z0-9_.]*)\}" + return re.sub(pattern, replace_var, text) diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_actions_control_flow.py b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_control_flow.py new file mode 100644 index 0000000000..e7bc6597bc --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_control_flow.py @@ -0,0 +1,397 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Control flow action handlers for declarative workflows. + +This module implements handlers for: +- Foreach: Iterate over a collection and execute nested actions +- If: Conditional branching +- Switch: Multi-way branching based on value matching +- RepeatUntil: Loop until a condition is met +- BreakLoop: Exit the current loop +- ContinueLoop: Skip to the next iteration +""" + +from collections.abc import AsyncGenerator + +from agent_framework import get_logger + +from ._handlers import ( + ActionContext, + LoopControlSignal, + WorkflowEvent, + action_handler, +) + +logger = get_logger("agent_framework.declarative.workflows.actions") + + +@action_handler("Foreach") +async def handle_foreach(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Iterate over a collection and execute nested actions for each item. + + Action schema: + kind: Foreach + source: =expression returning a collection + itemName: itemVariable # optional, defaults to 'item' + indexName: indexVariable # optional, defaults to 'index' + actions: + - kind: ... + """ + source_expr = ctx.action.get("source") + item_name = ctx.action.get("itemName", "item") + index_name = ctx.action.get("indexName", "index") + actions = ctx.action.get("actions", []) + + if not source_expr: + logger.warning("Foreach action missing 'source' property") + return + + # Evaluate the source collection + collection = ctx.state.eval_if_expression(source_expr) + + if collection is None: + logger.debug("Foreach: source evaluated to None, skipping") + return + + if not hasattr(collection, "__iter__"): + logger.warning(f"Foreach: source is not iterable: {type(collection).__name__}") + return + + collection_len = len(list(collection)) if hasattr(collection, "__len__") else "?" + logger.debug(f"Foreach: iterating over {collection_len} items") + + # Iterate over the collection + for index, item in enumerate(collection): + # Set loop variables in the turn scope + ctx.state.set(f"turn.{item_name}", item) + ctx.state.set(f"turn.{index_name}", index) + + # Execute nested actions + try: + async for event in ctx.execute_actions(actions, ctx.state): + # Check for loop control signals + if isinstance(event, LoopControlSignal): + if event.signal_type == "break": + logger.debug(f"Foreach: break signal received at index {index}") + return + elif event.signal_type == "continue": + logger.debug(f"Foreach: continue signal received at index {index}") + break # Break inner loop to continue outer + else: + yield event + except StopIteration: + # Continue signal was raised + continue + + +@action_handler("If") +async def handle_if(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Conditional branching based on a condition expression. + + Action schema: + kind: If + condition: =boolean expression + then: + - kind: ... # actions if condition is true + else: + - kind: ... # actions if condition is false (optional) + """ + condition_expr = ctx.action.get("condition") + then_actions = ctx.action.get("then", []) + else_actions = ctx.action.get("else", []) + + if condition_expr is None: + logger.warning("If action missing 'condition' property") + return + + # Evaluate the condition + condition_result = ctx.state.eval_if_expression(condition_expr) + + # Coerce to boolean + is_truthy = bool(condition_result) + + logger.debug( + "If: condition '%s' evaluated to %s", + condition_expr[:50] if len(str(condition_expr)) > 50 else condition_expr, + is_truthy, + ) + + # Execute the appropriate branch + actions_to_execute = then_actions if is_truthy else else_actions + + async for event in ctx.execute_actions(actions_to_execute, ctx.state): + yield event + + +@action_handler("Switch") +async def handle_switch(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Multi-way branching based on value matching. + + Action schema: + kind: Switch + value: =expression to match + cases: + - match: value1 + actions: + - kind: ... + - match: value2 + actions: + - kind: ... + default: + - kind: ... # optional default actions + """ + value_expr = ctx.action.get("value") + cases = ctx.action.get("cases", []) + default_actions = ctx.action.get("default", []) + + if not value_expr: + logger.warning("Switch action missing 'value' property") + return + + # Evaluate the switch value + switch_value = ctx.state.eval_if_expression(value_expr) + + logger.debug(f"Switch: value = {switch_value}") + + # Find matching case + matched_actions = None + for case in cases: + match_value = ctx.state.eval_if_expression(case.get("match")) + if switch_value == match_value: + matched_actions = case.get("actions", []) + logger.debug(f"Switch: matched case '{match_value}'") + break + + # Use default if no match found + if matched_actions is None: + matched_actions = default_actions + logger.debug("Switch: using default case") + + # Execute matched actions + async for event in ctx.execute_actions(matched_actions, ctx.state): + yield event + + +@action_handler("RepeatUntil") +async def handle_repeat_until(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Loop until a condition becomes true. + + Action schema: + kind: RepeatUntil + condition: =boolean expression (loop exits when true) + maxIterations: 100 # optional safety limit + actions: + - kind: ... + """ + condition_expr = ctx.action.get("condition") + max_iterations = ctx.action.get("maxIterations", 100) + actions = ctx.action.get("actions", []) + + if condition_expr is None: + logger.warning("RepeatUntil action missing 'condition' property") + return + + iteration = 0 + while iteration < max_iterations: + iteration += 1 + ctx.state.set("turn.iteration", iteration) + + logger.debug(f"RepeatUntil: iteration {iteration}") + + # Execute loop body + should_break = False + async for event in ctx.execute_actions(actions, ctx.state): + if isinstance(event, LoopControlSignal): + if event.signal_type == "break": + logger.debug(f"RepeatUntil: break signal received at iteration {iteration}") + should_break = True + break + elif event.signal_type == "continue": + logger.debug(f"RepeatUntil: continue signal received at iteration {iteration}") + break + else: + yield event + + if should_break: + break + + # Check exit condition + condition_result = ctx.state.eval_if_expression(condition_expr) + if bool(condition_result): + logger.debug(f"RepeatUntil: condition met after {iteration} iterations") + break + + if iteration >= max_iterations: + logger.warning(f"RepeatUntil: reached max iterations ({max_iterations})") + + +@action_handler("BreakLoop") +async def handle_break_loop(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Signal to break out of the current loop. + + Action schema: + kind: BreakLoop + """ + logger.debug("BreakLoop: signaling break") + yield LoopControlSignal(signal_type="break") + + +@action_handler("ContinueLoop") +async def handle_continue_loop(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Signal to continue to the next iteration of the current loop. + + Action schema: + kind: ContinueLoop + """ + logger.debug("ContinueLoop: signaling continue") + yield LoopControlSignal(signal_type="continue") + + +@action_handler("ConditionGroup") +async def handle_condition_group(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Multi-condition branching (like else-if chains). + + Evaluates conditions in order and executes the first matching condition's actions. + If no conditions match and elseActions is provided, executes those. + + Action schema: + kind: ConditionGroup + conditions: + - condition: =boolean expression + actions: + - kind: ... + - condition: =another expression + actions: + - kind: ... + elseActions: + - kind: ... # optional, executed if no conditions match + """ + conditions = ctx.action.get("conditions", []) + else_actions = ctx.action.get("elseActions", []) + + matched = False + for condition_def in conditions: + condition_expr = condition_def.get("condition") + actions = condition_def.get("actions", []) + + if condition_expr is None: + logger.warning("ConditionGroup condition missing 'condition' property") + continue + + # Evaluate the condition + condition_result = ctx.state.eval_if_expression(condition_expr) + is_truthy = bool(condition_result) + + logger.debug( + "ConditionGroup: condition '%s' evaluated to %s", + str(condition_expr)[:50] if len(str(condition_expr)) > 50 else condition_expr, + is_truthy, + ) + + if is_truthy: + matched = True + # Execute this condition's actions + async for event in ctx.execute_actions(actions, ctx.state): + yield event + # Only execute the first matching condition + break + + # Execute elseActions if no condition matched + if not matched and else_actions: + logger.debug("ConditionGroup: no conditions matched, executing elseActions") + async for event in ctx.execute_actions(else_actions, ctx.state): + yield event + + +@action_handler("GotoAction") +async def handle_goto_action(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Jump to another action by ID (triggers re-execution from that action). + + Note: GotoAction in the .NET implementation creates a loop by restarting + execution from a specific action. In Python, we emit a GotoSignal that + the top-level executor should handle. + + Action schema: + kind: GotoAction + actionId: target_action_id + """ + action_id = ctx.action.get("actionId") + + if not action_id: + logger.warning("GotoAction missing 'actionId' property") + return + + logger.debug(f"GotoAction: jumping to action '{action_id}'") + + # Emit a goto signal that the executor should handle + yield GotoSignal(target_action_id=action_id) + + +class GotoSignal(WorkflowEvent): + """Signal to jump to a specific action by ID. + + This signal is used by GotoAction to implement control flow jumps. + The top-level executor should handle this signal appropriately. + """ + + def __init__(self, target_action_id: str) -> None: + self.target_action_id = target_action_id + + +class EndWorkflowSignal(WorkflowEvent): + """Signal to end the workflow execution. + + This signal causes the workflow to terminate gracefully. + """ + + def __init__(self, reason: str | None = None) -> None: + self.reason = reason + + +class EndConversationSignal(WorkflowEvent): + """Signal to end the current conversation. + + This signal causes the conversation to terminate while the workflow may continue. + """ + + def __init__(self, conversation_id: str | None = None, reason: str | None = None) -> None: + self.conversation_id = conversation_id + self.reason = reason + + +@action_handler("EndWorkflow") +async def handle_end_workflow(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """End the workflow execution. + + Action schema: + kind: EndWorkflow + reason: Optional reason for ending (for logging) + """ + reason = ctx.action.get("reason") + + logger.debug(f"EndWorkflow: ending workflow{f' (reason: {reason})' if reason else ''}") + + yield EndWorkflowSignal(reason=reason) + + +@action_handler("EndConversation") +async def handle_end_conversation(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """End the current conversation. + + Action schema: + kind: EndConversation + conversationId: Optional specific conversation to end + reason: Optional reason for ending + """ + conversation_id = ctx.action.get("conversationId") + reason = ctx.action.get("reason") + + # Evaluate conversation ID if provided + if conversation_id: + evaluated_id = ctx.state.eval_if_expression(conversation_id) + else: + evaluated_id = ctx.state.get("system.ConversationId") + + logger.debug(f"EndConversation: ending conversation {evaluated_id}{f' (reason: {reason})' if reason else ''}") + + yield EndConversationSignal(conversation_id=evaluated_id, reason=reason) diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_actions_error.py b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_error.py new file mode 100644 index 0000000000..334114ee64 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_actions_error.py @@ -0,0 +1,130 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Error handling action handlers for declarative workflows. + +This module implements handlers for: +- ThrowException: Raise an error that can be caught by TryCatch +- TryCatch: Try-catch-finally error handling +""" + +from collections.abc import AsyncGenerator +from dataclasses import dataclass + +from agent_framework import get_logger + +from ._handlers import ( + ActionContext, + WorkflowEvent, + action_handler, +) + +logger = get_logger("agent_framework.declarative.workflows.actions") + + +class WorkflowActionError(Exception): + """Exception raised by ThrowException action.""" + + def __init__(self, message: str, code: str | None = None): + super().__init__(message) + self.code = code + + +@dataclass +class ErrorEvent(WorkflowEvent): + """Event emitted when an error occurs.""" + + message: str + """The error message.""" + + code: str | None = None + """Optional error code.""" + + source_action: str | None = None + """The action that caused the error.""" + + +@action_handler("ThrowException") +async def handle_throw_exception(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Raise an exception that can be caught by TryCatch. + + Action schema: + kind: ThrowException + message: =expression or literal error message + code: ERROR_CODE # optional error code + """ + message_expr = ctx.action.get("message", "An error occurred") + code = ctx.action.get("code") + + # Evaluate the message if it's an expression + message = ctx.state.eval_if_expression(message_expr) + + logger.debug(f"ThrowException: {message} (code={code})") + + raise WorkflowActionError(str(message), code) + + # This yield is never reached but makes it a generator + yield ErrorEvent(message=str(message), code=code) # type: ignore[unreachable] + + +@action_handler("TryCatch") +async def handle_try_catch(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + """Try-catch-finally error handling. + + Action schema: + kind: TryCatch + try: + - kind: ... # actions to try + catch: + - kind: ... # actions to execute on error (optional) + finally: + - kind: ... # actions to always execute (optional) + + In the catch block, the following variables are available: + turn.error.message: The error message + turn.error.code: The error code (if provided) + turn.error.type: The error type name + """ + try_actions = ctx.action.get("try", []) + catch_actions = ctx.action.get("catch", []) + finally_actions = ctx.action.get("finally", []) + + error_occurred = False + error_info = None + + # Execute try block + try: + async for event in ctx.execute_actions(try_actions, ctx.state): + yield event + except WorkflowActionError as e: + error_occurred = True + error_info = { + "message": str(e), + "code": e.code, + "type": "WorkflowActionError", + } + logger.debug(f"TryCatch: caught WorkflowActionError: {e}") + except Exception as e: + error_occurred = True + error_info = { + "message": str(e), + "code": None, + "type": type(e).__name__, + } + logger.debug(f"TryCatch: caught {type(e).__name__}: {e}") + + # Execute catch block if error occurred + if error_occurred and catch_actions: + # Set error info in turn scope + ctx.state.set("turn.error", error_info) + + try: + async for event in ctx.execute_actions(catch_actions, ctx.state): + yield event + finally: + # Clean up error info (but don't interfere with finally block) + pass + + # Execute finally block + if finally_actions: + async for event in ctx.execute_actions(finally_actions, ctx.state): + yield event diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_factory.py b/python/packages/declarative/agent_framework_declarative/_workflows/_factory.py new file mode 100644 index 0000000000..f7a6a82eaf --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_factory.py @@ -0,0 +1,450 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""WorkflowFactory creates executable Workflow objects from YAML definitions. + +This module provides the main entry point for declarative workflow support, +parsing YAML workflow definitions and creating Workflow objects that can be +executed using the core workflow runtime. + +Each YAML action becomes a real Executor node in the workflow graph, +enabling checkpointing, visualization, and pause/resume capabilities. +""" + +from collections.abc import Mapping +from pathlib import Path +from typing import Any, cast + +import yaml +from agent_framework import get_logger +from agent_framework._workflows import ( + CheckpointStorage, + Workflow, +) + +from .._loader import AgentFactory +from ._graph import DeclarativeGraphBuilder + +logger = get_logger("agent_framework.declarative.workflows") + + +class DeclarativeWorkflowError(Exception): + """Exception raised for errors in declarative workflow processing.""" + + pass + + +class WorkflowFactory: + """Factory for creating executable Workflow objects from YAML definitions. + + WorkflowFactory parses declarative workflow YAML files and creates + Workflow objects that can be executed using the core workflow runtime. + Each YAML action becomes a real Executor node in the workflow graph, + enabling checkpointing at action boundaries, visualization, and pause/resume. + + Example: + >>> factory = WorkflowFactory() + >>> workflow = factory.create_workflow_from_yaml_path("workflow.yaml") + >>> async for event in workflow.run_stream({"query": "Hello"}): + ... print(event) + + # With checkpointing enabled + >>> factory = WorkflowFactory(checkpoint_storage=FileCheckpointStorage(path)) + >>> workflow = factory.create_workflow_from_yaml_path("workflow.yaml") + """ + + def __init__( + self, + *, + agent_factory: AgentFactory | None = None, + agents: Mapping[str, Any] | None = None, + bindings: Mapping[str, Any] | None = None, + env_file: str | None = None, + checkpoint_storage: CheckpointStorage | None = None, + ) -> None: + """Initialize the workflow factory. + + Args: + agent_factory: Optional AgentFactory for creating agents from definitions + agents: Optional pre-created agents by name + bindings: Optional function bindings for tool calls + env_file: Optional path to .env file for environment variables + checkpoint_storage: Optional checkpoint storage for pause/resume + """ + self._agent_factory = agent_factory or AgentFactory(env_file=env_file) + self._agents: dict[str, Any] = dict(agents) if agents else {} + self._bindings: dict[str, Any] = dict(bindings) if bindings else {} + self._checkpoint_storage = checkpoint_storage + + def create_workflow_from_yaml_path( + self, + yaml_path: str | Path, + ) -> Workflow: + """Create a Workflow from a YAML file path. + + Args: + yaml_path: Path to the YAML workflow definition + + Returns: + An executable Workflow object + + Raises: + DeclarativeWorkflowError: If the YAML is invalid or cannot be parsed + FileNotFoundError: If the YAML file doesn't exist + """ + if not isinstance(yaml_path, Path): + yaml_path = Path(yaml_path) + + if not yaml_path.exists(): + raise FileNotFoundError(f"Workflow YAML file not found: {yaml_path}") + + with open(yaml_path) as f: + yaml_content = f.read() + + return self.create_workflow_from_yaml(yaml_content, base_path=yaml_path.parent) + + def create_workflow_from_yaml( + self, + yaml_content: str, + base_path: Path | None = None, + ) -> Workflow: + """Create a Workflow from a YAML string. + + Args: + yaml_content: The YAML workflow definition as a string + base_path: Optional base path for resolving relative file references + + Returns: + An executable Workflow object + + Raises: + DeclarativeWorkflowError: If the YAML is invalid or cannot be parsed + """ + try: + workflow_def = yaml.safe_load(yaml_content) + except yaml.YAMLError as e: + raise DeclarativeWorkflowError(f"Invalid YAML: {e}") from e + + return self.create_workflow_from_definition(workflow_def, base_path=base_path) + + def create_workflow_from_definition( + self, + workflow_def: dict[str, Any], + base_path: Path | None = None, + ) -> Workflow: + """Create a Workflow from a parsed workflow definition. + + Args: + workflow_def: The parsed workflow definition dictionary + base_path: Optional base path for resolving relative file references + + Returns: + An executable Workflow object + + Raises: + DeclarativeWorkflowError: If the definition is invalid + """ + # Validate the workflow definition + self._validate_workflow_def(workflow_def) + + # Extract workflow metadata + # Support both "name" field and trigger.id for workflow name + name = workflow_def.get("name") + if not name: + trigger = workflow_def.get("trigger", {}) + name = trigger.get("id", "declarative_workflow") if isinstance(trigger, dict) else "declarative_workflow" + description = workflow_def.get("description") + + # Create agents from definitions + agents = dict(self._agents) + agent_defs = workflow_def.get("agents", {}) + + for agent_name, agent_def in agent_defs.items(): + if agent_name in agents: + # Already have this agent + continue + + # Create agent using AgentFactory + try: + agent = self._create_agent_from_def(agent_def, base_path) + agents[agent_name] = agent + logger.debug(f"Created agent '{agent_name}' from definition") + except Exception as e: + logger.error(f"Failed to create agent '{agent_name}': {e}") + raise DeclarativeWorkflowError(f"Failed to create agent '{agent_name}': {e}") from e + + return self._create_workflow(workflow_def, name, description, agents) + + def _create_workflow( + self, + workflow_def: dict[str, Any], + name: str, + description: str | None, + agents: dict[str, Any], + ) -> Workflow: + """Create workflow from definition. + + Each YAML action becomes a real Executor node in the workflow graph. + This enables checkpointing at action boundaries. + + Args: + workflow_def: The workflow definition + name: Workflow name + description: Workflow description + agents: Registry of agent instances + + Returns: + Workflow with individual action executors as nodes + """ + # Normalize workflow definition to have actions at top level + normalized_def = self._normalize_workflow_def(workflow_def) + normalized_def["name"] = name + if description: + normalized_def["description"] = description + + # Build the graph-based workflow, passing agents for InvokeAzureAgent executors + try: + graph_builder = DeclarativeGraphBuilder( + normalized_def, + workflow_id=name, + agents=agents, + checkpoint_storage=self._checkpoint_storage, + ) + workflow = graph_builder.build() + except ValueError as e: + raise DeclarativeWorkflowError(f"Failed to build graph-based workflow: {e}") from e + + # Store agents and bindings for reference (executors already have them) + workflow._declarative_agents = agents # type: ignore[attr-defined] + workflow._declarative_bindings = self._bindings # type: ignore[attr-defined] + + # Store input schema if defined in workflow definition + # This allows DevUI to generate proper input forms + if "inputs" in workflow_def: + workflow.input_schema = self._convert_inputs_to_json_schema(workflow_def["inputs"]) # type: ignore[attr-defined] + + logger.debug( + "Created graph-based workflow '%s' with %d executors", + name, + len(graph_builder._executors), # type: ignore[reportPrivateUsage] + ) + + return workflow + + def _normalize_workflow_def(self, workflow_def: dict[str, Any]) -> dict[str, Any]: + """Normalize workflow definition to have actions at top level. + + Args: + workflow_def: The workflow definition + + Returns: + Normalized definition with actions at top level + """ + actions = self._get_actions_from_def(workflow_def) + return { + **workflow_def, + "actions": actions, + } + + def _validate_workflow_def(self, workflow_def: dict[str, Any]) -> None: + """Validate a workflow definition. + + Args: + workflow_def: The workflow definition to validate + + Raises: + DeclarativeWorkflowError: If the definition is invalid + """ + if not isinstance(workflow_def, dict): + raise DeclarativeWorkflowError("Workflow definition must be a dictionary") + + # Handle both formats: + # 1. Direct actions list: {"actions": [...]} + # 2. Trigger-based: {"kind": "Workflow", "trigger": {"actions": [...]}} + actions = self._get_actions_from_def(workflow_def) + + if not isinstance(actions, list): + raise DeclarativeWorkflowError("Workflow 'actions' must be a list") + + # Validate each action has a kind + for i, action in enumerate(actions): + if not isinstance(action, dict): + raise DeclarativeWorkflowError(f"Action at index {i} must be a dictionary") + if "kind" not in action: + raise DeclarativeWorkflowError(f"Action at index {i} missing 'kind' field") + + def _get_actions_from_def(self, workflow_def: dict[str, Any]) -> list[dict[str, Any]]: + """Extract actions from a workflow definition. + + Handles both direct actions format and trigger-based format. + + Args: + workflow_def: The workflow definition + + Returns: + List of action definitions + + Raises: + DeclarativeWorkflowError: If no actions can be found + """ + # Try direct actions first + if "actions" in workflow_def: + actions: list[dict[str, Any]] = workflow_def["actions"] + return actions + + # Try trigger-based format + if "trigger" in workflow_def: + trigger = workflow_def["trigger"] + if isinstance(trigger, dict) and "actions" in trigger: + trigger_actions: list[dict[str, Any]] = list(trigger["actions"]) # type: ignore[arg-type] + return trigger_actions + + raise DeclarativeWorkflowError("Workflow definition must have 'actions' field or 'trigger.actions' field") + + def _create_agent_from_def( + self, + agent_def: dict[str, Any], + base_path: Path | None = None, + ) -> Any: + """Create an agent from a definition. + + Args: + agent_def: The agent definition dictionary + base_path: Optional base path for resolving relative file references + + Returns: + An agent instance + """ + # Check if it's a reference to an external file + if "file" in agent_def: + file_path = agent_def["file"] + if base_path and not Path(file_path).is_absolute(): + file_path = base_path / file_path + return self._agent_factory.create_agent_from_yaml_path(file_path) + + # Check if it's an inline agent definition + if "kind" in agent_def: + # Convert to YAML and use AgentFactory + yaml_str = yaml.dump(agent_def) + return self._agent_factory.create_agent_from_yaml(yaml_str) + + # Handle connection-based agent (like Azure AI agents) + if "connection" in agent_def: + # This would create a hosted agent client + # For now, we'll need the user to provide pre-created agents + raise DeclarativeWorkflowError( + "Connection-based agents must be provided via the 'agents' parameter. " + "Create the agent using the appropriate client and pass it to WorkflowFactory." + ) + + raise DeclarativeWorkflowError( + f"Invalid agent definition. Expected 'file', 'kind', or 'connection': {agent_def}" + ) + + def register_agent(self, name: str, agent: Any) -> "WorkflowFactory": + """Register an agent instance with the factory. + + Args: + name: The name to register the agent under + agent: The agent instance + + Returns: + Self for method chaining + """ + self._agents[name] = agent + return self + + def register_binding(self, name: str, func: Any) -> "WorkflowFactory": + """Register a function binding with the factory. + + Args: + name: The name to register the function under + func: The function to bind + + Returns: + Self for method chaining + """ + self._bindings[name] = func + return self + + def _convert_inputs_to_json_schema(self, inputs_def: dict[str, Any]) -> dict[str, Any]: + """Convert a declarative inputs definition to JSON Schema. + + The inputs definition uses a simplified format: + inputs: + age: + type: integer + description: The user's age + name: + type: string + + This is converted to standard JSON Schema format. + + Args: + inputs_def: The inputs definition from the workflow YAML + + Returns: + A JSON Schema object + """ + properties: dict[str, Any] = {} + required: list[str] = [] + + for field_name, field_def in inputs_def.items(): + if isinstance(field_def, dict): + # Field has type and possibly other attributes + prop: dict[str, Any] = {} + field_def_dict: dict[str, Any] = cast(dict[str, Any], field_def) + field_type: str = str(field_def_dict.get("type", "string")) + + # Map declarative types to JSON Schema types + type_mapping: dict[str, str] = { + "string": "string", + "str": "string", + "integer": "integer", + "int": "integer", + "number": "number", + "float": "number", + "boolean": "boolean", + "bool": "boolean", + "array": "array", + "list": "array", + "object": "object", + "dict": "object", + } + prop["type"] = type_mapping.get(field_type, field_type) + + # Copy other attributes + if "description" in field_def_dict: + prop["description"] = field_def_dict["description"] + if "default" in field_def_dict: + prop["default"] = field_def_dict["default"] + if "enum" in field_def_dict: + prop["enum"] = field_def_dict["enum"] + + # Check if required (default: true unless explicitly false) + if field_def_dict.get("required", True): + required.append(field_name) + + properties[field_name] = prop + else: + # Simple type definition (e.g., "age: integer") + type_mapping_simple: dict[str, str] = { + "string": "string", + "str": "string", + "integer": "integer", + "int": "integer", + "number": "number", + "float": "number", + "boolean": "boolean", + "bool": "boolean", + } + properties[field_name] = {"type": type_mapping_simple.get(str(field_def), "string")} + required.append(field_name) + + schema: dict[str, Any] = { + "type": "object", + "properties": properties, + } + if required: + schema["required"] = required + + return schema diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_graph/__init__.py b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/__init__.py new file mode 100644 index 0000000000..46de48517b --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/__init__.py @@ -0,0 +1,113 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Graph-based declarative workflow execution. + +This module provides a graph-based approach to declarative workflows where +each YAML action is transformed into an actual workflow graph node (Executor). +This enables: +- Checkpointing at action boundaries +- Workflow visualization +- Pause/resume capabilities +- Full integration with the workflow runtime +""" + +from ._base import ( + DECLARATIVE_STATE_KEY, + ActionComplete, + ActionTrigger, + DeclarativeActionExecutor, + DeclarativeMessage, + DeclarativeWorkflowState, + LoopControl, + LoopIterationResult, +) +from ._builder import ALL_ACTION_EXECUTORS, DeclarativeGraphBuilder +from ._executors_agents import ( + AGENT_ACTION_EXECUTORS, + AGENT_REGISTRY_KEY, + TOOL_REGISTRY_KEY, + AgentExternalInputRequest, + AgentExternalInputResponse, + AgentResult, + ExternalLoopState, + InvokeAzureAgentExecutor, + InvokeToolExecutor, +) +from ._executors_basic import ( + BASIC_ACTION_EXECUTORS, + AppendValueExecutor, + ClearAllVariablesExecutor, + EmitEventExecutor, + ResetVariableExecutor, + SendActivityExecutor, + SetMultipleVariablesExecutor, + SetTextVariableExecutor, + SetValueExecutor, + SetVariableExecutor, +) +from ._executors_control_flow import ( + CONTROL_FLOW_EXECUTORS, + BreakLoopExecutor, + ContinueLoopExecutor, + EndConversationExecutor, + EndWorkflowExecutor, + ForeachInitExecutor, + ForeachNextExecutor, + JoinExecutor, +) +from ._executors_human_input import ( + HUMAN_INPUT_EXECUTORS, + ConfirmationExecutor, + HumanInputRequest, + QuestionChoice, + QuestionExecutor, + RequestExternalInputExecutor, + WaitForInputExecutor, +) + +__all__ = [ + "AGENT_ACTION_EXECUTORS", + "AGENT_REGISTRY_KEY", + "ALL_ACTION_EXECUTORS", + "BASIC_ACTION_EXECUTORS", + "CONTROL_FLOW_EXECUTORS", + "DECLARATIVE_STATE_KEY", + "HUMAN_INPUT_EXECUTORS", + "TOOL_REGISTRY_KEY", + "ActionComplete", + "ActionTrigger", + "AgentExternalInputRequest", + "AgentExternalInputResponse", + "AgentResult", + "AppendValueExecutor", + "BreakLoopExecutor", + "ClearAllVariablesExecutor", + "ConfirmationExecutor", + "ContinueLoopExecutor", + "DeclarativeActionExecutor", + "DeclarativeGraphBuilder", + "DeclarativeMessage", + "DeclarativeWorkflowState", + "EmitEventExecutor", + "EndConversationExecutor", + "EndWorkflowExecutor", + "ExternalLoopState", + "ForeachInitExecutor", + "ForeachNextExecutor", + "HumanInputRequest", + "InvokeAzureAgentExecutor", + "InvokeToolExecutor", + "JoinExecutor", + "LoopControl", + "LoopIterationResult", + "QuestionChoice", + "QuestionExecutor", + "RequestExternalInputExecutor", + "ResetVariableExecutor", + "SendActivityExecutor", + "SetMultipleVariablesExecutor", + "SetTextVariableExecutor", + "SetValueExecutor", + "SetVariableExecutor", + "WaitForInputExecutor", +] diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_base.py b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_base.py new file mode 100644 index 0000000000..b39ef71a37 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_base.py @@ -0,0 +1,668 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Base classes for graph-based declarative workflow executors. + +This module provides: +- DeclarativeWorkflowState: Manages workflow variables via SharedState +- DeclarativeActionExecutor: Base class for action executors +- Message types for inter-executor communication +""" + +import logging +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from agent_framework._workflows import ( + Executor, + SharedState, + WorkflowContext, +) + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from collections.abc import Mapping + +# Key used in SharedState to store declarative workflow variables +DECLARATIVE_STATE_KEY = "_declarative_workflow_state" + + +class DeclarativeWorkflowState: + """Manages workflow variables stored in SharedState. + + This class provides the same interface as the interpreter-based WorkflowState + but stores all data in SharedState for checkpointing support. + + The state is organized into namespaces: + - workflow.inputs: Initial inputs (read-only) + - workflow.outputs: Values to return from workflow + - turn: Variables persisting within the workflow turn + - agent: Results from most recent agent invocation + - conversation: Conversation history + """ + + def __init__(self, shared_state: SharedState): + """Initialize with a SharedState instance. + + Args: + shared_state: The workflow's shared state for persistence + """ + self._shared_state = shared_state + + async def initialize(self, inputs: "Mapping[str, Any] | None" = None) -> None: + """Initialize the declarative state with inputs. + + Args: + inputs: Initial workflow inputs (become workflow.inputs.*) + """ + state_data: dict[str, Any] = { + "inputs": dict(inputs) if inputs else {}, + "outputs": {}, + "turn": {}, + "agent": {}, + "conversation": {"messages": [], "history": []}, + "custom": {}, + } + await self._shared_state.set(DECLARATIVE_STATE_KEY, state_data) + + async def get_state_data(self) -> dict[str, Any]: + """Get the full state data dict from shared state.""" + try: + return await self._shared_state.get(DECLARATIVE_STATE_KEY) + except KeyError: + # Initialize if not present + await self.initialize() + return await self._shared_state.get(DECLARATIVE_STATE_KEY) + + async def set_state_data(self, data: dict[str, Any]) -> None: + """Set the full state data dict in shared state.""" + await self._shared_state.set(DECLARATIVE_STATE_KEY, data) + + async def get(self, path: str, default: Any = None) -> Any: + """Get a value from the state using a dot-notated path. + + Args: + path: Dot-notated path like 'turn.results' or 'workflow.inputs.query' + default: Default value if path doesn't exist + + Returns: + The value at the path, or default if not found + """ + # Map .NET style namespaces to Python style + if path.startswith("Local."): + path = "turn." + path[6:] + elif path.startswith("System."): + path = "system." + path[7:] + elif path.startswith("inputs."): + path = "workflow.inputs." + path[7:] + + state_data = await self.get_state_data() + parts = path.split(".") + if not parts: + return default + + namespace = parts[0] + remaining = parts[1:] + + # Handle workflow.inputs and workflow.outputs specially + if namespace == "workflow" and remaining: + sub_namespace = remaining[0] + remaining = remaining[1:] + if sub_namespace == "inputs": + obj: Any = state_data.get("inputs", {}) + elif sub_namespace == "outputs": + obj = state_data.get("outputs", {}) + else: + return default + elif namespace == "turn": + obj = state_data.get("turn", {}) + elif namespace == "system": + obj = state_data.get("system", {}) + elif namespace == "agent": + obj = state_data.get("agent", {}) + elif namespace == "conversation": + obj = state_data.get("conversation", {}) + else: + # Try custom namespace + custom_data: dict[str, Any] = state_data.get("custom", {}) + obj = custom_data.get(namespace, default) + if obj is default: + return default + + # Navigate the remaining path + for part in remaining: + if isinstance(obj, dict): + obj = obj.get(part, default) # type: ignore[union-attr] + if obj is default: + return default + elif hasattr(obj, part): # type: ignore[arg-type] + obj = getattr(obj, part) # type: ignore[arg-type] + else: + return default + + return obj # type: ignore[return-value] + + async def set(self, path: str, value: Any) -> None: + """Set a value in the state using a dot-notated path. + + Args: + path: Dot-notated path like 'turn.results' or 'workflow.outputs.response' + value: The value to set + + Raises: + ValueError: If attempting to set workflow.inputs (which is read-only) + """ + # Map .NET style namespaces to Python style + if path.startswith("Local."): + path = "turn." + path[6:] + elif path.startswith("System."): + path = "system." + path[7:] + elif path.startswith("inputs."): + path = "workflow.inputs." + path[7:] + + state_data = await self.get_state_data() + parts = path.split(".") + if not parts: + return + + namespace = parts[0] + remaining = parts[1:] + + # Determine target dict + if namespace == "workflow": + if not remaining: + raise ValueError("Cannot set 'workflow' directly; use 'workflow.outputs.*'") + sub_namespace = remaining[0] + remaining = remaining[1:] + if sub_namespace == "inputs": + raise ValueError("Cannot modify workflow.inputs - they are read-only") + if sub_namespace == "outputs": + target = state_data.setdefault("outputs", {}) + else: + raise ValueError(f"Unknown workflow namespace: {sub_namespace}") + elif namespace == "turn": + target = state_data.setdefault("turn", {}) + elif namespace == "system": + target = state_data.setdefault("system", {}) + elif namespace == "agent": + target = state_data.setdefault("agent", {}) + elif namespace == "conversation": + target = state_data.setdefault("conversation", {}) + else: + # Create or use custom namespace + custom = state_data.setdefault("custom", {}) + if namespace not in custom: + custom[namespace] = {} + target = custom[namespace] + + if not remaining: + raise ValueError(f"Cannot replace entire namespace '{namespace}'") + + # Navigate to parent, creating dicts as needed + for part in remaining[:-1]: + if part not in target: + target[part] = {} + target = target[part] + + # Set the final value + target[remaining[-1]] = value + await self.set_state_data(state_data) + + async def append(self, path: str, value: Any) -> None: + """Append a value to a list at the specified path. + + If the path doesn't exist, creates a new list with the value. + + Args: + path: Dot-notated path to a list + value: The value to append + """ + existing = await self.get(path) + if existing is None: + await self.set(path, [value]) + elif isinstance(existing, list): + existing_list: list[Any] = list(existing) # type: ignore[arg-type] + existing_list.append(value) + await self.set(path, existing_list) + else: + raise ValueError(f"Cannot append to non-list at path '{path}'") + + async def get_inputs(self) -> dict[str, Any]: + """Get the workflow inputs.""" + state_data = await self.get_state_data() + inputs: dict[str, Any] = state_data.get("inputs", {}) + return inputs + + async def get_outputs(self) -> dict[str, Any]: + """Get the workflow outputs.""" + state_data = await self.get_state_data() + outputs: dict[str, Any] = state_data.get("outputs", {}) + return outputs + + async def eval(self, expression: str) -> Any: + """Evaluate a PowerFx expression with the current state. + + Expressions starting with '=' are evaluated as PowerFx. + Other strings are returned as-is. + + Args: + expression: The expression to evaluate + + Returns: + The evaluated result + """ + if not expression: + return expression + + if not isinstance(expression, str): + return expression + + if not expression.startswith("="): + return expression + + # Strip the leading '=' for evaluation + formula = expression[1:] + + # Try PowerFx evaluation if available + try: + from powerfx import Engine + + engine = Engine() + symbols = await self._to_powerfx_symbols() + return engine.eval(formula, symbols=symbols) + except ImportError: + # powerfx package not installed - use simple fallback + logger.debug(f"PowerFx package not installed, using simple evaluation for: {formula}") + except Exception: + # PowerFx evaluation failed (syntax error, unsupported function, etc.) + # Fall back to simple evaluation which handles basic cases + logger.debug(f"PowerFx evaluation failed for '{formula}', falling back to simple evaluation") + + # Fallback to simple evaluation + return await self._eval_simple(formula) + + async def _to_powerfx_symbols(self) -> dict[str, Any]: + """Convert the current state to a PowerFx symbols dictionary.""" + state_data = await self.get_state_data() + return { + "workflow": { + "inputs": state_data.get("inputs", {}), + "outputs": state_data.get("outputs", {}), + }, + "turn": state_data.get("turn", {}), + "agent": state_data.get("agent", {}), + "conversation": state_data.get("conversation", {}), + **state_data.get("custom", {}), + } + + async def _eval_simple(self, formula: str) -> Any: + """Simple expression evaluation fallback.""" + from .._powerfx_functions import CUSTOM_FUNCTIONS + + formula = formula.strip() + + # Handle logical operators first (lowest precedence) + # Note: " And " and " Or " are case-sensitive in PowerFx + # We also handle common variations with newlines + for and_op in [" And ", "\n And ", " And\n", "\nAnd\n", "\nAnd ", " And\r\n", "\r\nAnd "]: + if and_op in formula: + # Split on first occurrence + idx = formula.find(and_op) + left_str = formula[:idx].strip() + right_str = formula[idx + len(and_op) :].strip() + left = await self._eval_simple(left_str) + right = await self._eval_simple(right_str) + return bool(left) and bool(right) + + for or_op in [" Or ", "\n Or ", " Or\n", "\nOr\n", "\nOr ", " Or\r\n", "\r\nOr "]: + if or_op in formula: + idx = formula.find(or_op) + left_str = formula[:idx].strip() + right_str = formula[idx + len(or_op) :].strip() + left = await self._eval_simple(left_str) + right = await self._eval_simple(right_str) + return bool(left) or bool(right) + + # Handle negation + if formula.startswith("!"): + inner = formula[1:].strip() + result = await self._eval_simple(inner) + return not bool(result) + + # Handle Not() function + if formula.startswith("Not(") and formula.endswith(")"): + inner = formula[4:-1].strip() + result = await self._eval_simple(inner) + return not bool(result) + + # Handle function calls + for func_name, func in CUSTOM_FUNCTIONS.items(): + if formula.startswith(f"{func_name}(") and formula.endswith(")"): + args_str = formula[len(func_name) + 1 : -1] + args = self._parse_function_args(args_str) + evaluated_args: list[Any] = [] + for arg in args: + if isinstance(arg, str): + evaluated_args.append(await self._eval_simple(arg)) + else: + evaluated_args.append(arg) + try: + return func(*evaluated_args) + except Exception: + return formula + + # Handle comparison operators + # Support both PowerFx style (=) and Python style (==) for equality + for op in [" < ", " > ", " <= ", " >= ", " <> ", " != ", " == ", " = "]: + if op in formula: + parts = formula.split(op, 1) + left = await self._eval_simple(parts[0].strip()) + right = await self._eval_simple(parts[1].strip()) + if op == " < ": + return left < right + if op == " > ": + return left > right + if op == " <= ": + return left <= right + if op == " >= ": + return left >= right + if op == " <> " or op == " != ": + return left != right + if op == " = " or op == " == ": + return left == right + + # Handle arithmetic operators (lower precedence than comparison) + for op in [" + ", " - ", " * ", " / "]: + if op in formula: + parts = formula.split(op, 1) + left = await self._eval_simple(parts[0].strip()) + right = await self._eval_simple(parts[1].strip()) + # Treat None as 0 for arithmetic (PowerFx behavior) + if left is None: + left = 0 + if right is None: + right = 0 + # Coerce Decimal to float for arithmetic + if hasattr(left, "__float__"): + left = float(left) + if hasattr(right, "__float__"): + right = float(right) + if op == " + ": + return left + right + if op == " - ": + return left - right + if op == " * ": + return left * right + if op == " / ": + return left / right + + # Handle string literals + if (formula.startswith('"') and formula.endswith('"')) or (formula.startswith("'") and formula.endswith("'")): + return formula[1:-1] + + # Handle numeric literals + try: + if "." in formula: + return float(formula) + return int(formula) + except ValueError: + pass + + # Handle boolean literals + if formula.lower() == "true": + return True + if formula.lower() == "false": + return False + + # Handle variable references + if "." in formula: + path = formula + if formula.startswith("Local."): + path = "turn." + formula[6:] + elif formula.startswith("System."): + path = "system." + formula[7:] + elif formula.startswith("inputs."): + path = "workflow.inputs." + formula[7:] + return await self.get(path) + + return formula + + def _parse_function_args(self, args_str: str) -> list[str]: + """Parse function arguments, handling nested parentheses.""" + args: list[str] = [] + current = "" + depth = 0 + in_string = False + string_char = None + + for char in args_str: + if char in ('"', "'") and not in_string: + in_string = True + string_char = char + current += char + elif char == string_char and in_string: + in_string = False + string_char = None + current += char + elif char == "(" and not in_string: + depth += 1 + current += char + elif char == ")" and not in_string: + depth -= 1 + current += char + elif char == "," and depth == 0 and not in_string: + args.append(current.strip()) + current = "" + else: + current += char + + if current.strip(): + args.append(current.strip()) + + return args + + async def eval_if_expression(self, value: Any) -> Any: + """Evaluate a value if it's a PowerFx expression, otherwise return as-is.""" + if isinstance(value, str): + return await self.eval(value) + if isinstance(value, dict): + value_dict: dict[str, Any] = dict(value) # type: ignore[arg-type] + return {k: await self.eval_if_expression(v) for k, v in value_dict.items()} + if isinstance(value, list): + value_list: list[Any] = list(value) # type: ignore[arg-type] + return [await self.eval_if_expression(item) for item in value_list] + return value + + async def interpolate_string(self, text: str) -> str: + """Interpolate {Variable.Path} references in a string. + + This handles template-style variable substitution like: + - "Created ticket #{Local.TicketParameters.TicketId}" + - "Routing to {Local.RoutingParameters.TeamName}" + + Args: + text: Text that may contain {Variable.Path} references + + Returns: + Text with variables interpolated + """ + import re + + async def replace_var(match: re.Match[str]) -> str: + var_path: str = match.group(1) + # Map .NET style to Python style (Local.X -> turn.X) + path = var_path + if var_path.startswith("Local."): + path = "turn." + var_path[6:] + elif var_path.startswith("System."): + path = "system." + var_path[7:] + value = await self.get(path) + return str(value) if value is not None else "" + + # Match {Variable.Path} patterns + pattern = r"\{([A-Za-z][A-Za-z0-9_.]*)\}" + + # re.sub doesn't support async, so we need to do it manually + result = text + for match in re.finditer(pattern, text): + replacement = await replace_var(match) + result = result.replace(match.group(0), replacement, 1) + + return result + + +# Message types for inter-executor communication +# These are defined before DeclarativeActionExecutor since it references them + + +class ActionTrigger: + """Message that triggers a declarative action executor. + + This is sent between executors in the graph to pass control + and any action-specific data. + """ + + def __init__(self, data: Any = None): + """Initialize the action trigger. + + Args: + data: Optional data to pass to the action + """ + self.data = data + + +class ActionComplete: + """Message sent when a declarative action completes. + + This is sent to downstream executors to continue the workflow. + """ + + def __init__(self, result: Any = None): + """Initialize the completion message. + + Args: + result: Optional result from the action + """ + self.result = result + + +@dataclass +class ConditionResult: + """Result of evaluating a condition (If/Switch). + + This message is output by ConditionEvaluatorExecutor and SwitchEvaluatorExecutor + to indicate which branch should be taken. + """ + + matched: bool + branch_index: int # Which branch matched (0 = first, -1 = else/default) + value: Any = None # The evaluated condition value + + +@dataclass +class LoopIterationResult: + """Result of a loop iteration step. + + This message is output by ForeachInitExecutor and ForeachNextExecutor + to indicate whether the loop should continue. + """ + + has_next: bool + current_item: Any = None + current_index: int = 0 + + +@dataclass +class LoopControl: + """Signal for loop control (break/continue). + + This message is output by BreakLoopExecutor and ContinueLoopExecutor. + """ + + action: str # "break" or "continue" + + +# Union type for any declarative action message - allows executors to accept +# messages from triggers, completions, and control flow results +DeclarativeMessage = ActionTrigger | ActionComplete | ConditionResult | LoopIterationResult | LoopControl + + +class DeclarativeActionExecutor(Executor): + """Base class for declarative action executors. + + Each declarative action (SetValue, SendActivity, etc.) is implemented + as a subclass of this executor. The executor receives an ActionInput + message containing the action definition and state reference. + """ + + def __init__( + self, + action_def: dict[str, Any], + *, + id: str | None = None, + ): + """Initialize the declarative action executor. + + Args: + action_def: The action definition from YAML + id: Optional executor ID (defaults to action id or generated) + """ + action_id = id or action_def.get("id") or f"{action_def.get('kind', 'action')}_{hash(str(action_def)) % 10000}" + super().__init__(id=action_id, defer_discovery=True) + self._action_def = action_def + + # Manually register handlers after initialization + self._handlers = {} + self._handler_specs = [] + self._discover_handlers() + self._discover_response_handlers() + + @property + def action_def(self) -> dict[str, Any]: + """Get the action definition.""" + return self._action_def + + @property + def display_name(self) -> str | None: + """Get the display name for logging.""" + return self._action_def.get("displayName") + + def _get_state(self, shared_state: SharedState) -> DeclarativeWorkflowState: + """Get the declarative workflow state wrapper.""" + return DeclarativeWorkflowState(shared_state) + + async def _ensure_state_initialized( + self, + ctx: WorkflowContext[Any, Any], + trigger: Any, + ) -> DeclarativeWorkflowState: + """Ensure declarative state is initialized. + + Follows .NET's DefaultTransform pattern - accepts any input type: + - dict/Mapping: Used directly as workflow.inputs + - str: Converted to {"input": value} + - DeclarativeMessage: Internal message, no initialization needed + - Any other type: Converted via str() to {"input": str(value)} + + Args: + ctx: The workflow context + trigger: The trigger message - can be any type + + Returns: + The initialized DeclarativeWorkflowState + """ + state = self._get_state(ctx.shared_state) + + if isinstance(trigger, dict): + # Structured inputs - use directly + await state.initialize(trigger) + elif isinstance(trigger, str): + # String input - wrap in dict + await state.initialize({"input": trigger}) + elif not isinstance( + trigger, (ActionTrigger, ActionComplete, ConditionResult, LoopIterationResult, LoopControl) + ): + # Any other type - convert to string like .NET's DefaultTransform + await state.initialize({"input": str(trigger)}) + + return state diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_builder.py b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_builder.py new file mode 100644 index 0000000000..054d596d0b --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_builder.py @@ -0,0 +1,1043 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Builder that transforms declarative YAML into a workflow graph. + +This module provides the DeclarativeGraphBuilder which is analogous to +.NET's WorkflowActionVisitor + WorkflowElementWalker. It walks the YAML +action definitions and creates a proper workflow graph with: +- Executor nodes for each action +- Edges for sequential flow +- Async state-aware edge conditions for If/Switch branching +- Loop edges for foreach +""" + +import re +from collections.abc import Awaitable, Callable +from typing import Any, cast + +from agent_framework._workflows import ( + Workflow, + WorkflowBuilder, +) +from agent_framework._workflows._shared_state import SharedState + +from ._base import ( + DECLARATIVE_STATE_KEY, + DeclarativeWorkflowState, + LoopIterationResult, +) +from ._executors_agents import AGENT_ACTION_EXECUTORS, InvokeAzureAgentExecutor +from ._executors_basic import BASIC_ACTION_EXECUTORS +from ._executors_control_flow import ( + CONTROL_FLOW_EXECUTORS, + ForeachInitExecutor, + ForeachNextExecutor, + JoinExecutor, +) +from ._executors_human_input import HUMAN_INPUT_EXECUTORS + +# Type alias for async condition functions used in workflow edges +ConditionFunc = Callable[[Any, SharedState], Awaitable[bool]] + +# Combined mapping of all action kinds to executor classes +ALL_ACTION_EXECUTORS = { + **BASIC_ACTION_EXECUTORS, + **CONTROL_FLOW_EXECUTORS, + **AGENT_ACTION_EXECUTORS, + **HUMAN_INPUT_EXECUTORS, +} + + +def _generate_semantic_id(action_def: dict[str, Any], kind: str) -> str | None: + """Generate a semantic ID from the action's content. + + Derives a meaningful identifier from the action's primary properties rather + than using generic index-based naming. Returns None if no semantic name + can be derived. + + Args: + action_def: The action definition from YAML + kind: The action kind (e.g., "SetValue", "SendActivity") + + Returns: + A semantic ID string, or None if no semantic name can be derived + """ + # SetValue/SetVariable: use the value being set + if kind in ("SetValue", "SetVariable", "SetTextVariable"): + value = action_def.get("value") or action_def.get("text") + if value and isinstance(value, str) and not value.startswith("="): + # Simple value like "child", "teenager", "adult" + slug = _slugify(value) + if slug: + return f"set_{slug}" + + # Try to derive from path (e.g., turn.category -> set_category) + path = action_def.get("path") or action_def.get("variable", {}).get("path") + if path and isinstance(path, str): + # Extract last segment: "turn.category" -> "category" + last_segment = path.split(".")[-1] + slug = _slugify(last_segment) + if slug: + return f"set_{slug}" + + # SendActivity: extract meaningful words from text + if kind == "SendActivity": + activity: dict[str, Any] | Any = action_def.get("activity", {}) + text: str | None = None + if isinstance(activity, dict): + activity_dict: dict[str, Any] = cast(dict[str, Any], activity) + text_val = activity_dict.get("text") + text = str(text_val) if text_val else None + if text and isinstance(text, str): + slug = _extract_activity_slug(text) + if slug: + return f"send_{slug}" + + # InvokeAzureAgent: use agent name + if kind == "InvokeAzureAgent": + agent = action_def.get("agent") or action_def.get("agentName") + if agent and isinstance(agent, str): + slug = _slugify(agent) + if slug: + return f"invoke_{slug}" + + # AppendValue: use the path + if kind == "AppendValue": + path = action_def.get("path") + if path and isinstance(path, str): + last_segment = path.split(".")[-1] + slug = _slugify(last_segment) + if slug: + return f"append_{slug}" + + # ResetVariable: use the path + if kind in ("ResetVariable", "DeleteVariable"): + path = action_def.get("path") or action_def.get("variable", {}).get("path") + if path and isinstance(path, str): + last_segment = path.split(".")[-1] + slug = _slugify(last_segment) + if slug: + prefix = "reset" if kind == "ResetVariable" else "delete" + return f"{prefix}_{slug}" + + # HumanInput actions: use prompt or variable + if kind in ("RequestHumanInput", "WaitForHumanInput"): + prompt = action_def.get("prompt") + if prompt and isinstance(prompt, str): + slug = _extract_activity_slug(prompt) + if slug: + return f"input_{slug}" + variable = action_def.get("variable", {}).get("path") + if variable and isinstance(variable, str): + slug = _slugify(variable.split(".")[-1]) + if slug: + return f"input_{slug}" + + return None + + +def _slugify(text: str, max_words: int = 3) -> str: + """Convert text to a slug suitable for an ID. + + Args: + text: The text to slugify + max_words: Maximum number of words to include + + Returns: + A lowercase, underscore-separated slug + """ + # Remove expression prefix if present + if text.startswith("="): + text = text[1:] + + # Convert to lowercase and extract words + text = text.lower() + # Replace non-alphanumeric with spaces + text = re.sub(r"[^a-z0-9]+", " ", text) + # Split and take first N words + words = text.split()[:max_words] + # Filter out very short words (articles, etc.) except meaningful ones + words = [w for w in words if len(w) > 1 or w in ("a", "i")] + + if not words: + return "" + + return "_".join(words) + + +def _extract_activity_slug(text: str) -> str: + """Extract a meaningful slug from activity text. + + Focuses on finding the most distinctive content words. + + Args: + text: The activity text + + Returns: + A slug representing the key content + """ + # Words to skip - common greeting/filler words + skip_words = { + "welcome", + "hello", + "hi", + "hey", + "there", + "dear", + "greetings", + "here", + "are", + "some", + "our", + "the", + "for", + "and", + "you", + "your", + "have", + "been", + "with", + "this", + "that", + "these", + "those", + "check", + "out", + "enjoy", + "fun", + "cool", + "great", + "things", + "activities", + } + + text = text.lower() + # Remove punctuation + text = str(re.sub(r"[^a-z0-9\s]+", " ", text)) + words: list[str] = text.split() + + # Find distinctive words (not in skip list, longer than 3 chars) + meaningful_words: list[str] = [] + for word in words: + if word not in skip_words and len(word) > 3: + meaningful_words.append(word) + if len(meaningful_words) >= 2: + break + + if not meaningful_words: + # Fall back to any word longer than 2 chars not in greeting set + greeting_set = {"welcome", "hello", "hi", "hey", "there", "dear", "greetings"} + for word in words: + if len(word) > 2 and word not in greeting_set: + meaningful_words.append(word) + if len(meaningful_words) >= 2: + break + + if not meaningful_words: + return "" + + return "_".join(meaningful_words) + + +def _create_condition_evaluator(condition_expr: str, negate: bool = False) -> ConditionFunc: + """Create an async condition function that evaluates a declarative expression. + + Args: + condition_expr: The condition expression (e.g., "=turn.age < 13") + negate: If True, negate the result (for else branches) + + Returns: + An async function (data, shared_state) -> bool + """ + + async def evaluate_condition(data: Any, shared_state: SharedState) -> bool: + # Get DeclarativeWorkflowState from shared state + try: + await shared_state.get(DECLARATIVE_STATE_KEY) + except KeyError: + # State not initialized - shouldn't happen but handle gracefully + return not negate # Default to then branch + + state = DeclarativeWorkflowState(shared_state) + result = await state.eval(condition_expr) + bool_result = bool(result) + return not bool_result if negate else bool_result + + return evaluate_condition + + +def _create_none_matched_condition(condition_exprs: list[str]) -> ConditionFunc: + """Create an async condition that returns True when none of the given conditions match. + + Args: + condition_exprs: List of condition expressions that should all be False + + Returns: + An async function (data, shared_state) -> bool + """ + + async def evaluate_none_matched(data: Any, shared_state: SharedState) -> bool: + # Get DeclarativeWorkflowState from shared state + try: + await shared_state.get(DECLARATIVE_STATE_KEY) + except KeyError: + return True # Default to else branch if no state + + state = DeclarativeWorkflowState(shared_state) + + # Check that none of the conditions match + for condition_expr in condition_exprs: + result = await state.eval(condition_expr) + if bool(result): + return False # A condition matched, so this default branch shouldn't trigger + + return True # No conditions matched + + return evaluate_none_matched + + +class DeclarativeGraphBuilder: + """Builds a Workflow graph from declarative YAML actions. + + This builder transforms declarative action definitions into a proper + workflow graph with executor nodes and edges. It handles: + - Sequential actions (simple edges) + - Conditional branching (If/Switch with condition edges) + - Loops (Foreach with loop edges) + - Jumps (Goto with target edges) + + Example usage: + yaml_def = { + "actions": [ + {"kind": "SendActivity", "activity": {"text": "Hello"}}, + {"kind": "SetValue", "path": "turn.count", "value": 0}, + ] + } + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + """ + + def __init__( + self, + yaml_definition: dict[str, Any], + workflow_id: str | None = None, + agents: dict[str, Any] | None = None, + checkpoint_storage: Any | None = None, + ): + """Initialize the builder. + + Args: + yaml_definition: The parsed YAML workflow definition + workflow_id: Optional ID for the workflow (defaults to name from YAML) + agents: Registry of agent instances by name (for InvokeAzureAgent actions) + checkpoint_storage: Optional checkpoint storage for pause/resume support + """ + self._yaml_def = yaml_definition + self._workflow_id = workflow_id or yaml_definition.get("name", "declarative_workflow") + self._executors: dict[str, Any] = {} # id -> executor + self._action_index = 0 # Counter for generating unique IDs + self._agents = agents or {} # Agent registry for agent executors + self._checkpoint_storage = checkpoint_storage + self._pending_gotos: list[tuple[Any, str]] = [] # (goto_executor, target_id) + + def build(self) -> Workflow: + """Build the workflow graph. + + Returns: + A Workflow instance with all executors wired together + + Raises: + ValueError: If no actions are defined (empty workflow) + """ + builder = WorkflowBuilder(name=self._workflow_id) + + # Enable checkpointing if storage is provided + if self._checkpoint_storage: + builder.with_checkpointing(self._checkpoint_storage) + + actions = self._yaml_def.get("actions", []) + if not actions: + # Empty workflow - raise an error since we need at least one executor + raise ValueError("Cannot build workflow with no actions. At least one action is required.") + + # First pass: create all executors + entry_executor = self._create_executors_for_actions(actions, builder) + + # Set the entry point + if entry_executor: + # Check if entry is a control flow structure (If/Switch) + if getattr(entry_executor, "_is_if_structure", False) or getattr( + entry_executor, "_is_switch_structure", False + ): + # Create an entry passthrough node and wire to the structure's branches + entry_node = JoinExecutor({"kind": "Entry"}, id="_workflow_entry") + self._executors[entry_node.id] = entry_node + builder.set_start_executor(entry_node) + # Use _add_sequential_edge which knows how to wire to structures + self._add_sequential_edge(builder, entry_node, entry_executor) + else: + builder.set_start_executor(entry_executor) + else: + raise ValueError("Failed to create any executors from actions.") + + # Resolve pending gotos (back-edges for loops, forward-edges for jumps) + self._resolve_pending_gotos(builder) + + return builder.build() + + def _resolve_pending_gotos(self, builder: WorkflowBuilder) -> None: + """Resolve pending goto edges after all executors are created. + + Creates edges from goto executors to their target executors. + """ + for goto_executor, target_id in self._pending_gotos: + target_executor = self._executors.get(target_id) + if target_executor: + # Create edge from goto to target + builder.add_edge(source=goto_executor, target=target_executor) + # If target not found, the goto effectively becomes a no-op (workflow ends) + + def _create_executors_for_actions( + self, + actions: list[dict[str, Any]], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any | None: + """Create executors for a list of actions and wire them together. + + Args: + actions: List of action definitions + builder: The workflow builder + parent_context: Context from parent (e.g., loop info) + + Returns: + The first executor in the chain, or None if no actions + """ + if not actions: + return None + + first_executor = None + prev_executor = None + executors_in_chain: list[Any] = [] + + for action_def in actions: + executor = self._create_executor_for_action(action_def, builder, parent_context) + + if executor is None: + continue + + executors_in_chain.append(executor) + + if first_executor is None: + first_executor = executor + + # Wire sequential edge from previous executor + if prev_executor is not None: + self._add_sequential_edge(builder, prev_executor, executor) + + prev_executor = executor + + # Store the chain for later reference + if first_executor is not None: + first_executor._chain_executors = executors_in_chain # type: ignore[attr-defined] + + return first_executor + + def _create_executor_for_action( + self, + action_def: dict[str, Any], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any | None: + """Create an executor for a single action. + + Args: + action_def: The action definition from YAML + builder: The workflow builder + parent_context: Context from parent + + Returns: + The created executor, or None if action type not supported + """ + kind = action_def.get("kind", "") + + # Handle special control flow actions + if kind == "If": + return self._create_if_structure(action_def, builder, parent_context) + if kind == "Switch" or kind == "ConditionGroup": + return self._create_switch_structure(action_def, builder, parent_context) + if kind == "Foreach": + return self._create_foreach_structure(action_def, builder, parent_context) + if kind == "Goto" or kind == "GotoAction": + return self._create_goto_reference(action_def, builder, parent_context) + if kind == "BreakLoop": + return self._create_break_executor(action_def, builder, parent_context) + if kind == "ContinueLoop": + return self._create_continue_executor(action_def, builder, parent_context) + + # Get the executor class for this action kind + executor_class = ALL_ACTION_EXECUTORS.get(kind) + + if executor_class is None: + # Unknown action type - skip with warning + # In production, might want to log this + return None + + # Create the executor with ID + # Priority: explicit ID > semantic ID > fallback index-based ID + explicit_id = action_def.get("id") + if explicit_id: + action_id = explicit_id + else: + # Try to generate a semantic ID from action content + semantic_id = _generate_semantic_id(action_def, kind) + if semantic_id: + # Ensure uniqueness by checking if ID already exists + base_id = semantic_id + suffix = 0 + while semantic_id in self._executors: + suffix += 1 + semantic_id = f"{base_id}_{suffix}" + action_id = semantic_id + else: + # Fallback to index-based ID + parent_id = (parent_context or {}).get("parent_id") + action_id = f"{parent_id}_{kind}_{self._action_index}" if parent_id else f"{kind}_{self._action_index}" + self._action_index += 1 + + # Pass agents to agent-related executors + executor: Any + if kind in ("InvokeAzureAgent",): + executor = InvokeAzureAgentExecutor(action_def, id=action_id, agents=self._agents) + else: + executor = executor_class(action_def, id=action_id) + self._executors[action_id] = executor + + return executor + + def _create_if_structure( + self, + action_def: dict[str, Any], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any: + """Create the graph structure for an If action. + + An If action is implemented without condition evaluator or join executors. + Conditional edges evaluate expressions against workflow state. + Branch exits are tracked so they can be wired directly to the successor. + + Args: + action_def: The If action definition + builder: The workflow builder + parent_context: Context from parent + + Returns: + A structure representing the If with branch entries and exits + """ + action_id = action_def.get("id") or f"If_{self._action_index}" + self._action_index += 1 + + condition_expr = action_def.get("condition", "true") + # Normalize boolean conditions from YAML to PowerFx-style strings + if condition_expr is True: + condition_expr = "=true" + elif condition_expr is False: + condition_expr = "=false" + elif isinstance(condition_expr, str) and not condition_expr.startswith("="): + # Bare string conditions should be evaluated as expressions + condition_expr = f"={condition_expr}" + + # Pass the If's ID as context for child action naming + branch_context = { + **(parent_context or {}), + "parent_id": action_id, + } + + # Create then branch + then_actions = action_def.get("then", action_def.get("actions", [])) + then_entry = self._create_executors_for_actions(then_actions, builder, branch_context) + + # Create else branch + else_actions = action_def.get("else", []) + else_entry = self._create_executors_for_actions(else_actions, builder, branch_context) if else_actions else None + else_passthrough = None + if not else_entry: + # No else branch - create a passthrough for continuation when condition is false + else_passthrough = JoinExecutor({"kind": "ElsePassthrough"}, id=f"{action_id}_else_pass") + self._executors[else_passthrough.id] = else_passthrough + + # Create async conditions + then_condition = _create_condition_evaluator(condition_expr, negate=False) + else_condition = _create_condition_evaluator(condition_expr, negate=True) + + # Get branch exit executors for later wiring to successor + then_exit = self._get_branch_exit(then_entry) + else_exit = self._get_branch_exit(else_entry) if else_entry else else_passthrough + + # Collect all branch exits (for wiring to successor) + branch_exits: list[Any] = [] + if then_exit: + branch_exits.append(then_exit) + if else_exit: + branch_exits.append(else_exit) + + # Create an IfStructure to hold all the info needed for wiring + class IfStructure: + def __init__(self) -> None: + self.id = action_id + self.then_entry = then_entry + self.else_entry = else_entry + self.else_passthrough = else_passthrough # Passthrough when no else actions + self.then_condition = then_condition + self.else_condition = else_condition + self.branch_exits = branch_exits # All exits that need wiring to successor + self._is_if_structure = True + + return IfStructure() + + def _create_switch_structure( + self, + action_def: dict[str, Any], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any: + """Create the graph structure for a Switch/ConditionGroup action. + + Like If, Switch is implemented without evaluator or join executors. + Conditional edges evaluate each condition expression against workflow state. + Branch exits are tracked for wiring directly to successor. + + Args: + action_def: The Switch action definition + builder: The workflow builder + parent_context: Context from parent + + Returns: + A SwitchStructure containing branch info for wiring + """ + action_id = action_def.get("id") or f"Switch_{self._action_index}" + self._action_index += 1 + + conditions = action_def.get("conditions", []) + + # Pass the Switch's ID as context for child action naming + branch_context = { + **(parent_context or {}), + "parent_id": action_id, + } + + # Collect branches with their conditions and exits + branches: list[tuple[Any, Any]] = [] # (entry_executor, async_condition) + branch_exits: list[Any] = [] # All exits that need wiring to successor + matched_conditions: list[str] = [] # For building the "none matched" condition + + for i, cond_item in enumerate(conditions): + condition_expr = cond_item.get("condition", "true") + # Normalize boolean conditions from YAML to PowerFx-style strings + if condition_expr is True: + condition_expr = "=true" + elif condition_expr is False: + condition_expr = "=false" + elif isinstance(condition_expr, str) and not condition_expr.startswith("="): + condition_expr = f"={condition_expr}" + matched_conditions.append(condition_expr) + + branch_actions = cond_item.get("actions", []) + # Use branch-specific context + case_context = {**branch_context, "parent_id": f"{action_id}_case{i}"} + branch_entry = self._create_executors_for_actions(branch_actions, builder, case_context) + + if branch_entry: + # Create async condition for this branch + branch_condition = _create_condition_evaluator(condition_expr, negate=False) + branches.append((branch_entry, branch_condition)) + # Track exit for later wiring + branch_exit = self._get_branch_exit(branch_entry) + if branch_exit: + branch_exits.append(branch_exit) + + # Handle else/default branch + # .NET uses "elseActions", Python fallback to "else" or "default" + else_actions = action_def.get("elseActions", action_def.get("else", action_def.get("default", []))) + default_entry = None + default_passthrough = None + if else_actions: + default_context = {**branch_context, "parent_id": f"{action_id}_else"} + default_entry = self._create_executors_for_actions(else_actions, builder, default_context) + if default_entry: + default_exit = self._get_branch_exit(default_entry) + if default_exit: + branch_exits.append(default_exit) + else: + # No else actions - create a passthrough for the "no match" case + # This allows the workflow to continue to the next action when no condition matches + default_passthrough = JoinExecutor({"kind": "DefaultPassthrough"}, id=f"{action_id}_default") + self._executors[default_passthrough.id] = default_passthrough + branch_exits.append(default_passthrough) + + # Create default condition (none of the previous conditions matched) + default_condition = _create_none_matched_condition(matched_conditions) + + # Create a SwitchStructure to hold all the info needed for wiring + class SwitchStructure: + def __init__(self) -> None: + self.id = action_id + self.branches = branches + self.default_entry = default_entry + self.default_passthrough = default_passthrough # Passthrough when no else actions + self.default_condition = default_condition + self.branch_exits = branch_exits # All exits that need wiring to successor + self._is_switch_structure = True + + return SwitchStructure() + + def _create_foreach_structure( + self, + action_def: dict[str, Any], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any: + """Create the graph structure for a Foreach action. + + A Foreach action becomes: + 1. ForeachInit node that initializes the loop + 2. Loop body actions + 3. ForeachNext node that advances to next item + 4. Back-edge from ForeachNext to loop body (when has_next=True) + 5. Exit edge from ForeachNext (when has_next=False) + + Args: + action_def: The Foreach action definition + builder: The workflow builder + parent_context: Context from parent + + Returns: + The foreach init executor (entry point) + """ + action_id = action_def.get("id") or f"Foreach_{self._action_index}" + self._action_index += 1 + + # Create foreach init executor + init_executor = ForeachInitExecutor(action_def, id=f"{action_id}_init") + self._executors[init_executor.id] = init_executor + + # Create foreach next executor (for advancing to next item) + next_executor = ForeachNextExecutor(action_def, init_executor.id, id=f"{action_id}_next") + self._executors[next_executor.id] = next_executor + + # Create join node for loop exit + join_executor = JoinExecutor({"kind": "Join"}, id=f"{action_id}_exit") + self._executors[join_executor.id] = join_executor + + # Create loop body + body_actions = action_def.get("actions", []) + loop_context = { + **(parent_context or {}), + "loop_id": action_id, + "loop_next_executor": next_executor, + } + body_entry = self._create_executors_for_actions(body_actions, builder, loop_context) + + if body_entry: + # Init -> body (when has_next=True) + builder.add_edge( + source=init_executor, + target=body_entry, + condition=lambda msg: isinstance(msg, LoopIterationResult) and msg.has_next, + ) + + # Body exit -> Next (get all exits from body and wire to next_executor) + body_exits = self._get_source_exits(body_entry) + for body_exit in body_exits: + builder.add_edge(source=body_exit, target=next_executor) + + # Next -> body (when has_next=True, loop back) + builder.add_edge( + source=next_executor, + target=body_entry, + condition=lambda msg: isinstance(msg, LoopIterationResult) and msg.has_next, + ) + + # Init -> join (when has_next=False, empty collection) + builder.add_edge( + source=init_executor, + target=join_executor, + condition=lambda msg: isinstance(msg, LoopIterationResult) and not msg.has_next, + ) + + # Next -> join (when has_next=False, loop complete) + builder.add_edge( + source=next_executor, + target=join_executor, + condition=lambda msg: isinstance(msg, LoopIterationResult) and not msg.has_next, + ) + + init_executor._exit_executor = join_executor # type: ignore[attr-defined] + return init_executor + + def _create_goto_reference( + self, + action_def: dict[str, Any], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any | None: + """Create a GotoAction executor that jumps to the target action. + + GotoAction creates a back-edge (or forward-edge) in the graph to the target action. + We create a pass-through executor and record the pending edge to be resolved + after all executors are created. + """ + from ._executors_control_flow import JoinExecutor + + target_id = action_def.get("target") or action_def.get("actionId") + + if not target_id: + return None + + # Create a pass-through executor for the goto + action_id = action_def.get("id") or f"goto_{target_id}_{self._action_index}" + self._action_index += 1 + + # Use JoinExecutor as a simple pass-through node + goto_executor = JoinExecutor(action_def, id=action_id) + self._executors[action_id] = goto_executor + + # Record pending goto edge to be resolved after all executors created + self._pending_gotos.append((goto_executor, target_id)) + + return goto_executor + + def _create_break_executor( + self, + action_def: dict[str, Any], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any | None: + """Create a break executor for loop control.""" + from ._executors_control_flow import BreakLoopExecutor + + if parent_context and "loop_next_executor" in parent_context: + loop_next = parent_context["loop_next_executor"] + action_id = action_def.get("id") or f"Break_{self._action_index}" + self._action_index += 1 + + executor = BreakLoopExecutor(action_def, loop_next.id, id=action_id) + self._executors[action_id] = executor + + # Wire break to loop next + builder.add_edge(source=executor, target=loop_next) + + return executor + + return None + + def _create_continue_executor( + self, + action_def: dict[str, Any], + builder: WorkflowBuilder, + parent_context: dict[str, Any] | None = None, + ) -> Any | None: + """Create a continue executor for loop control.""" + from ._executors_control_flow import ContinueLoopExecutor + + if parent_context and "loop_next_executor" in parent_context: + loop_next = parent_context["loop_next_executor"] + action_id = action_def.get("id") or f"Continue_{self._action_index}" + self._action_index += 1 + + executor = ContinueLoopExecutor(action_def, loop_next.id, id=action_id) + self._executors[action_id] = executor + + # Wire continue to loop next + builder.add_edge(source=executor, target=loop_next) + + return executor + + return None + + def _add_sequential_edge( + self, + builder: WorkflowBuilder, + source: Any, + target: Any, + ) -> None: + """Add a sequential edge between two executors. + + Handles control flow structures: + - If source is a structure (If/Switch), wire from all branch exits + - If target is a structure (If/Switch), wire with conditional edges to branches + """ + # Get all source exit points + source_exits = self._get_source_exits(source) + + # Wire each source exit to target + for source_exit in source_exits: + self._wire_to_target(builder, source_exit, target) + + def _get_source_exits(self, source: Any) -> list[Any]: + """Get all exit executors from a source (handles structures with multiple exits).""" + # Check if source is a structure with branch_exits + if hasattr(source, "branch_exits"): + # Collect all exits, recursively flattening nested structures + all_exits: list[Any] = [] + for exit_item in source.branch_exits: + if hasattr(exit_item, "branch_exits"): + # Nested structure - recurse + all_exits.extend(self._collect_all_exits(exit_item)) + else: + all_exits.append(exit_item) + return all_exits if all_exits else [] + + # Check if source has a single exit executor + actual_exit = getattr(source, "_exit_executor", source) + return [actual_exit] + + def _wire_to_target( + self, + builder: WorkflowBuilder, + source: Any, + target: Any, + ) -> None: + """Wire a single source executor to a target (which may be a structure).""" + # Check if target is an IfStructure (needs conditional edges) + if getattr(target, "_is_if_structure", False): + # Wire from source to then branch with then_condition + if target.then_entry: + self._add_conditional_edge(builder, source, target.then_entry, target.then_condition) + # If no then entry, the condition just doesn't match - no edge needed + # (the else condition will handle it) + + # Wire from source to else branch with else_condition + if target.else_entry: + self._add_conditional_edge(builder, source, target.else_entry, target.else_condition) + elif target.else_passthrough: + # No else actions - wire to the passthrough executor for continuation + self._add_conditional_edge(builder, source, target.else_passthrough, target.else_condition) + # If neither else_entry nor else_passthrough, unmatched conditions have no destination + + elif getattr(target, "_is_switch_structure", False): + # Wire from source to switch branches + for branch_entry, branch_condition in target.branches: + if branch_entry: + self._add_conditional_edge(builder, source, branch_entry, branch_condition) + + # Wire default/else branch + if target.default_entry: + self._add_conditional_edge(builder, source, target.default_entry, target.default_condition) + elif target.default_passthrough: + # No else actions - wire to the passthrough executor for continuation + self._add_conditional_edge(builder, source, target.default_passthrough, target.default_condition) + # If neither default_entry nor default_passthrough, unmatched conditions have no destination + + else: + # Normal sequential edge to a regular executor + builder.add_edge(source=source, target=target) + + def _add_conditional_edge( + self, + builder: WorkflowBuilder, + source: Any, + target: Any, + condition: Any, + ) -> None: + """Add a conditional edge, handling nested structures. + + If target is itself a structure (nested If/Switch), recursively wire + with combined conditions. + """ + if getattr(target, "_is_if_structure", False): + # Nested If - need to combine conditions + # Source --[condition AND then_condition]--> then_entry + # Source --[condition AND else_condition]--> else_entry + combined_then = self._combine_conditions(condition, target.then_condition) + combined_else = self._combine_conditions(condition, target.else_condition) + + if target.then_entry: + self._add_conditional_edge(builder, source, target.then_entry, combined_then) + + if target.else_entry: + self._add_conditional_edge(builder, source, target.else_entry, combined_else) + + elif getattr(target, "_is_switch_structure", False): + # Nested Switch - combine with each branch condition + for branch_entry, branch_condition in target.branches: + if branch_entry: + combined = self._combine_conditions(condition, branch_condition) + self._add_conditional_edge(builder, source, branch_entry, combined) + + if target.default_entry: + combined_default = self._combine_conditions(condition, target.default_condition) + self._add_conditional_edge(builder, source, target.default_entry, combined_default) + else: + # Regular executor - just add the edge with condition + builder.add_edge(source=source, target=target, async_condition=condition) + + def _combine_conditions( + self, outer_condition: ConditionFunc | None, inner_condition: ConditionFunc | None + ) -> ConditionFunc | None: + """Combine two async conditions with AND logic. + + Returns a new async condition that evaluates both conditions. + """ + if outer_condition is None: + return inner_condition + if inner_condition is None: + return outer_condition + + async def combined_condition(data: Any, shared_state: SharedState) -> bool: + outer_result = await outer_condition(data, shared_state) + if not outer_result: + return False + return await inner_condition(data, shared_state) + + return combined_condition + + def _get_branch_exit(self, branch_entry: Any) -> Any | None: + """Get the exit executor of a branch. + + For a linear sequence of actions, returns the last executor. + For nested structures, returns None (they have their own branch_exits). + + Args: + branch_entry: The first executor of the branch + + Returns: + The exit executor, or None if branch is empty or ends with a structure + """ + if branch_entry is None: + return None + + # Get the chain of executors in this branch + chain = getattr(branch_entry, "_chain_executors", [branch_entry]) + + if not chain: + return None + + last_executor = chain[-1] + + # Check if last executor is a structure with branch_exits + # In that case, we return the structure so its exits can be collected + if hasattr(last_executor, "branch_exits"): + return last_executor + + # Regular executor - get its exit point + return getattr(last_executor, "_exit_executor", last_executor) + + def _collect_all_exits(self, structure: Any) -> list[Any]: + """Recursively collect all exit executors from a structure.""" + exits: list[Any] = [] + + if not hasattr(structure, "branch_exits"): + # Not a structure - return the executor itself + actual_exit = getattr(structure, "_exit_executor", structure) + return [actual_exit] + + for exit_item in structure.branch_exits: + if hasattr(exit_item, "branch_exits"): + # Nested structure - recurse + exits.extend(self._collect_all_exits(exit_item)) + else: + exits.append(exit_item) + + return exits diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_agents.py b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_agents.py new file mode 100644 index 0000000000..1b9956330b --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_agents.py @@ -0,0 +1,778 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Agent invocation executors for declarative workflows. + +These executors handle invoking Azure AI Foundry agents and other AI agents, +supporting both streaming responses and human-in-loop patterns. + +Aligned with .NET's InvokeAzureAgentExecutor behavior including: +- Structured input with arguments and messages +- External loop support for human-in-loop patterns +- Output with messages and responseObject (JSON parsing) +- AutoSend behavior control +""" + +import contextlib +import json +import logging +import uuid +from dataclasses import dataclass, field +from typing import Any, cast + +from agent_framework import ChatMessage +from agent_framework._types import FunctionCallContent, FunctionResultContent +from agent_framework._workflows import ( + WorkflowContext, + handler, + response_handler, +) + +from ._base import ( + ActionComplete, + DeclarativeActionExecutor, + DeclarativeWorkflowState, +) + +logger = logging.getLogger(__name__) + +# Keys for agent-related state +AGENT_REGISTRY_KEY = "_agent_registry" +TOOL_REGISTRY_KEY = "_tool_registry" +# Key to store external loop state for resumption +EXTERNAL_LOOP_STATE_KEY = "_external_loop_state" + + +@dataclass +class AgentResult: + """Result from an agent invocation.""" + + success: bool + response: str + agent_name: str + messages: list[ChatMessage] = field(default_factory=lambda: cast(list[ChatMessage], [])) + tool_calls: list[FunctionCallContent] = field(default_factory=lambda: cast(list[FunctionCallContent], [])) + error: str | None = None + + +@dataclass +class AgentExternalInputRequest: + """Request for external input during agent invocation. + + Emitted when externalLoop.when condition evaluates to true, + signaling that the workflow should yield and wait for user input. + + This is the request type used with ctx.request_info() to implement + the Yield/Resume pattern for human-in-loop workflows. + """ + + request_id: str + agent_name: str + agent_response: str + iteration: int = 0 + messages: list[ChatMessage] = field(default_factory=lambda: cast(list[ChatMessage], [])) + function_calls: list[FunctionCallContent] = field(default_factory=lambda: cast(list[FunctionCallContent], [])) + + +@dataclass +class AgentExternalInputResponse: + """Response to an AgentExternalInputRequest. + + Provided by the caller to resume agent execution with new user input. + This is the response type expected by the response_handler. + """ + + user_input: str + messages: list[ChatMessage] = field(default_factory=lambda: cast(list[ChatMessage], [])) + function_results: dict[str, FunctionResultContent] = field( + default_factory=lambda: cast(dict[str, FunctionResultContent], {}) + ) + + +@dataclass +class ExternalLoopState: + """State saved for external loop resumption. + + Stored in shared_state to allow the response_handler to + continue the loop with the same configuration. + """ + + agent_name: str + iteration: int + external_loop_when: str + messages_var: str | None + response_obj_var: str | None + result_property: str | None + auto_send: bool + messages_path: str = "conversation.messages" + + +def _map_variable_to_path(variable: str) -> str: + """Map .NET-style variable names to state paths. + + Args: + variable: Variable name like 'Local.X' or 'System.ConversationId' + + Returns: + State path like 'turn.X' or 'system.ConversationId' + """ + if variable.startswith("Local."): + return "turn." + variable[6:] + if variable.startswith("System."): + return "system." + variable[7:] + if variable.startswith("Workflow."): + return "workflow." + variable[9:] + if "." in variable: + return variable + return "turn." + variable + + +class InvokeAzureAgentExecutor(DeclarativeActionExecutor): + """Executor that invokes an Azure AI Foundry agent. + + This executor supports both Python-style and .NET-style YAML schemas: + + Python-style (simple): + kind: InvokeAzureAgent + agent: MenuAgent + input: =turn.userInput + resultProperty: turn.agentResponse + + .NET-style (full featured): + kind: InvokeAzureAgent + agent: + name: AgentName + conversationId: =System.ConversationId + input: + arguments: + param1: =turn.value1 + param2: literal value + messages: =conversation.messages + externalLoop: + when: =turn.needsMoreInput + output: + messages: Local.ResponseMessages + responseObject: Local.StructuredResponse + autoSend: true + + Features: + - Structured input with arguments and messages + - External loop support for human-in-loop patterns + - Output with messages and responseObject (JSON parsing) + - AutoSend behavior control for streaming output + """ + + def __init__( + self, + action_def: dict[str, Any], + *, + id: str | None = None, + agents: dict[str, Any] | None = None, + ): + """Initialize the agent executor. + + Args: + action_def: The action definition from YAML + id: Optional executor ID + agents: Registry of agent instances by name + """ + super().__init__(action_def, id=id) + self._agents = agents or {} + + def _get_agent_name(self, state: Any) -> str | None: + """Extract agent name from action definition. + + Supports both simple string and nested object formats. + """ + agent_config = self._action_def.get("agent") + + if isinstance(agent_config, str): + return agent_config + + if isinstance(agent_config, dict): + agent_dict = cast(dict[str, Any], agent_config) + name = agent_dict.get("name") + if name is not None and isinstance(name, str): + # Support dynamic agent name from expression (would need async eval) + return str(name) + + agent_name = self._action_def.get("agentName") + return str(agent_name) if isinstance(agent_name, str) else None + + def _get_input_config(self) -> tuple[dict[str, Any], Any, str | None]: + """Parse input configuration. + + Returns: + Tuple of (arguments dict, messages expression, externalLoop.when expression) + """ + input_config = self._action_def.get("input", {}) + + if not isinstance(input_config, dict): + # Simple input - treat as message directly + return {}, input_config, None + + input_dict = cast(dict[str, Any], input_config) + arguments: dict[str, Any] = cast(dict[str, Any], input_dict.get("arguments", {})) + messages: Any = input_dict.get("messages") + + # Extract external loop configuration + external_loop_when: str | None = None + external_loop = input_dict.get("externalLoop") + if isinstance(external_loop, dict): + when_val = cast(dict[str, Any], external_loop).get("when") + external_loop_when = str(when_val) if when_val is not None else None + + return arguments, messages, external_loop_when + + def _get_output_config(self) -> tuple[str | None, str | None, str | None, bool]: + """Parse output configuration. + + Returns: + Tuple of (messages var, responseObject var, resultProperty, autoSend) + """ + output_config = self._action_def.get("output", {}) + + # Legacy Python-style + result_property: str | None = cast(str | None, self._action_def.get("resultProperty")) + + if not isinstance(output_config, dict): + return None, None, result_property, True + + output_dict = cast(dict[str, Any], output_config) + messages_var_val: Any = output_dict.get("messages") + messages_var: str | None = str(messages_var_val) if messages_var_val is not None else None + response_obj_val: Any = output_dict.get("responseObject") + response_obj_var: str | None = str(response_obj_val) if response_obj_val is not None else None + property_val: Any = output_dict.get("property") + property_var: str | None = str(property_val) if property_val is not None else None + auto_send_val: Any = output_dict.get("autoSend", True) + auto_send: bool = bool(auto_send_val) + + return messages_var, response_obj_var, property_var or result_property, auto_send + + def _get_conversation_id(self) -> str | None: + """Get the conversation ID expression from action definition. + + Returns: + The conversationId expression/value, or None if not specified + """ + return self._action_def.get("conversationId") + + async def _get_conversation_messages_path( + self, state: DeclarativeWorkflowState, conversation_id_expr: str | None + ) -> str: + """Get the state path for conversation messages. + + Args: + state: Workflow state for expression evaluation + conversation_id_expr: The conversationId expression from action definition + + Returns: + State path for messages (e.g., "conversation.messages" or "system.conversations.{id}.messages") + """ + if not conversation_id_expr: + return "conversation.messages" + + # Evaluate the conversation ID expression + evaluated_id = await state.eval_if_expression(conversation_id_expr) + if not evaluated_id: + return "conversation.messages" + + # Use conversation-specific messages path + return f"system.conversations.{evaluated_id}.messages" + + async def _build_input_text(self, state: Any, arguments: dict[str, Any], messages_expr: Any) -> str: + """Build input text from arguments and messages. + + Args: + state: Workflow state for expression evaluation + arguments: Input arguments to evaluate + messages_expr: Messages expression or direct input + + Returns: + Input text for the agent + """ + # Evaluate arguments + evaluated_args: dict[str, Any] = {} + for key, value in arguments.items(): + evaluated_args[key] = await state.eval_if_expression(value) + + # Evaluate messages/input + if messages_expr: + evaluated_input: Any = await state.eval_if_expression(messages_expr) + if isinstance(evaluated_input, str): + return evaluated_input + if isinstance(evaluated_input, list) and evaluated_input: + # Extract text from last message + last: Any = evaluated_input[-1] + if isinstance(last, str): + return last + if isinstance(last, dict): + last_dict = cast(dict[str, Any], last) + content_val: Any = last_dict.get("content", last_dict.get("text", "")) + return str(content_val) if content_val else "" + if last is not None and hasattr(last, "text"): + return str(getattr(last, "text", "")) + if evaluated_input: + return str(cast(Any, evaluated_input)) + return "" + + # Fallback chain for implicit input (like .NET conversationId pattern): + # 1. turn.input / turn.userInput (explicit turn state) + # 2. system.LastMessage.Text (previous agent's response) + # 3. workflow.inputs (first agent gets workflow inputs) + input_text: str = str(await state.get("turn.input") or await state.get("turn.userInput") or "") + if not input_text: + # Try system.LastMessage.Text (used by external loop and agent chaining) + last_message: Any = await state.get("system.LastMessage") + if isinstance(last_message, dict): + last_msg_dict = cast(dict[str, Any], last_message) + text_val: Any = last_msg_dict.get("Text", "") + input_text = str(text_val) if text_val else "" + if not input_text: + # Fall back to workflow inputs (for first agent in chain) + inputs: Any = await state.get("workflow.inputs") + if isinstance(inputs, dict): + inputs_dict = cast(dict[str, Any], inputs) + # If single input, use its value directly + if len(inputs_dict) == 1: + input_text = str(next(iter(inputs_dict.values()))) + else: + # Multiple inputs - format as key: value pairs + input_text = "\n".join(f"{k}: {v}" for k, v in inputs_dict.items()) + return input_text if input_text else "" + + def _get_agent(self, agent_name: str, ctx: WorkflowContext[Any, Any]) -> Any: + """Get agent from registry (sync helper for response handler).""" + return self._agents.get(agent_name) if self._agents else None + + async def _invoke_agent_and_store_results( + self, + agent: Any, + agent_name: str, + input_text: str, + state: DeclarativeWorkflowState, + ctx: WorkflowContext[ActionComplete, str], + messages_var: str | None, + response_obj_var: str | None, + result_property: str | None, + auto_send: bool, + messages_path: str = "conversation.messages", + ) -> tuple[str, list[Any], list[Any]]: + """Invoke agent and store results in state. + + Args: + agent: The agent instance to invoke + agent_name: Name of the agent for logging + input_text: User input text + state: Workflow state + ctx: Workflow context + messages_var: Output variable for messages + response_obj_var: Output variable for parsed response object + result_property: Output property for result + auto_send: Whether to auto-send output to context + messages_path: State path for conversation messages (default: "conversation.messages") + + Returns: + Tuple of (accumulated_response, all_messages, tool_calls) + """ + accumulated_response = "" + all_messages: list[ChatMessage] = [] + tool_calls: list[FunctionCallContent] = [] + + # Get conversation history from state using the specified path + conversation_history: list[ChatMessage] = await state.get(messages_path) or [] + + # Add user input to conversation history + if input_text: + user_message = ChatMessage(role="user", text=input_text) + conversation_history.append(user_message) + await state.append(messages_path, user_message) + + # Build messages list for agent (use history if available, otherwise just input) + messages_for_agent: list[ChatMessage] | str = conversation_history if conversation_history else input_text + + # Use run() method to get properly structured messages (including tool calls and results) + # This is critical for multi-turn conversations where tool calls must be followed + # by their results in the message history + if hasattr(agent, "run"): + result: Any = await agent.run(messages_for_agent) + if hasattr(result, "text") and result.text: + accumulated_response = str(result.text) + if auto_send: + await ctx.yield_output(str(result.text)) + elif isinstance(result, str): + accumulated_response = result + if auto_send: + await ctx.yield_output(result) + + if not isinstance(result, str): + result_messages: Any = getattr(result, "messages", None) + if result_messages is not None: + all_messages = list(cast(list[ChatMessage], result_messages)) + result_tool_calls: Any = getattr(result, "tool_calls", None) + if result_tool_calls is not None: + tool_calls = list(cast(list[FunctionCallContent], result_tool_calls)) + + else: + raise RuntimeError(f"Agent '{agent_name}' has no run or run_stream method") + + # Add messages to conversation history + # We need to include ALL messages from the agent run (including tool calls and tool results) + # to maintain proper conversation state for the next agent invocation + if all_messages: + # Agent returned full message history - use it + for msg in all_messages: + await state.append(messages_path, msg) + elif accumulated_response: + # No messages returned, create a simple assistant message + assistant_message = ChatMessage(role="assistant", text=accumulated_response) + await state.append(messages_path, assistant_message) + + # Store results in state + await state.set("agent.response", accumulated_response) + await state.set("agent.name", agent_name) + + # Store System.LastMessage for externalLoop.when condition evaluation + await state.set("system.LastMessage", {"Text": accumulated_response}) + + # Store in output variables (.NET style) + if messages_var: + output_path = _map_variable_to_path(messages_var) + await state.set(output_path, all_messages if all_messages else accumulated_response) + + if response_obj_var: + output_path = _map_variable_to_path(response_obj_var) + # Try to parse as JSON for structured output + try: + parsed = json.loads(accumulated_response) if accumulated_response else None + await state.set(output_path, parsed) + except (json.JSONDecodeError, TypeError): + await state.set(output_path, accumulated_response) + + # Store in result property (Python style) + if result_property: + await state.set(result_property, accumulated_response) + + return accumulated_response, all_messages, tool_calls + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete, str], + ) -> None: + """Handle the agent invocation with full .NET feature parity. + + When externalLoop.when is configured and evaluates to true after agent response, + this method emits an AgentExternalInputRequest via ctx.request_info() and returns. + The workflow will yield, and when the caller provides a response via + send_responses_streaming(), the handle_external_input_response handler + will continue the loop. + """ + state = await self._ensure_state_initialized(ctx, trigger) + + # Parse configuration + agent_name = self._get_agent_name(state) + if not agent_name: + logger.warning("InvokeAzureAgent action missing 'agent' or 'agent.name' property") + await ctx.send_message(ActionComplete()) + return + + arguments, messages_expr, external_loop_when = self._get_input_config() + messages_var, response_obj_var, result_property, auto_send = self._get_output_config() + + # Get conversation-specific messages path if conversationId is specified + conversation_id_expr = self._get_conversation_id() + messages_path = await self._get_conversation_messages_path(state, conversation_id_expr) + + # Build input + input_text = await self._build_input_text(state, arguments, messages_expr) + + # Get agent from registry + agent: Any = self._agents.get(agent_name) if self._agents else None + if agent is None: + try: + agent_registry: dict[str, Any] | None = await ctx.shared_state.get(AGENT_REGISTRY_KEY) + except KeyError: + agent_registry = {} + agent = agent_registry.get(agent_name) if agent_registry else None + + if agent is None: + error_msg = f"Agent '{agent_name}' not found in registry" + logger.error(f"InvokeAzureAgent: {error_msg}") + await state.set("agent.error", error_msg) + if result_property: + await state.set(result_property, {"error": error_msg}) + await ctx.send_message(ActionComplete()) + return + + iteration = 0 + + try: + accumulated_response, all_messages, tool_calls = await self._invoke_agent_and_store_results( + agent=agent, + agent_name=agent_name, + input_text=input_text, + state=state, + ctx=ctx, + messages_var=messages_var, + response_obj_var=response_obj_var, + result_property=result_property, + auto_send=auto_send, + messages_path=messages_path, + ) + except Exception as e: + logger.error(f"InvokeAzureAgent: error invoking agent '{agent_name}': {e}") + await state.set("agent.error", str(e)) + if result_property: + await state.set(result_property, {"error": str(e)}) + await ctx.send_message(ActionComplete()) + return + + # Check external loop condition + if external_loop_when: + should_continue = await state.eval(external_loop_when) + should_continue = bool(should_continue) if should_continue is not None else False + + logger.debug( + f"InvokeAzureAgent: external loop condition '{str(external_loop_when)[:50]}' = " + f"{should_continue} (iteration {iteration})" + ) + + if should_continue: + # Save loop state for resumption + loop_state = ExternalLoopState( + agent_name=agent_name, + iteration=iteration + 1, + external_loop_when=external_loop_when, + messages_var=messages_var, + response_obj_var=response_obj_var, + result_property=result_property, + auto_send=auto_send, + messages_path=messages_path, + ) + await ctx.shared_state.set(EXTERNAL_LOOP_STATE_KEY, loop_state) + + # Emit request for external input - workflow will yield here + request = AgentExternalInputRequest( + request_id=str(uuid.uuid4()), + agent_name=agent_name, + agent_response=accumulated_response, + iteration=iteration, + messages=all_messages, + function_calls=tool_calls, + ) + logger.info(f"InvokeAzureAgent: yielding for external input (iteration {iteration})") + await ctx.request_info(request, AgentExternalInputResponse) + # Return without sending ActionComplete - workflow yields + return + + # No external loop or condition is false - complete the action + await ctx.send_message(ActionComplete()) + + @response_handler + async def handle_external_input_response( + self, + original_request: AgentExternalInputRequest, + response: AgentExternalInputResponse, + ctx: WorkflowContext[ActionComplete, str], + ) -> None: + """Handle response to an AgentExternalInputRequest and continue the loop. + + This is called when the workflow resumes after yielding for external input. + It continues the agent invocation loop with the user's new input. + """ + state = self._get_state(ctx.shared_state) + + # Retrieve saved loop state + try: + loop_state: ExternalLoopState = await ctx.shared_state.get(EXTERNAL_LOOP_STATE_KEY) + except KeyError: + logger.error("InvokeAzureAgent: external loop state not found, cannot resume") + await ctx.send_message(ActionComplete()) + return + + agent_name = loop_state.agent_name + iteration = loop_state.iteration + external_loop_when = loop_state.external_loop_when + max_iterations = 100 + + # Get the user's new input + input_text = response.user_input + + # Store the user input in state for condition evaluation + await state.set("turn.userInput", input_text) + await state.set("system.LastMessage", {"Text": input_text}) + + # Check if we should continue BEFORE invoking the agent + # This matches .NET behavior where the condition checks the user's input + should_continue = await state.eval(external_loop_when) + should_continue = bool(should_continue) if should_continue is not None else False + + logger.debug( + f"InvokeAzureAgent: external loop condition '{str(external_loop_when)[:50]}' = " + f"{should_continue} (iteration {iteration}) for input '{input_text[:30]}...'" + ) + + if not should_continue: + # User input caused loop to exit - clean up and complete + with contextlib.suppress(KeyError): + await ctx.shared_state.delete(EXTERNAL_LOOP_STATE_KEY) + await ctx.send_message(ActionComplete()) + return + + # Get agent from registry + agent: Any = self._agents.get(agent_name) if self._agents else None + if agent is None: + try: + agent_registry: dict[str, Any] | None = await ctx.shared_state.get(AGENT_REGISTRY_KEY) + except KeyError: + agent_registry = {} + agent = agent_registry.get(agent_name) if agent_registry else None + + if agent is None: + logger.error(f"InvokeAzureAgent: agent '{agent_name}' not found during loop resumption") + await ctx.send_message(ActionComplete()) + return + + try: + accumulated_response, all_messages, tool_calls = await self._invoke_agent_and_store_results( + agent=agent, + agent_name=agent_name, + input_text=input_text, + state=state, + ctx=ctx, + messages_var=loop_state.messages_var, + response_obj_var=loop_state.response_obj_var, + result_property=loop_state.result_property, + auto_send=loop_state.auto_send, + messages_path=loop_state.messages_path, + ) + except Exception as e: + logger.error(f"InvokeAzureAgent: error invoking agent '{agent_name}' during loop: {e}") + await state.set("agent.error", str(e)) + await ctx.send_message(ActionComplete()) + return + + # Re-evaluate the condition AFTER the agent responds + # This is critical: the agent's response may have set NeedsTicket=true or IsResolved=true + should_continue = await state.eval(external_loop_when) + should_continue = bool(should_continue) if should_continue is not None else False + + logger.debug( + f"InvokeAzureAgent: external loop condition after response '{str(external_loop_when)[:50]}' = " + f"{should_continue} (iteration {iteration})" + ) + + if not should_continue: + # Agent response caused loop to exit (e.g., NeedsTicket=true or IsResolved=true) + logger.info( + "InvokeAzureAgent: external loop exited due to condition=false " + "(sending ActionComplete to continue workflow)" + ) + with contextlib.suppress(KeyError): + await ctx.shared_state.delete(EXTERNAL_LOOP_STATE_KEY) + await ctx.send_message(ActionComplete()) + return + + # Continue the loop - condition still true + if iteration < max_iterations: + # Update loop state for next iteration + loop_state.iteration = iteration + 1 + await ctx.shared_state.set(EXTERNAL_LOOP_STATE_KEY, loop_state) + + # Emit another request for external input + request = AgentExternalInputRequest( + request_id=str(uuid.uuid4()), + agent_name=agent_name, + agent_response=accumulated_response, + iteration=iteration, + messages=all_messages, + function_calls=tool_calls, + ) + logger.info(f"InvokeAzureAgent: yielding for external input (iteration {iteration})") + await ctx.request_info(request, AgentExternalInputResponse) + return + + logger.warning(f"InvokeAzureAgent: external loop exceeded max iterations ({max_iterations})") + + # Loop complete - clean up and send completion + with contextlib.suppress(KeyError): + await ctx.shared_state.delete(EXTERNAL_LOOP_STATE_KEY) + + await ctx.send_message(ActionComplete()) + + +class InvokeToolExecutor(DeclarativeActionExecutor): + """Executor that invokes a registered tool/function. + + Tools are simpler than agents - they take input, perform an action, + and return a result synchronously (or with a simple async call). + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the tool invocation.""" + state = await self._ensure_state_initialized(ctx, trigger) + + tool_name = self._action_def.get("tool") or self._action_def.get("toolName", "") + input_expr = self._action_def.get("input") + output_property = self._action_def.get("output", {}).get("property") or self._action_def.get("resultProperty") + parameters = self._action_def.get("parameters", {}) + + # Get tools registry + try: + tool_registry: dict[str, Any] | None = await ctx.shared_state.get(TOOL_REGISTRY_KEY) + except KeyError: + tool_registry = {} + + tool: Any = tool_registry.get(tool_name) if tool_registry else None + + if tool is None: + error_msg = f"Tool '{tool_name}' not found in registry" + if output_property: + await state.set(output_property, {"error": error_msg}) + await ctx.send_message(ActionComplete()) + return + + # Build parameters + params: dict[str, Any] = {} + for param_name, param_expression in parameters.items(): + params[param_name] = await state.eval_if_expression(param_expression) + + # Add main input if specified + if input_expr: + params["input"] = await state.eval_if_expression(input_expr) + + try: + # Invoke the tool + if callable(tool): + import inspect + + if inspect.iscoroutinefunction(tool): + result = await tool(**params) + else: + result = tool(**params) + + # Store result + if output_property: + await state.set(output_property, result) + + except Exception as e: + if output_property: + await state.set(output_property, {"error": str(e)}) + await ctx.send_message(ActionComplete()) + return + + await ctx.send_message(ActionComplete()) + + +# Mapping of agent action kinds to executor classes +AGENT_ACTION_EXECUTORS: dict[str, type[DeclarativeActionExecutor]] = { + "InvokeAzureAgent": InvokeAzureAgentExecutor, + "InvokeTool": InvokeToolExecutor, +} diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_basic.py b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_basic.py new file mode 100644 index 0000000000..545c8aeedd --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_basic.py @@ -0,0 +1,274 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Basic action executors for the graph-based declarative workflow system. + +These executors handle simple actions like SetValue, SendActivity, etc. +Each action becomes a node in the workflow graph. +""" + +from typing import Any + +from agent_framework._workflows import ( + WorkflowContext, + handler, +) + +from ._base import ( + ActionComplete, + DeclarativeActionExecutor, +) + + +def _get_variable_path(action_def: dict[str, Any], key: str = "variable") -> str | None: + """Extract variable path from action definition. + + Supports .NET style (variable: Local.VarName) and nested object style (variable: {path: ...}). + """ + variable = action_def.get(key) + if isinstance(variable, str): + return variable + if isinstance(variable, dict): + return variable.get("path") + return action_def.get("path") + + +class SetValueExecutor(DeclarativeActionExecutor): + """Executor for the SetValue action. + + Sets a value in the workflow state at a specified path. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the SetValue action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + path = self._action_def.get("path") + value = self._action_def.get("value") + + if path: + # Evaluate value if it's an expression + evaluated_value = await state.eval_if_expression(value) + await state.set(path, evaluated_value) + + await ctx.send_message(ActionComplete()) + + +class SetVariableExecutor(DeclarativeActionExecutor): + """Executor for the SetVariable action (.NET style naming).""" + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the SetVariable action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + path = _get_variable_path(self._action_def) + value = self._action_def.get("value") + + if path: + evaluated_value = await state.eval_if_expression(value) + await state.set(path, evaluated_value) + + await ctx.send_message(ActionComplete()) + + +class SetTextVariableExecutor(DeclarativeActionExecutor): + """Executor for the SetTextVariable action.""" + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the SetTextVariable action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + path = _get_variable_path(self._action_def) + text = self._action_def.get("text", "") + + if path: + evaluated_text = await state.eval_if_expression(text) + await state.set(path, str(evaluated_text) if evaluated_text is not None else "") + + await ctx.send_message(ActionComplete()) + + +class SetMultipleVariablesExecutor(DeclarativeActionExecutor): + """Executor for the SetMultipleVariables action.""" + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the SetMultipleVariables action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + assignments = self._action_def.get("assignments", []) + for assignment in assignments: + variable = assignment.get("variable") + path: str | None + if isinstance(variable, str): + path = variable + elif isinstance(variable, dict): + path = variable.get("path") + else: + path = assignment.get("path") + value = assignment.get("value") + if path: + evaluated_value = await state.eval_if_expression(value) + await state.set(path, evaluated_value) + + await ctx.send_message(ActionComplete()) + + +class AppendValueExecutor(DeclarativeActionExecutor): + """Executor for the AppendValue action.""" + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the AppendValue action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + path = self._action_def.get("path") + value = self._action_def.get("value") + + if path: + evaluated_value = await state.eval_if_expression(value) + await state.append(path, evaluated_value) + + await ctx.send_message(ActionComplete()) + + +class ResetVariableExecutor(DeclarativeActionExecutor): + """Executor for the ResetVariable action.""" + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the ResetVariable action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + path = _get_variable_path(self._action_def) + + if path: + # Reset to None/empty + await state.set(path, None) + + await ctx.send_message(ActionComplete()) + + +class ClearAllVariablesExecutor(DeclarativeActionExecutor): + """Executor for the ClearAllVariables action.""" + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Handle the ClearAllVariables action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + # Get state data and clear turn variables + state_data = await state.get_state_data() + state_data["turn"] = {} + await state.set_state_data(state_data) + + await ctx.send_message(ActionComplete()) + + +class SendActivityExecutor(DeclarativeActionExecutor): + """Executor for the SendActivity action. + + Sends a text message or activity as workflow output. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete, str], + ) -> None: + """Handle the SendActivity action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + activity = self._action_def.get("activity", "") + + # Activity can be a string directly or a dict with a "text" field + text = activity.get("text", "") if isinstance(activity, dict) else activity + + if isinstance(text, str): + # First evaluate any =expression syntax + text = await state.eval_if_expression(text) + # Then interpolate any {Variable.Path} template syntax + if isinstance(text, str): + text = await state.interpolate_string(text) + + # Yield the text as workflow output + if text: + await ctx.yield_output(str(text)) + + await ctx.send_message(ActionComplete()) + + +class EmitEventExecutor(DeclarativeActionExecutor): + """Executor for the EmitEvent action. + + Emits a custom event to the workflow event stream. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete, dict[str, Any]], + ) -> None: + """Handle the EmitEvent action.""" + state = await self._ensure_state_initialized(ctx, trigger) + + event_name = self._action_def.get("eventName", "") + event_value = self._action_def.get("eventValue") + + if event_name: + evaluated_name = await state.eval_if_expression(event_name) + evaluated_value = await state.eval_if_expression(event_value) + + event_data = { + "eventName": evaluated_name, + "eventValue": evaluated_value, + } + await ctx.yield_output(event_data) + + await ctx.send_message(ActionComplete()) + + +# Mapping of action kinds to executor classes +BASIC_ACTION_EXECUTORS: dict[str, type[DeclarativeActionExecutor]] = { + "SetValue": SetValueExecutor, + "SetVariable": SetVariableExecutor, + "SetTextVariable": SetTextVariableExecutor, + "SetMultipleVariables": SetMultipleVariablesExecutor, + "AppendValue": AppendValueExecutor, + "ResetVariable": ResetVariableExecutor, + "ClearAllVariables": ClearAllVariablesExecutor, + "SendActivity": SendActivityExecutor, + "EmitEvent": EmitEventExecutor, +} diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_control_flow.py b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_control_flow.py new file mode 100644 index 0000000000..a57b9d1837 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_control_flow.py @@ -0,0 +1,299 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Control flow executors for the graph-based declarative workflow system. + +Control flow in the graph-based system is handled differently than the interpreter: +- If/Switch: Condition evaluation happens on edges via async conditions +- Foreach: Loop iteration state managed in SharedState + loop edges +- Goto: Edge to target action (handled by builder) +- Break/Continue: Special signals for loop control + +The key insight is that control flow becomes GRAPH STRUCTURE, not executor logic. +Conditions are evaluated on edges, not in separate executor nodes. +""" + +from typing import Any + +from agent_framework._workflows import ( + WorkflowContext, + handler, +) + +from ._base import ( + ActionComplete, + ActionTrigger, + DeclarativeActionExecutor, + LoopControl, + LoopIterationResult, +) + +# Keys for loop state in SharedState +LOOP_STATE_KEY = "_declarative_loop_state" + + +class ForeachInitExecutor(DeclarativeActionExecutor): + """Initializes a foreach loop. + + Sets up the loop state in SharedState and determines if there are items. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[LoopIterationResult], + ) -> None: + """Initialize the loop and check for first item.""" + state = await self._ensure_state_initialized(ctx, trigger) + + items_expr = self._action_def.get("itemsSource") or self._action_def.get("items") + items_raw: Any = await state.eval_if_expression(items_expr) or [] + + items: list[Any] + items = (list(items_raw) if items_raw else []) if not isinstance(items_raw, (list, tuple)) else list(items_raw) # type: ignore + + loop_id = self.id + + # Store loop state + state_data = await state.get_state_data() + loop_states = state_data.setdefault(LOOP_STATE_KEY, {}) + loop_states[loop_id] = { + "items": items, + "index": 0, + "length": len(items), + } + await state.set_state_data(state_data) + + # Check if we have items + if items: + # Set the iteration variable + item_var = self._action_def.get("iteratorVariable") or self._action_def.get("item", "turn.item") + index_var = self._action_def.get("indexVariable") or self._action_def.get("index") + + await state.set(item_var, items[0]) + if index_var: + await state.set(index_var, 0) + + await ctx.send_message(LoopIterationResult(has_next=True, current_item=items[0], current_index=0)) + else: + await ctx.send_message(LoopIterationResult(has_next=False)) + + +class ForeachNextExecutor(DeclarativeActionExecutor): + """Advances to the next item in a foreach loop. + + This executor is triggered after the loop body completes. + """ + + def __init__( + self, + action_def: dict[str, Any], + init_executor_id: str, + *, + id: str | None = None, + ): + """Initialize with reference to the init executor. + + Args: + action_def: The Foreach action definition + init_executor_id: ID of the corresponding ForeachInitExecutor + id: Optional executor ID + """ + super().__init__(action_def, id=id) + self._init_executor_id = init_executor_id + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[LoopIterationResult], + ) -> None: + """Advance to next item and send result.""" + state = await self._ensure_state_initialized(ctx, trigger) + + loop_id = self._init_executor_id + + # Get loop state + state_data = await state.get_state_data() + loop_states = state_data.get(LOOP_STATE_KEY, {}) + loop_state = loop_states.get(loop_id) + + if not loop_state: + # No loop state - shouldn't happen but handle gracefully + await ctx.send_message(LoopIterationResult(has_next=False)) + return + + items = loop_state["items"] + current_index = loop_state["index"] + 1 + + if current_index < len(items): + # Update loop state + loop_state["index"] = current_index + await state.set_state_data(state_data) + + # Set the iteration variable + item_var = self._action_def.get("iteratorVariable") or self._action_def.get("item", "turn.item") + index_var = self._action_def.get("indexVariable") or self._action_def.get("index") + + await state.set(item_var, items[current_index]) + if index_var: + await state.set(index_var, current_index) + + await ctx.send_message( + LoopIterationResult(has_next=True, current_item=items[current_index], current_index=current_index) + ) + else: + # Loop complete - clean up + del loop_states[loop_id] + await state.set_state_data(state_data) + + await ctx.send_message(LoopIterationResult(has_next=False)) + + @handler + async def handle_loop_control( + self, + control: LoopControl, + ctx: WorkflowContext[LoopIterationResult], + ) -> None: + """Handle break/continue signals.""" + state = self._get_state(ctx.shared_state) + + if control.action == "break": + # Clean up loop state and signal done + state_data = await state.get_state_data() + loop_states = state_data.get(LOOP_STATE_KEY, {}) + if self._init_executor_id in loop_states: + del loop_states[self._init_executor_id] + await state.set_state_data(state_data) + + await ctx.send_message(LoopIterationResult(has_next=False)) + + elif control.action == "continue": + # Just advance to next iteration + await self.handle_action(ActionTrigger(), ctx) + + +class BreakLoopExecutor(DeclarativeActionExecutor): + """Executor for BreakLoop action. + + Sends a LoopControl signal to break out of the enclosing loop. + """ + + def __init__( + self, + action_def: dict[str, Any], + loop_next_executor_id: str, + *, + id: str | None = None, + ): + """Initialize with reference to the loop's next executor. + + Args: + action_def: The action definition + loop_next_executor_id: ID of the ForeachNextExecutor to signal + id: Optional executor ID + """ + super().__init__(action_def, id=id) + self._loop_next_executor_id = loop_next_executor_id + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[LoopControl], + ) -> None: + """Send break signal to the loop.""" + await ctx.send_message(LoopControl(action="break")) + + +class ContinueLoopExecutor(DeclarativeActionExecutor): + """Executor for ContinueLoop action. + + Sends a LoopControl signal to continue to next iteration. + """ + + def __init__( + self, + action_def: dict[str, Any], + loop_next_executor_id: str, + *, + id: str | None = None, + ): + """Initialize with reference to the loop's next executor. + + Args: + action_def: The action definition + loop_next_executor_id: ID of the ForeachNextExecutor to signal + id: Optional executor ID + """ + super().__init__(action_def, id=id) + self._loop_next_executor_id = loop_next_executor_id + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[LoopControl], + ) -> None: + """Send continue signal to the loop.""" + await ctx.send_message(LoopControl(action="continue")) + + +class EndWorkflowExecutor(DeclarativeActionExecutor): + """Executor for EndWorkflow/EndDialog action. + + This executor simply doesn't send any message, causing the workflow + to terminate at this point. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """End the workflow by not sending any continuation message.""" + # Don't send ActionComplete - workflow ends here + pass + + +class EndConversationExecutor(DeclarativeActionExecutor): + """Executor for EndConversation action.""" + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """End the conversation.""" + # For now, just don't continue + # In a full implementation, this would signal to close the conversation + pass + + +# Passthrough executor for joining control flow branches +class JoinExecutor(DeclarativeActionExecutor): + """Executor that joins multiple branches back together. + + Used after If/Switch to merge control flow back to a single path. + """ + + @handler + async def handle_action( + self, + trigger: dict[str, Any] | str | ActionTrigger | ActionComplete | LoopIterationResult, + ctx: WorkflowContext[ActionComplete], + ) -> None: + """Simply pass through to continue the workflow.""" + await ctx.send_message(ActionComplete()) + + +# Mapping of control flow action kinds to executor classes +# Note: Most control flow is handled by the builder creating graph structure, +# these are the executors that are part of that structure +CONTROL_FLOW_EXECUTORS: dict[str, type[DeclarativeActionExecutor]] = { + "EndWorkflow": EndWorkflowExecutor, + "EndDialog": EndWorkflowExecutor, + "EndConversation": EndConversationExecutor, +} diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_human_input.py b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_human_input.py new file mode 100644 index 0000000000..6a4aa90241 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_graph/_executors_human_input.py @@ -0,0 +1,260 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Human-in-loop executors for declarative workflows. + +These executors handle interactions that require human input, using the +RequestInfo pattern to pause the workflow and wait for responses. +""" + +from dataclasses import dataclass +from typing import Any + +from agent_framework._workflows import ( + WorkflowContext, + handler, +) + +from ._base import ( + ActionComplete, + DeclarativeActionExecutor, +) + + +@dataclass +class QuestionChoice: + """A choice option for a question.""" + + value: str + label: str | None = None + + +@dataclass +class HumanInputRequest: + """Request for human input (triggers workflow pause).""" + + request_type: str + message: str + metadata: dict[str, Any] + + +class QuestionExecutor(DeclarativeActionExecutor): + """Executor that asks the user a question and waits for a response. + + This uses the workflow's request_info mechanism to pause execution until + the user provides an answer. The response is stored in workflow state. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete, HumanInputRequest], + ) -> None: + """Ask the question and wait for a response.""" + state = await self._ensure_state_initialized(ctx, trigger) + + question_text = self._action_def.get("text") or self._action_def.get("question", "") + output_property = self._action_def.get("output", {}).get("property") or self._action_def.get( + "property", "turn.answer" + ) + choices = self._action_def.get("choices", []) + default_value = self._action_def.get("defaultValue") + allow_free_text = self._action_def.get("allowFreeText", True) + + # Evaluate the question text if it's an expression + evaluated_question = await state.eval_if_expression(question_text) + + # Build choices metadata + choices_data: list[dict[str, str]] | None = None + if choices: + choices_data = [] + for c in choices: + if isinstance(c, dict): + c_dict: dict[str, Any] = dict(c) # type: ignore[arg-type] + choices_data.append({ + "value": c_dict.get("value", ""), + "label": c_dict.get("label") or c_dict.get("value", ""), + }) + else: + choices_data.append({"value": str(c), "label": str(c)}) + + # Yield the request for human input + # The workflow runtime will pause here and return the response when provided + await ctx.yield_output( + HumanInputRequest( + request_type="question", + message=str(evaluated_question), + metadata={ + "output_property": output_property, + "choices": choices_data, + "allow_free_text": allow_free_text, + "default_value": default_value, + }, + ) + ) + + # Note: In a full implementation, the workflow would pause here + # and resume with the response. For now, we just use default. + answer = default_value + + # Store the answer + if output_property: + await state.set(output_property, answer) + + await ctx.send_message(ActionComplete()) + + +class ConfirmationExecutor(DeclarativeActionExecutor): + """Executor that asks for a yes/no confirmation. + + This is a specialized version of Question that returns a boolean. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete, HumanInputRequest], + ) -> None: + """Ask for confirmation.""" + state = await self._ensure_state_initialized(ctx, trigger) + + message = self._action_def.get("text") or self._action_def.get("message", "") + output_property = self._action_def.get("output", {}).get("property") or self._action_def.get( + "property", "turn.confirmed" + ) + yes_label = self._action_def.get("yesLabel", "Yes") + no_label = self._action_def.get("noLabel", "No") + default_value = self._action_def.get("defaultValue", False) + + # Evaluate the message if it's an expression + evaluated_message = await state.eval_if_expression(message) + + # Yield the request for confirmation + await ctx.yield_output( + HumanInputRequest( + request_type="confirmation", + message=str(evaluated_message), + metadata={ + "output_property": output_property, + "yes_label": yes_label, + "no_label": no_label, + "default_value": default_value, + }, + ) + ) + + # Store the default value + if output_property: + await state.set(output_property, default_value) + + await ctx.send_message(ActionComplete()) + + +class WaitForInputExecutor(DeclarativeActionExecutor): + """Executor that waits for user input during a conversation turn. + + This is used when the workflow needs to pause and wait for the next + user message in a conversational flow. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete, HumanInputRequest | str], + ) -> None: + """Wait for user input.""" + state = await self._ensure_state_initialized(ctx, trigger) + + prompt = self._action_def.get("prompt") + output_property = self._action_def.get("output", {}).get("property") or self._action_def.get( + "property", "turn.input" + ) + timeout_seconds = self._action_def.get("timeout") + + # Emit prompt if specified + if prompt: + evaluated_prompt = await state.eval_if_expression(prompt) + await ctx.yield_output(str(evaluated_prompt)) + + # Yield the request for input + await ctx.yield_output( + HumanInputRequest( + request_type="user_input", + message=str(prompt) if prompt else "Waiting for input...", + metadata={ + "output_property": output_property, + "timeout_seconds": timeout_seconds, + }, + ) + ) + + # Store empty input (will be populated when workflow resumes) + if output_property: + await state.set(output_property, "") + + await ctx.send_message(ActionComplete()) + + +class RequestExternalInputExecutor(DeclarativeActionExecutor): + """Executor that requests external input/approval. + + This is used for more complex external integrations beyond simple questions, + such as approval workflows, document uploads, or external system integrations. + """ + + @handler + async def handle_action( + self, + trigger: Any, + ctx: WorkflowContext[ActionComplete, HumanInputRequest], + ) -> None: + """Request external input.""" + state = await self._ensure_state_initialized(ctx, trigger) + + request_type = self._action_def.get("requestType", "external") + message = self._action_def.get("message", "") + output_property = self._action_def.get("output", {}).get("property") or self._action_def.get( + "property", "turn.externalInput" + ) + timeout_seconds = self._action_def.get("timeout") + required_fields = self._action_def.get("requiredFields", []) + metadata = self._action_def.get("metadata", {}) + + # Evaluate the message if it's an expression + evaluated_message = await state.eval_if_expression(message) + + # Build request metadata + request_metadata: dict[str, Any] = { + **metadata, + "output_property": output_property, + "required_fields": required_fields, + } + + if timeout_seconds: + request_metadata["timeout_seconds"] = timeout_seconds + + # Yield the request + await ctx.yield_output( + HumanInputRequest( + request_type=request_type, + message=str(evaluated_message), + metadata=request_metadata, + ) + ) + + # Store None (will be populated when workflow resumes) + if output_property: + await state.set(output_property, None) + + await ctx.send_message(ActionComplete()) + + +# Mapping of human input action kinds to executor classes +HUMAN_INPUT_EXECUTORS: dict[str, type[DeclarativeActionExecutor]] = { + "Question": QuestionExecutor, + "Confirmation": ConfirmationExecutor, + "WaitForInput": WaitForInputExecutor, + "RequestExternalInput": RequestExternalInputExecutor, +} diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_handlers.py b/python/packages/declarative/agent_framework_declarative/_workflows/_handlers.py new file mode 100644 index 0000000000..64db7f43f6 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_handlers.py @@ -0,0 +1,211 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Action handlers for declarative workflow execution. + +This module provides the ActionHandler protocol and registry for executing +workflow actions defined in YAML. Each action type (InvokeAzureAgent, Foreach, etc.) +has a corresponding handler registered via the @action_handler decorator. +""" + +from collections.abc import AsyncGenerator, Callable +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable + +from agent_framework import get_logger + +if TYPE_CHECKING: + from ._state import WorkflowState + +logger = get_logger("agent_framework.declarative.workflows") + + +@dataclass +class ActionContext: + """Context passed to action handlers during execution. + + Provides access to workflow state, the action definition, and methods + for executing nested actions (for control flow constructs like Foreach). + """ + + state: "WorkflowState" + """The current workflow state with variables and agent results.""" + + action: dict[str, Any] + """The action definition from the YAML.""" + + execute_actions: "ExecuteActionsFn" + """Function to execute a list of nested actions (for Foreach, If, etc.).""" + + agents: dict[str, Any] + """Registry of agent instances by name.""" + + bindings: dict[str, Any] + """Function bindings for tool calls.""" + + @property + def action_id(self) -> str | None: + """Get the action's unique identifier.""" + return self.action.get("id") + + @property + def display_name(self) -> str | None: + """Get the action's human-readable display name for debugging/logging.""" + return self.action.get("displayName") + + @property + def action_kind(self) -> str | None: + """Get the action's type/kind.""" + return self.action.get("kind") + + +# Type alias for the nested action executor function +ExecuteActionsFn = Callable[ + [list[dict[str, Any]], "WorkflowState"], + AsyncGenerator["WorkflowEvent", None], +] + + +@dataclass +class WorkflowEvent: + """Base class for events emitted during workflow execution.""" + + pass + + +@dataclass +class TextOutputEvent(WorkflowEvent): + """Event emitted when text should be sent to the user.""" + + text: str + """The text content to output.""" + + +@dataclass +class AttachmentOutputEvent(WorkflowEvent): + """Event emitted when an attachment should be sent to the user.""" + + content: Any + """The attachment content.""" + + content_type: str = "application/octet-stream" + """The MIME type of the attachment.""" + + +@dataclass +class AgentResponseEvent(WorkflowEvent): + """Event emitted when an agent produces a response.""" + + agent_name: str + """The name of the agent that produced the response.""" + + text: str | None + """The text content of the response, if any.""" + + messages: list[Any] + """The messages from the agent response.""" + + tool_calls: list[Any] | None = None + """Any tool calls made by the agent.""" + + +@dataclass +class AgentStreamingChunkEvent(WorkflowEvent): + """Event emitted for streaming chunks from an agent.""" + + agent_name: str + """The name of the agent producing the chunk.""" + + chunk: str + """The streaming chunk content.""" + + +@dataclass +class CustomEvent(WorkflowEvent): + """Custom event emitted via EmitEvent action.""" + + name: str + """The event name.""" + + data: Any + """The event data.""" + + +@dataclass +class LoopControlSignal(WorkflowEvent): + """Signal for loop control (break/continue).""" + + signal_type: str + """Either 'break' or 'continue'.""" + + +@runtime_checkable +class ActionHandler(Protocol): + """Protocol for action handlers. + + Action handlers are async generators that execute a single action type + and yield events as they process. They receive an ActionContext with + the current state, action definition, and utilities for nested execution. + """ + + def __call__( + self, + ctx: ActionContext, + ) -> AsyncGenerator[WorkflowEvent, None]: + """Execute the action and yield events. + + Args: + ctx: The action context containing state, action definition, and utilities + + Yields: + WorkflowEvent instances as the action executes + """ + ... + + +# Global registry of action handlers +_ACTION_HANDLERS: dict[str, ActionHandler] = {} + + +def action_handler(action_kind: str) -> Callable[[ActionHandler], ActionHandler]: + """Decorator to register an action handler for a specific action type. + + Args: + action_kind: The action type this handler processes (e.g., 'InvokeAzureAgent') + + Example: + @action_handler("SetValue") + async def handle_set_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: + path = ctx.action.get("path") + value = ctx.state.eval_if_expression(ctx.action.get("value")) + ctx.state.set(path, value) + return + yield # Make it a generator + """ + + def decorator(func: ActionHandler) -> ActionHandler: + _ACTION_HANDLERS[action_kind] = func + logger.debug(f"Registered action handler for '{action_kind}'") + return func + + return decorator + + +def get_action_handler(action_kind: str) -> ActionHandler | None: + """Get the registered handler for an action type. + + Args: + action_kind: The action type to look up + + Returns: + The registered ActionHandler, or None if not found + """ + return _ACTION_HANDLERS.get(action_kind) + + +def list_action_handlers() -> list[str]: + """List all registered action handler types. + + Returns: + A list of registered action type names + """ + return list(_ACTION_HANDLERS.keys()) diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_human_input.py b/python/packages/declarative/agent_framework_declarative/_workflows/_human_input.py new file mode 100644 index 0000000000..f008a9fdd6 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_human_input.py @@ -0,0 +1,317 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Human-in-the-loop action handlers for declarative workflows. + +This module implements handlers for human input patterns: +- Question: Request human input with validation +- RequestExternalInput: Request input from external system +- ExternalLoop processing: Loop while waiting for external input +""" + +from collections.abc import AsyncGenerator +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, cast + +from agent_framework import get_logger + +from ._handlers import ( + ActionContext, + WorkflowEvent, + action_handler, +) + +if TYPE_CHECKING: + from ._state import WorkflowState + +logger = get_logger("agent_framework.declarative.workflows.human_input") + + +@dataclass +class ExternalInputRequest(WorkflowEvent): + """Event emitted when the workflow needs external input. + + When this event is yielded, the workflow execution should pause + and wait for external input to be provided via workflow.send_response(). + """ + + request_id: str + """Unique identifier for this request.""" + + prompt: str | None + """The prompt/question to display to the user.""" + + variable: str + """The variable where the response should be stored.""" + + validation: dict[str, Any] | None = None + """Optional validation rules for the input.""" + + choices: list[str] | None = None + """Optional list of valid choices.""" + + default_value: Any = None + """Default value if no input is provided.""" + + +@dataclass +class ExternalLoopEvent(WorkflowEvent): + """Event emitted when entering an external input loop. + + This event signals that the action is waiting for external input + in a loop pattern (e.g., input.externalLoop.when condition). + """ + + action_id: str + """The ID of the action that requires external input.""" + + iteration: int + """The current iteration number (0-based).""" + + condition_expression: str + """The PowerFx condition that must become false to exit the loop.""" + + +@action_handler("Question") +async def handle_question(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Handle Question action - request human input with optional validation. + + Action schema: + kind: Question + id: ask_name + variable: Local.userName + prompt: What is your name? + validation: + required: true + minLength: 1 + maxLength: 100 + choices: # optional - present as multiple choice + - Option A + - Option B + default: Option A # optional default value + + The handler emits an ExternalInputRequest and expects the workflow runner + to capture and provide the response before continuing. + """ + question_id = ctx.action.get("id", "question") + variable = ctx.action.get("variable") + prompt = ctx.action.get("prompt") + question: dict[str, Any] | Any = ctx.action.get("question", {}) + validation = ctx.action.get("validation", {}) + choices = ctx.action.get("choices") + default_value = ctx.action.get("default") + + if not variable: + logger.warning("Question action missing 'variable' property") + return + + # Evaluate prompt if it's an expression (support both 'prompt' and 'question.text') + prompt_text: Any | None = None + if isinstance(question, dict): + question_dict: dict[str, Any] = cast(dict[str, Any], question) + prompt_text = prompt or question_dict.get("text") + else: + prompt_text = prompt + evaluated_prompt = ctx.state.eval_if_expression(prompt_text) if prompt_text else None + + # Evaluate choices if they're expressions + evaluated_choices = None + if choices: + evaluated_choices = [ctx.state.eval_if_expression(c) if isinstance(c, str) else c for c in choices] + + logger.debug(f"Question: requesting input for {variable}") + + # Emit the request event + yield ExternalInputRequest( + request_id=question_id, + prompt=str(evaluated_prompt) if evaluated_prompt else None, + variable=variable, + validation=validation, + choices=evaluated_choices, + default_value=default_value, + ) + + # Apply default value if specified (for non-interactive scenarios) + if default_value is not None: + evaluated_default = ctx.state.eval_if_expression(default_value) + ctx.state.set(variable, evaluated_default) + + +@action_handler("RequestExternalInput") +async def handle_request_external_input(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Handle RequestExternalInput action - request input from external system. + + Action schema: + kind: RequestExternalInput + id: get_approval + variable: Local.approval + prompt: Please approve or reject the request + timeout: 300 # seconds + default: "No feedback provided" # optional default value + output: + response: Local.approvalResponse + timestamp: Local.approvalTime + + Similar to Question but designed for external system integration + rather than direct human input. + """ + request_id = ctx.action.get("id", "external_input") + variable = ctx.action.get("variable") + prompt = ctx.action.get("prompt") + timeout = ctx.action.get("timeout") # seconds + default_value = ctx.action.get("default") + _output = ctx.action.get("output", {}) # Reserved for future use + + if not variable: + logger.warning("RequestExternalInput action missing 'variable' property") + return + + # Extract prompt text (support both 'prompt' string and 'prompt.text' object) + prompt_text: Any | None = None + if isinstance(prompt, dict): + prompt_dict: dict[str, Any] = cast(dict[str, Any], prompt) + prompt_text = prompt_dict.get("text") + else: + prompt_text = prompt + + # Evaluate prompt if it's an expression + evaluated_prompt = ctx.state.eval_if_expression(prompt_text) if prompt_text else None + + logger.debug(f"RequestExternalInput: requesting input for {variable}") + + # Emit the request event + yield ExternalInputRequest( + request_id=request_id, + prompt=str(evaluated_prompt) if evaluated_prompt else None, + variable=variable, + validation={"timeout": timeout} if timeout else None, + default_value=default_value, + ) + + # Apply default value if specified (for non-interactive scenarios) + if default_value is not None: + evaluated_default = ctx.state.eval_if_expression(default_value) + ctx.state.set(variable, evaluated_default) + + +@action_handler("WaitForInput") +async def handle_wait_for_input(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029 + """Handle WaitForInput action - pause and wait for external input. + + Action schema: + kind: WaitForInput + id: wait_for_response + variable: Local.response + message: Waiting for user response... + + This is a simpler form of RequestExternalInput that just pauses + execution until input is provided. + """ + wait_id = ctx.action.get("id", "wait") + variable = ctx.action.get("variable") + message = ctx.action.get("message") + + if not variable: + logger.warning("WaitForInput action missing 'variable' property") + return + + # Evaluate message if it's an expression + evaluated_message = ctx.state.eval_if_expression(message) if message else None + + logger.debug(f"WaitForInput: waiting for {variable}") + + yield ExternalInputRequest( + request_id=wait_id, + prompt=str(evaluated_message) if evaluated_message else None, + variable=variable, + ) + + +def process_external_loop( + input_config: dict[str, Any], + state: "WorkflowState", +) -> tuple[bool, str | None]: + """Process the externalLoop.when pattern from action input. + + This function evaluates the externalLoop.when condition to determine + if the action should continue looping for external input. + + Args: + input_config: The input configuration containing externalLoop + state: The workflow state for expression evaluation + + Returns: + Tuple of (should_continue_loop, condition_expression) + - should_continue_loop: True if the loop should continue + - condition_expression: The original condition expression for diagnostics + """ + external_loop = input_config.get("externalLoop", {}) + when_condition = external_loop.get("when") + + if not when_condition: + return (False, None) + + # Evaluate the condition + result = state.eval(when_condition) + + # The loop continues while the condition is True + should_continue = bool(result) if result is not None else False + + logger.debug(f"ExternalLoop condition '{when_condition[:50]}' evaluated to {should_continue}") + + return (should_continue, when_condition) + + +def validate_input_response( + value: Any, + validation: dict[str, Any] | None, +) -> tuple[bool, str | None]: + """Validate input response against validation rules. + + Args: + value: The input value to validate + validation: Validation rules from the Question action + + Returns: + Tuple of (is_valid, error_message) + """ + if not validation: + return (True, None) + + # Check required + if validation.get("required") and (value is None or value == ""): + return (False, "This field is required") + + if value is None: + return (True, None) + + # Check string length + if isinstance(value, str): + min_length = validation.get("minLength") + max_length = validation.get("maxLength") + + if min_length is not None and len(value) < min_length: + return (False, f"Minimum length is {min_length}") + + if max_length is not None and len(value) > max_length: + return (False, f"Maximum length is {max_length}") + + # Check numeric range + if isinstance(value, (int, float)): + min_value = validation.get("min") + max_value = validation.get("max") + + if min_value is not None and value < min_value: + return (False, f"Minimum value is {min_value}") + + if max_value is not None and value > max_value: + return (False, f"Maximum value is {max_value}") + + # Check pattern (regex) + pattern = validation.get("pattern") + if pattern and isinstance(value, str): + import re + + if not re.match(pattern, value): + return (False, f"Value does not match pattern: {pattern}") + + return (True, None) diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_powerfx_functions.py b/python/packages/declarative/agent_framework_declarative/_workflows/_powerfx_functions.py new file mode 100644 index 0000000000..b0f3a8e228 --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_powerfx_functions.py @@ -0,0 +1,464 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Custom PowerFx-like functions for declarative workflows. + +This module provides Python implementations of custom PowerFx functions +that are used in declarative workflows but may not be available in the +standard PowerFx Python package. + +These functions can be used as fallbacks when PowerFx is not available, +or registered with the PowerFx engine when it is available. +""" + +from typing import Any, cast + + +def message_text(messages: Any) -> str: + """Extract text content from a message or list of messages. + + This is equivalent to the .NET MessageText() function. + + Args: + messages: A message object, list of messages, or string + + Returns: + The concatenated text content of all messages + + Example: + >>> message_text([{"role": "assistant", "content": "Hello"}]) + 'Hello' + """ + if messages is None: + return "" + + if isinstance(messages, str): + return messages + + if isinstance(messages, dict): + # Single message object + messages_dict = cast(dict[str, Any], messages) + content: Any = messages_dict.get("content", "") + if isinstance(content, str): + return content + if hasattr(content, "text"): + return str(content.text) + return str(content) if content else "" + + if isinstance(messages, list): + # List of messages - concatenate all text + texts: list[str] = [] + for msg in messages: + if isinstance(msg, str): + texts.append(msg) + elif isinstance(msg, dict): + msg_dict = cast(dict[str, Any], msg) + msg_content: Any = msg_dict.get("content", "") + if isinstance(msg_content, str): + texts.append(msg_content) + elif msg_content: + texts.append(str(msg_content)) + elif hasattr(msg, "content"): + msg_obj_content: Any = msg.content + if isinstance(msg_obj_content, str): + texts.append(msg_obj_content) + elif hasattr(msg_obj_content, "text"): + texts.append(str(msg_obj_content.text)) + elif msg_obj_content: + texts.append(str(msg_obj_content)) + return " ".join(texts) + + # Try to get text attribute + if hasattr(messages, "text"): + return str(messages.text) + if hasattr(messages, "content"): + content_attr: Any = messages.content + if isinstance(content_attr, str): + return content_attr + return str(content_attr) if content_attr else "" + + return str(messages) if messages else "" + + +def user_message(text: str) -> dict[str, str]: + """Create a user message object. + + This is equivalent to the .NET UserMessage() function. + + Args: + text: The text content of the message + + Returns: + A message dictionary with role 'user' + + Example: + >>> user_message("Hello") + {'role': 'user', 'content': 'Hello'} + """ + return {"role": "user", "content": str(text) if text else ""} + + +def assistant_message(text: str) -> dict[str, str]: + """Create an assistant message object. + + Args: + text: The text content of the message + + Returns: + A message dictionary with role 'assistant' + + Example: + >>> assistant_message("Hello") + {'role': 'assistant', 'content': 'Hello'} + """ + return {"role": "assistant", "content": str(text) if text else ""} + + +def system_message(text: str) -> dict[str, str]: + """Create a system message object. + + Args: + text: The text content of the message + + Returns: + A message dictionary with role 'system' + + Example: + >>> system_message("You are a helpful assistant") + {'role': 'system', 'content': 'You are a helpful assistant'} + """ + return {"role": "system", "content": str(text) if text else ""} + + +def if_func(condition: Any, true_value: Any, false_value: Any = None) -> Any: + """Conditional expression - returns one value or another based on a condition. + + This is equivalent to the PowerFx If() function. + + Args: + condition: The condition to evaluate (truthy/falsy) + true_value: Value to return if condition is truthy + false_value: Value to return if condition is falsy (defaults to None) + + Returns: + true_value if condition is truthy, otherwise false_value + """ + return true_value if condition else false_value + + +def is_blank(value: Any) -> bool: + """Check if a value is blank (None, empty string, empty list, etc.). + + This is equivalent to the PowerFx IsBlank() function. + + Args: + value: The value to check + + Returns: + True if the value is considered blank + """ + if value is None: + return True + if isinstance(value, str) and not value.strip(): + return True + if isinstance(value, list): + return len(value) == 0 + if isinstance(value, dict): + return len(value) == 0 + return False + + +def or_func(*args: Any) -> bool: + """Logical OR - returns True if any argument is truthy. + + This is equivalent to the PowerFx Or() function. + + Args: + *args: Variable number of values to check + + Returns: + True if any argument is truthy + """ + return any(bool(arg) for arg in args) + + +def and_func(*args: Any) -> bool: + """Logical AND - returns True if all arguments are truthy. + + This is equivalent to the PowerFx And() function. + + Args: + *args: Variable number of values to check + + Returns: + True if all arguments are truthy + """ + return all(bool(arg) for arg in args) + + +def not_func(value: Any) -> bool: + """Logical NOT - returns the opposite boolean value. + + This is equivalent to the PowerFx Not() function. + + Args: + value: The value to negate + + Returns: + True if value is falsy, False if truthy + """ + return not bool(value) + + +def count_rows(table: Any) -> int: + """Count the number of rows/items in a table/list. + + This is equivalent to the PowerFx CountRows() function. + + Args: + table: A list or table-like object + + Returns: + The number of rows/items + """ + if table is None: + return 0 + if isinstance(table, (list, tuple)): + return len(cast(list[Any], table)) + if isinstance(table, dict): + return len(cast(dict[str, Any], table)) + return 0 + + +def first(table: Any) -> Any: + """Get the first item from a table/list. + + This is equivalent to the PowerFx First() function. + + Args: + table: A list or table-like object + + Returns: + The first item, or None if empty + """ + if table is None: + return None + if isinstance(table, (list, tuple)): + table_list = cast(list[Any], table) + if len(table_list) > 0: + return table_list[0] + return None + + +def last(table: Any) -> Any: + """Get the last item from a table/list. + + This is equivalent to the PowerFx Last() function. + + Args: + table: A list or table-like object + + Returns: + The last item, or None if empty + """ + if table is None: + return None + if isinstance(table, (list, tuple)): + table_list = cast(list[Any], table) + if len(table_list) > 0: + return table_list[-1] + return None + + +def find(substring: str | None, text: str | None) -> int | None: + """Find the position of a substring within text. + + This is equivalent to the PowerFx Find() function. + Returns None (Blank) if not found, otherwise 1-based index. + + Args: + substring: The substring to find + text: The text to search in + + Returns: + 1-based index if found, None (Blank) if not found + """ + if substring is None or text is None: + return None + try: + index = str(text).find(str(substring)) + return index + 1 if index >= 0 else None + except (TypeError, ValueError): + return None + + +def upper(text: str | None) -> str: + """Convert text to uppercase. + + This is equivalent to the PowerFx Upper() function. + + Args: + text: The text to convert + + Returns: + Uppercase text + """ + if text is None: + return "" + return str(text).upper() + + +def lower(text: str | None) -> str: + """Convert text to lowercase. + + This is equivalent to the PowerFx Lower() function. + + Args: + text: The text to convert + + Returns: + Lowercase text + """ + if text is None: + return "" + return str(text).lower() + + +def concat_strings(*args: Any) -> str: + """Concatenate multiple string arguments. + + This is equivalent to the PowerFx Concat() function for string concatenation. + + Args: + *args: Variable number of values to concatenate + + Returns: + Concatenated string + """ + return "".join(str(arg) if arg is not None else "" for arg in args) + + +def concat_text(table: Any, field: str | None = None, separator: str = "") -> str: + """Concatenate values from a table/list. + + This is equivalent to the PowerFx Concat() function. + + Args: + table: A list of items + field: Optional field name to extract from each item + separator: Separator between values + + Returns: + Concatenated string + """ + if table is None: + return "" + if not isinstance(table, (list, tuple)): + return str(table) + + values: list[str] = [] + for item in cast(list[Any], table): + value: Any = None + if field and isinstance(item, dict): + item_dict = cast(dict[str, Any], item) + value = item_dict.get(field, "") + elif field and hasattr(item, field): + value = getattr(item, field, "") + else: + value = item + values.append(str(value) if value is not None else "") + + return separator.join(values) + + +def for_all(table: Any, expression: str, field_mapping: dict[str, str] | None = None) -> list[Any]: + """Apply an expression to each row of a table. + + This is equivalent to the PowerFx ForAll() function. + + Args: + table: A list of records + expression: A string expression that references item fields + field_mapping: Optional dict mapping placeholder names to field names + + Returns: + List of results from applying expression to each row + + Note: + The expression can use field names directly from the record. + For example: ForAll(items, "$" & name & ": " & description) + """ + if table is None or not isinstance(table, (list, tuple)): + return [] + + results: list[Any] = [] + for item in cast(list[Any], table): + # If item is a dict, we can directly substitute field values + if isinstance(item, dict): + item_dict = cast(dict[str, Any], item) + # The expression is typically already evaluated by the expression parser + # This function primarily handles table iteration + # Return the item itself for further processing + results.append(item_dict) + else: + results.append(item) + + return results + + +def search_table(table: Any, value: Any, column: str) -> list[Any]: + """Search for rows in a table where a column matches a value. + + This is equivalent to the PowerFx Search() function. + + Args: + table: A list of records + value: The value to search for + column: The column name to search in + + Returns: + List of matching records + """ + if table is None or not isinstance(table, (list, tuple)): + return [] + + results: list[Any] = [] + search_value = str(value).lower() if value else "" + + for item in cast(list[Any], table): + item_value: Any = None + if isinstance(item, dict): + item_dict = cast(dict[str, Any], item) + item_value = item_dict.get(column, "") + elif hasattr(item, column): + item_value = getattr(item, column, "") + else: + continue + + # Case-insensitive contains search + if search_value in str(item_value).lower(): + results.append(item) + + return results + + +# Registry of custom functions +CUSTOM_FUNCTIONS: dict[str, Any] = { + "MessageText": message_text, + "UserMessage": user_message, + "AssistantMessage": assistant_message, + "SystemMessage": system_message, + "If": if_func, + "IsBlank": is_blank, + "Or": or_func, + "And": and_func, + "Not": not_func, + "CountRows": count_rows, + "First": first, + "Last": last, + "Find": find, + "Upper": upper, + "Lower": lower, + "Concat": concat_strings, + "Search": search_table, + "ForAll": for_all, +} diff --git a/python/packages/declarative/agent_framework_declarative/_workflows/_state.py b/python/packages/declarative/agent_framework_declarative/_workflows/_state.py new file mode 100644 index 0000000000..31cd7fe44f --- /dev/null +++ b/python/packages/declarative/agent_framework_declarative/_workflows/_state.py @@ -0,0 +1,548 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""WorkflowState manages PowerFx variables during declarative workflow execution. + +This module provides state management for declarative workflows, handling: +- Workflow inputs (read-only) +- Turn-scoped variables +- Workflow outputs +- Agent results and context +""" + +from collections.abc import Mapping +from typing import Any, cast + +from agent_framework import get_logger + +try: + from powerfx import Engine + + _powerfx_engine: Engine | None = Engine() +except (ImportError, RuntimeError): + # ImportError: powerfx package not installed + # RuntimeError: .NET runtime not available or misconfigured + _powerfx_engine = None + +logger = get_logger("agent_framework.declarative.workflows") + + +class WorkflowState: + """Manages variables and state during declarative workflow execution. + + WorkflowState provides a unified interface for: + - Reading workflow inputs (immutable after initialization) + - Managing turn-scoped variables that persist across actions + - Storing agent results and making them available to subsequent actions + - Evaluating PowerFx expressions with the current state as context + + The state is organized into namespaces that mirror the .NET implementation: + - workflow.inputs: Initial inputs to the workflow + - workflow.outputs: Values to be returned from the workflow + - turn: Variables that persist within the current workflow turn + - agent: Results from the most recent agent invocation + - conversation: Conversation history and messages + + Example: + >>> state = WorkflowState(inputs={"query": "Hello"}) + >>> state.set("turn.results", []) + >>> state.append("turn.results", "item1") + >>> state.eval("=Concat(workflow.inputs.query, ' World')") + 'Hello World' + """ + + def __init__( + self, + inputs: Mapping[str, Any] | None = None, + ) -> None: + """Initialize workflow state with optional inputs. + + Args: + inputs: Initial inputs to the workflow. These become available + as workflow.inputs.* and are immutable after initialization. + """ + self._inputs: dict[str, Any] = dict(inputs) if inputs else {} + self._turn: dict[str, Any] = {} + self._outputs: dict[str, Any] = {} + self._agent: dict[str, Any] = {} + self._conversation: dict[str, Any] = { + "messages": [], + "history": [], + } + self._custom: dict[str, Any] = {} + + @property + def inputs(self) -> Mapping[str, Any]: + """Get the workflow inputs (read-only).""" + return self._inputs + + @property + def outputs(self) -> dict[str, Any]: + """Get the workflow outputs.""" + return self._outputs + + @property + def turn(self) -> dict[str, Any]: + """Get the turn-scoped variables.""" + return self._turn + + @property + def agent(self) -> dict[str, Any]: + """Get the most recent agent result.""" + return self._agent + + @property + def conversation(self) -> dict[str, Any]: + """Get the conversation state.""" + return self._conversation + + def get(self, path: str, default: Any = None) -> Any: + """Get a value from the state using a dot-notated path. + + Args: + path: Dot-notated path like 'turn.results' or 'workflow.inputs.query' + default: Default value if path doesn't exist + + Returns: + The value at the path, or default if not found + """ + parts = path.split(".") + if not parts: + return default + + # Determine the namespace (case-insensitive to match .NET) + namespace = parts[0].lower() + remaining = parts[1:] + + # Handle workflow.inputs and workflow.outputs specially + if namespace == "workflow" and remaining: + sub_namespace = remaining[0].lower() + remaining = remaining[1:] + if sub_namespace == "inputs": + obj: Any = self._inputs + elif sub_namespace == "outputs": + obj = self._outputs + else: + return default + elif namespace == "turn": + obj = self._turn + elif namespace == "agent": + obj = self._agent + elif namespace == "conversation": + obj = self._conversation + else: + # Try custom namespace + obj = self._custom.get(namespace, default) + if obj is default: + return default + + # Navigate the remaining path + for part in remaining: + if isinstance(obj, dict): + obj_dict: dict[str, Any] = cast(dict[str, Any], obj) + obj = obj_dict.get(part, default) + if obj is default: + return default + elif hasattr(obj, part): + obj = getattr(obj, part) + else: + return default + + return obj + + def set(self, path: str, value: Any) -> None: + """Set a value in the state using a dot-notated path. + + Args: + path: Dot-notated path like 'turn.results' or 'workflow.outputs.response' + value: The value to set + + Raises: + ValueError: If attempting to set workflow.inputs (which is read-only) + """ + parts = path.split(".") + if not parts: + return + + # Normalize namespace to lowercase for case-insensitive matching + namespace = parts[0].lower() + remaining = parts[1:] + + # Handle workflow.inputs and workflow.outputs specially + if namespace == "workflow": + if not remaining: + raise ValueError("Cannot set 'workflow' directly; use 'workflow.outputs.*'") + sub_namespace = remaining[0].lower() + remaining = remaining[1:] + if sub_namespace == "inputs": + raise ValueError("Cannot modify workflow.inputs - they are read-only") + if sub_namespace == "outputs": + target = self._outputs + else: + raise ValueError(f"Unknown workflow namespace: {sub_namespace}") + elif namespace == "turn": + target = self._turn + elif namespace == "agent": + target = self._agent + elif namespace == "conversation": + target = self._conversation + else: + # Create or use custom namespace (normalized to lowercase) + if namespace not in self._custom: + self._custom[namespace] = {} + target = self._custom[namespace] + + # Navigate to the parent and set the value + if not remaining: + # Setting the namespace root itself - this shouldn't happen normally + raise ValueError(f"Cannot replace entire namespace '{namespace}'") + + # Navigate to parent, creating dicts as needed + for part in remaining[:-1]: + if part not in target: + target[part] = {} + target = target[part] + + # Set the final value + target[remaining[-1]] = value + + def append(self, path: str, value: Any) -> None: + """Append a value to a list at the specified path. + + If the path doesn't exist, creates a new list with the value. + If the path exists but isn't a list, raises ValueError. + + Args: + path: Dot-notated path to a list + value: The value to append + + Raises: + ValueError: If the existing value is not a list + """ + existing = self.get(path) + if existing is None: + self.set(path, [value]) + elif isinstance(existing, list): + existing.append(value) + self.set(path, existing) + else: + raise ValueError(f"Cannot append to non-list at path '{path}'") + + def set_agent_result( + self, + text: str | None = None, + messages: list[Any] | None = None, + tool_calls: list[Any] | None = None, + **kwargs: Any, + ) -> None: + """Set the result from the most recent agent invocation. + + This updates the 'agent' namespace with the agent's response, + making it available to subsequent actions via agent.text, agent.messages, etc. + + Args: + text: The text content of the agent's response + messages: The messages from the agent + tool_calls: Any tool calls made by the agent + **kwargs: Additional result data + """ + self._agent = { + "text": text, + "messages": messages or [], + "toolCalls": tool_calls or [], + **kwargs, + } + + def add_conversation_message(self, message: Any) -> None: + """Add a message to the conversation history. + + Args: + message: The message to add (typically a ChatMessage or similar) + """ + self._conversation["messages"].append(message) + self._conversation["history"].append(message) + + def to_powerfx_symbols(self) -> dict[str, Any]: + """Convert the current state to a PowerFx symbols dictionary. + + Returns: + A dictionary suitable for passing to PowerFx Engine.eval() + """ + return { + "workflow": { + "inputs": dict(self._inputs), + "outputs": dict(self._outputs), + }, + "turn": dict(self._turn), + "agent": dict(self._agent), + "conversation": dict(self._conversation), + **self._custom, + } + + def eval(self, expression: str) -> Any: + """Evaluate a PowerFx expression with the current state. + + Expressions starting with '=' are evaluated as PowerFx. + Other strings are returned as-is (after variable interpolation if applicable). + + Args: + expression: The expression to evaluate + + Returns: + The evaluated result, or the original expression if not a PowerFx expression + """ + if not expression: + return expression + + if not expression.startswith("="): + return expression + + # Strip the leading '=' for evaluation + formula = expression[1:] + + if _powerfx_engine is not None: + # Try PowerFx evaluation first + try: + symbols = self.to_powerfx_symbols() + return _powerfx_engine.eval(formula, symbols=symbols) + except Exception as exc: + logger.warning(f"PowerFx evaluation failed for '{expression[:50]}': {exc}") + # Fall through to simple evaluation + + # Fallback: Simple expression evaluation using custom functions + return self._eval_simple(formula) + + def _eval_simple(self, formula: str) -> Any: + """Simple expression evaluation when PowerFx is not available. + + Supports: + - Variable references: Local.X, System.X, turn.x + - Simple function calls: IsBlank(x), Find(a, b), etc. + - Simple comparisons: x < 4, x = "value" + - Logical operators: And, Or, Not, ||, ! + - Negation: !expression + + Args: + formula: The formula to evaluate (without leading '=') + + Returns: + The evaluated result + """ + from ._powerfx_functions import CUSTOM_FUNCTIONS + + formula = formula.strip() + + # Handle negation prefix + if formula.startswith("!"): + inner = formula[1:].strip() + result = self._eval_simple(inner) + return not bool(result) + + # Handle Not() function + if formula.startswith("Not(") and formula.endswith(")"): + inner = formula[4:-1].strip() + result = self._eval_simple(inner) + return not bool(result) + + # Handle function calls + for func_name, func in CUSTOM_FUNCTIONS.items(): + if formula.startswith(f"{func_name}(") and formula.endswith(")"): + args_str = formula[len(func_name) + 1 : -1] + # Simple argument parsing (doesn't handle nested calls well) + args = self._parse_function_args(args_str) + evaluated_args = [self._eval_simple(arg) if isinstance(arg, str) else arg for arg in args] + try: + return func(*evaluated_args) + except Exception as e: + logger.warning(f"Function {func_name} failed: {e}") + return formula + + # Handle And operator + if " And " in formula: + parts = formula.split(" And ", 1) + left = self._eval_simple(parts[0]) + right = self._eval_simple(parts[1]) + return bool(left) and bool(right) + + # Handle Or operator (||) + if " || " in formula or " Or " in formula: + parts = formula.split(" || ", 1) if " || " in formula else formula.split(" Or ", 1) + left = self._eval_simple(parts[0]) + right = self._eval_simple(parts[1]) + return bool(left) or bool(right) + + # Handle comparison operators + for op in [" < ", " > ", " <= ", " >= ", " <> ", " = "]: + if op in formula: + parts = formula.split(op, 1) + left = self._eval_simple(parts[0].strip()) + right = self._eval_simple(parts[1].strip()) + if op == " < ": + return left < right + if op == " > ": + return left > right + if op == " <= ": + return left <= right + if op == " >= ": + return left >= right + if op == " <> ": + return left != right + if op == " = ": + return left == right + + # Handle arithmetic operators + if " + " in formula: + parts = formula.split(" + ", 1) + left = self._eval_simple(parts[0].strip()) + right = self._eval_simple(parts[1].strip()) + # Treat None as 0 for arithmetic (PowerFx behavior) + if left is None: + left = 0 + if right is None: + right = 0 + # Try numeric addition first, fall back to string concat + try: + return float(left) + float(right) + except (ValueError, TypeError): + return str(left) + str(right) + + if " - " in formula: + parts = formula.split(" - ", 1) + left = self._eval_simple(parts[0].strip()) + right = self._eval_simple(parts[1].strip()) + # Treat None as 0 for arithmetic (PowerFx behavior) + if left is None: + left = 0 + if right is None: + right = 0 + try: + return float(left) - float(right) + except (ValueError, TypeError): + return formula + + # Handle string literals + if (formula.startswith('"') and formula.endswith('"')) or (formula.startswith("'") and formula.endswith("'")): + return formula[1:-1] + + # Handle numeric literals + try: + if "." in formula: + return float(formula) + return int(formula) + except ValueError: + pass + + # Handle boolean literals + if formula.lower() == "true": + return True + if formula.lower() == "false": + return False + + # Handle variable references + if "." in formula: + # Map .NET style to Python style + path = formula + if formula.startswith("Local."): + path = "turn." + formula[6:] + elif formula.startswith("System."): + path = "system." + formula[7:] + elif formula.startswith("inputs."): + path = "workflow.inputs." + formula[7:] + # For known namespaces, return None if not found (PowerFx semantics) + # rather than the formula string + if path.startswith(("turn.", "workflow.", "agent.", "conversation.", "system.")): + return self.get(path) + not_found = object() + value = self.get(path, default=not_found) + if value is not not_found: + return value + + # Return the formula as-is if we can't evaluate it + return formula + + def _parse_function_args(self, args_str: str) -> list[str]: + """Parse function arguments, handling nested parentheses and strings. + + Args: + args_str: The argument string (without outer parentheses) + + Returns: + List of argument strings + """ + args: list[str] = [] + current = "" + depth = 0 + in_string = False + string_char = None + + for char in args_str: + if char in ('"', "'") and not in_string: + in_string = True + string_char = char + current += char + elif char == string_char and in_string: + in_string = False + string_char = None + current += char + elif char == "(" and not in_string: + depth += 1 + current += char + elif char == ")" and not in_string: + depth -= 1 + current += char + elif char == "," and depth == 0 and not in_string: + args.append(current.strip()) + current = "" + else: + current += char + + if current.strip(): + args.append(current.strip()) + + return args + + def eval_if_expression(self, value: Any) -> Any: + """Evaluate a value if it's a PowerFx expression, otherwise return as-is. + + This is a convenience method that handles both expressions and literals. + + Args: + value: A value that may or may not be a PowerFx expression + + Returns: + The evaluated result if it's an expression, or the original value + """ + if isinstance(value, str): + return self.eval(value) + if isinstance(value, dict): + return {str(k): self.eval_if_expression(v) for k, v in value.items()} + if isinstance(value, list): + return [self.eval_if_expression(item) for item in value] + return value + + def reset_turn(self) -> None: + """Reset turn-scoped variables for a new turn. + + This clears the turn namespace while preserving other state. + """ + self._turn.clear() + + def reset_agent(self) -> None: + """Reset the agent result for a new agent invocation.""" + self._agent.clear() + + def clone(self) -> "WorkflowState": + """Create a shallow copy of the state. + + Returns: + A new WorkflowState with copied data + """ + import copy + + new_state = WorkflowState() + new_state._inputs = copy.copy(self._inputs) + new_state._turn = copy.copy(self._turn) + new_state._outputs = copy.copy(self._outputs) + new_state._agent = copy.copy(self._agent) + new_state._conversation = copy.copy(self._conversation) + new_state._custom = copy.copy(self._custom) + return new_state diff --git a/python/packages/declarative/tests/test_additional_handlers.py b/python/packages/declarative/tests/test_additional_handlers.py new file mode 100644 index 0000000000..1991ab7d51 --- /dev/null +++ b/python/packages/declarative/tests/test_additional_handlers.py @@ -0,0 +1,346 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Tests for additional action handlers (conversation, variables, etc.).""" + +import pytest + +from agent_framework_declarative._workflows._handlers import get_action_handler +from agent_framework_declarative._workflows._state import WorkflowState + + +def create_action_context(action: dict, state: WorkflowState | None = None): + """Create a minimal action context for testing.""" + from agent_framework_declarative._workflows._handlers import ActionContext + + if state is None: + state = WorkflowState() + + async def execute_actions(actions, state): + for act in actions: + handler = get_action_handler(act.get("kind")) + if handler: + async for event in handler( + ActionContext( + state=state, + action=act, + execute_actions=execute_actions, + agents={}, + bindings={}, + ) + ): + yield event + + return ActionContext( + state=state, + action=action, + execute_actions=execute_actions, + agents={}, + bindings={}, + ) + + +class TestSetTextVariableHandler: + """Tests for SetTextVariable action handler.""" + + @pytest.mark.asyncio + async def test_set_text_variable_simple(self): + """Test setting a simple text variable.""" + ctx = create_action_context({ + "kind": "SetTextVariable", + "variable": "Local.greeting", + "value": "Hello, World!", + }) + + handler = get_action_handler("SetTextVariable") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.greeting") == "Hello, World!" + + @pytest.mark.asyncio + async def test_set_text_variable_with_interpolation(self): + """Test setting text with variable interpolation.""" + state = WorkflowState() + state.set("turn.name", "Alice") + + ctx = create_action_context( + { + "kind": "SetTextVariable", + "variable": "Local.message", + "value": "Hello, {Local.name}!", + }, + state=state, + ) + + handler = get_action_handler("SetTextVariable") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.message") == "Hello, Alice!" + + +class TestResetVariableHandler: + """Tests for ResetVariable action handler.""" + + @pytest.mark.asyncio + async def test_reset_variable(self): + """Test resetting a variable to None.""" + state = WorkflowState() + state.set("turn.counter", 5) + + ctx = create_action_context( + { + "kind": "ResetVariable", + "variable": "Local.counter", + }, + state=state, + ) + + handler = get_action_handler("ResetVariable") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.counter") is None + + +class TestSetMultipleVariablesHandler: + """Tests for SetMultipleVariables action handler.""" + + @pytest.mark.asyncio + async def test_set_multiple_variables(self): + """Test setting multiple variables at once.""" + ctx = create_action_context({ + "kind": "SetMultipleVariables", + "variables": [ + {"variable": "Local.a", "value": 1}, + {"variable": "Local.b", "value": 2}, + {"variable": "Local.c", "value": "three"}, + ], + }) + + handler = get_action_handler("SetMultipleVariables") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.a") == 1 + assert ctx.state.get("turn.b") == 2 + assert ctx.state.get("turn.c") == "three" + + +class TestClearAllVariablesHandler: + """Tests for ClearAllVariables action handler.""" + + @pytest.mark.asyncio + async def test_clear_all_variables(self): + """Test clearing all turn-scoped variables.""" + state = WorkflowState() + state.set("turn.a", 1) + state.set("turn.b", 2) + state.set("workflow.outputs.result", "kept") + + ctx = create_action_context( + { + "kind": "ClearAllVariables", + }, + state=state, + ) + + handler = get_action_handler("ClearAllVariables") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.a") is None + assert ctx.state.get("turn.b") is None + # Workflow outputs should be preserved + assert ctx.state.get("workflow.outputs.result") == "kept" + + +class TestCreateConversationHandler: + """Tests for CreateConversation action handler.""" + + @pytest.mark.asyncio + async def test_create_conversation_dotnet_style(self): + """Test creating a new conversation with .NET style output binding. + + In .NET style, conversationId is the OUTPUT variable where the + auto-generated conversation ID is stored. + """ + ctx = create_action_context({ + "kind": "CreateConversation", + "conversationId": "Local.myConvId", # Output variable + }) + + handler = get_action_handler("CreateConversation") + _events = [e async for e in handler(ctx)] # noqa: F841 + + # Check conversation was created with auto-generated ID + conversations = ctx.state.get("system.conversations") + assert conversations is not None + assert len(conversations) == 1 + + # Get the generated ID + generated_id = list(conversations.keys())[0] + assert conversations[generated_id]["messages"] == [] + + # Check output binding - the ID should be stored in the specified variable + assert ctx.state.get("turn.myConvId") == generated_id + + @pytest.mark.asyncio + async def test_create_conversation_legacy_output(self): + """Test creating a conversation with legacy output binding.""" + ctx = create_action_context({ + "kind": "CreateConversation", + "output": { + "conversationId": "Local.myConvId", + }, + }) + + handler = get_action_handler("CreateConversation") + _events = [e async for e in handler(ctx)] # noqa: F841 + + # Check conversation was created + conversations = ctx.state.get("system.conversations") + assert conversations is not None + assert len(conversations) == 1 + + # Get the generated ID + generated_id = list(conversations.keys())[0] + + # Check legacy output binding + assert ctx.state.get("turn.myConvId") == generated_id + + @pytest.mark.asyncio + async def test_create_conversation_auto_id(self): + """Test creating a conversation with auto-generated ID.""" + ctx = create_action_context({ + "kind": "CreateConversation", + }) + + handler = get_action_handler("CreateConversation") + _events = [e async for e in handler(ctx)] # noqa: F841 + + # Check conversation was created with some ID + conversations = ctx.state.get("system.conversations") + assert conversations is not None + assert len(conversations) == 1 + + +class TestAddConversationMessageHandler: + """Tests for AddConversationMessage action handler.""" + + @pytest.mark.asyncio + async def test_add_conversation_message(self): + """Test adding a message to a conversation.""" + state = WorkflowState() + state.set( + "system.conversations", + { + "conv-123": {"id": "conv-123", "messages": []}, + }, + ) + + ctx = create_action_context( + { + "kind": "AddConversationMessage", + "conversationId": "conv-123", + "message": { + "role": "user", + "content": "Hello!", + }, + }, + state=state, + ) + + handler = get_action_handler("AddConversationMessage") + _events = [e async for e in handler(ctx)] # noqa: F841 + + conversations = ctx.state.get("system.conversations") + assert len(conversations["conv-123"]["messages"]) == 1 + assert conversations["conv-123"]["messages"][0]["content"] == "Hello!" + + +class TestEndWorkflowHandler: + """Tests for EndWorkflow action handler.""" + + @pytest.mark.asyncio + async def test_end_workflow_signal(self): + """Test that EndWorkflow emits correct signal.""" + from agent_framework_declarative._workflows._actions_control_flow import EndWorkflowSignal + + ctx = create_action_context({ + "kind": "EndWorkflow", + "reason": "Completed successfully", + }) + + handler = get_action_handler("EndWorkflow") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + assert isinstance(events[0], EndWorkflowSignal) + assert events[0].reason == "Completed successfully" + + +class TestEndConversationHandler: + """Tests for EndConversation action handler.""" + + @pytest.mark.asyncio + async def test_end_conversation_signal(self): + """Test that EndConversation emits correct signal.""" + from agent_framework_declarative._workflows._actions_control_flow import EndConversationSignal + + ctx = create_action_context({ + "kind": "EndConversation", + "conversationId": "conv-123", + }) + + handler = get_action_handler("EndConversation") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + assert isinstance(events[0], EndConversationSignal) + assert events[0].conversation_id == "conv-123" + + +class TestConditionGroupWithElseActions: + """Tests for ConditionGroup with elseActions.""" + + @pytest.mark.asyncio + async def test_condition_group_else_actions(self): + """Test that elseActions execute when no condition matches.""" + ctx = create_action_context({ + "kind": "ConditionGroup", + "conditions": [ + { + "condition": False, + "actions": [ + {"kind": "SetValue", "path": "turn.result", "value": "matched"}, + ], + }, + ], + "elseActions": [ + {"kind": "SetValue", "path": "turn.result", "value": "else"}, + ], + }) + + handler = get_action_handler("ConditionGroup") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.result") == "else" + + @pytest.mark.asyncio + async def test_condition_group_match_skips_else(self): + """Test that elseActions don't execute when a condition matches.""" + ctx = create_action_context({ + "kind": "ConditionGroup", + "conditions": [ + { + "condition": True, + "actions": [ + {"kind": "SetValue", "path": "turn.result", "value": "matched"}, + ], + }, + ], + "elseActions": [ + {"kind": "SetValue", "path": "turn.result", "value": "else"}, + ], + }) + + handler = get_action_handler("ConditionGroup") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.result") == "matched" diff --git a/python/packages/declarative/tests/test_declarative_models.py b/python/packages/declarative/tests/test_declarative_models.py index dc13b3a642..925b55d534 100644 --- a/python/packages/declarative/tests/test_declarative_models.py +++ b/python/packages/declarative/tests/test_declarative_models.py @@ -838,6 +838,16 @@ def test_environment_variable_from_dict(self): assert env_var.value == "secret123" +# Check if PowerFx is available +try: + from powerfx import Engine as _PfxEngine + + _PfxEngine() + _powerfx_available = True +except (ImportError, RuntimeError): + _powerfx_available = False + + class TestTryPowerfxEval: """Tests for _try_powerfx_eval function.""" @@ -855,6 +865,7 @@ def test_empty_string_returns_empty(self): """Test that empty strings are returned as empty.""" assert _try_powerfx_eval("") == "" + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_simple_powerfx_expressions(self): """Test simple PowerFx expressions.""" from decimal import Decimal @@ -867,6 +878,7 @@ def test_simple_powerfx_expressions(self): assert _try_powerfx_eval('="hello"') == "hello" assert _try_powerfx_eval('="test value"') == "test value" + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_env_variable_access(self, monkeypatch): """Test accessing environment variables using =Env. pattern.""" # Set up test environment variables @@ -879,6 +891,7 @@ def test_env_variable_access(self, monkeypatch): assert _try_powerfx_eval("=Env.API_KEY") == "secret123" assert _try_powerfx_eval("=Env.PORT") == "8080" + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_env_variable_with_string_concatenation(self, monkeypatch): """Test env variables with string concatenation operator.""" monkeypatch.setenv("BASE_URL", "https://api.example.com") @@ -892,6 +905,7 @@ def test_env_variable_with_string_concatenation(self, monkeypatch): result = _try_powerfx_eval('="API Key: " & Env.API_VERSION') assert result == "API Key: v1" + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_string_comparison_operators(self, monkeypatch): """Test PowerFx string comparison operators.""" monkeypatch.setenv("ENV_MODE", "production") @@ -904,6 +918,7 @@ def test_string_comparison_operators(self, monkeypatch): assert _try_powerfx_eval('=Env.ENV_MODE <> "development"') is True assert _try_powerfx_eval('=Env.ENV_MODE <> "production"') is False + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_string_in_operator(self): """Test PowerFx 'in' operator for substring testing (case-insensitive).""" # Substring test - case insensitive - returns bool @@ -911,6 +926,7 @@ def test_string_in_operator(self): assert _try_powerfx_eval('="THE" in "The keyboard and the monitor"') is True assert _try_powerfx_eval('="xyz" in "The keyboard and the monitor"') is False + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_string_exactin_operator(self): """Test PowerFx 'exactin' operator for substring testing (case-sensitive).""" # Substring test - case sensitive - returns bool @@ -918,6 +934,7 @@ def test_string_exactin_operator(self): assert _try_powerfx_eval('="windows" exactin "To display windows in the Windows operating system"') is True assert _try_powerfx_eval('="WINDOWS" exactin "To display windows in the Windows operating system"') is False + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_logical_operators_with_strings(self): """Test PowerFx logical operators (And, Or, Not) with string comparisons.""" # And operator - returns bool @@ -941,6 +958,7 @@ def test_logical_operators_with_strings(self): # ! operator (alternative syntax) - returns bool assert _try_powerfx_eval('=!("a" = "b")') is True + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_parentheses_for_precedence(self): """Test using parentheses to control operator precedence.""" from decimal import Decimal @@ -953,6 +971,7 @@ def test_parentheses_for_precedence(self): result = _try_powerfx_eval('=("a" = "a" Or "b" = "c") And "d" = "d"') assert result is True + @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available") def test_env_with_special_characters(self, monkeypatch): """Test env variables containing special characters in values.""" monkeypatch.setenv("URL_WITH_QUERY", "https://example.com?param=value") diff --git a/python/packages/declarative/tests/test_graph_executors.py b/python/packages/declarative/tests/test_graph_executors.py new file mode 100644 index 0000000000..8d0d6c7be3 --- /dev/null +++ b/python/packages/declarative/tests/test_graph_executors.py @@ -0,0 +1,537 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Tests for the graph-based declarative workflow executors.""" + +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from agent_framework_declarative._workflows._graph import ( + ALL_ACTION_EXECUTORS, + DECLARATIVE_STATE_KEY, + ActionComplete, + ActionTrigger, + DeclarativeGraphBuilder, + DeclarativeWorkflowState, +) +from agent_framework_declarative._workflows._graph._base import ( + LoopIterationResult, +) +from agent_framework_declarative._workflows._graph._executors_basic import ( + SendActivityExecutor, + SetValueExecutor, +) +from agent_framework_declarative._workflows._graph._executors_control_flow import ( + ForeachInitExecutor, +) + + +class TestDeclarativeWorkflowState: + """Tests for DeclarativeWorkflowState.""" + + @pytest.fixture + def mock_shared_state(self): + """Create a mock shared state with async get/set methods.""" + shared_state = MagicMock() + shared_state._data = {} + + async def mock_get(key): + if key not in shared_state._data: + raise KeyError(key) + return shared_state._data[key] + + async def mock_set(key, value): + shared_state._data[key] = value + + shared_state.get = AsyncMock(side_effect=mock_get) + shared_state.set = AsyncMock(side_effect=mock_set) + + return shared_state + + @pytest.mark.asyncio + async def test_initialize_state(self, mock_shared_state): + """Test initializing the workflow state.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize({"query": "test"}) + + # Verify state was set + mock_shared_state.set.assert_called_once() + call_args = mock_shared_state.set.call_args + assert call_args[0][0] == DECLARATIVE_STATE_KEY + state_data = call_args[0][1] + assert state_data["inputs"] == {"query": "test"} + assert state_data["outputs"] == {} + assert state_data["turn"] == {} + + @pytest.mark.asyncio + async def test_get_and_set_values(self, mock_shared_state): + """Test getting and setting values.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + # Set a turn value + await state.set("turn.counter", 5) + + # Get the value + result = await state.get("turn.counter") + assert result == 5 + + @pytest.mark.asyncio + async def test_get_inputs(self, mock_shared_state): + """Test getting workflow inputs.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize({"name": "Alice", "age": 30}) + + # Get via path + name = await state.get("workflow.inputs.name") + assert name == "Alice" + + # Get all inputs + inputs = await state.get_inputs() + assert inputs == {"name": "Alice", "age": 30} + + @pytest.mark.asyncio + async def test_append_value(self, mock_shared_state): + """Test appending values to a list.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + # Append to non-existent list creates it + await state.append("turn.items", "first") + result = await state.get("turn.items") + assert result == ["first"] + + # Append to existing list + await state.append("turn.items", "second") + result = await state.get("turn.items") + assert result == ["first", "second"] + + @pytest.mark.asyncio + async def test_eval_expression(self, mock_shared_state): + """Test evaluating expressions.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + # Non-expression returns as-is + result = await state.eval("plain text") + assert result == "plain text" + + # Boolean literals + result = await state.eval("=true") + assert result is True + + result = await state.eval("=false") + assert result is False + + # String literals + result = await state.eval('="hello"') + assert result == "hello" + + # Numeric literals + result = await state.eval("=42") + assert result == 42 + + +class TestDeclarativeActionExecutor: + """Tests for DeclarativeActionExecutor subclasses.""" + + @pytest.fixture + def mock_context(self, mock_shared_state): + """Create a mock workflow context.""" + ctx = MagicMock() + ctx.shared_state = mock_shared_state + ctx.send_message = AsyncMock() + ctx.yield_output = AsyncMock() + return ctx + + @pytest.fixture + def mock_shared_state(self): + """Create a mock shared state.""" + shared_state = MagicMock() + shared_state._data = {} + + async def mock_get(key): + if key not in shared_state._data: + raise KeyError(key) + return shared_state._data[key] + + async def mock_set(key, value): + shared_state._data[key] = value + + shared_state.get = AsyncMock(side_effect=mock_get) + shared_state.set = AsyncMock(side_effect=mock_set) + + return shared_state + + @pytest.mark.asyncio + async def test_set_value_executor(self, mock_context, mock_shared_state): + """Test SetValueExecutor.""" + # Initialize state + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + action_def = { + "kind": "SetValue", + "path": "turn.result", + "value": "test value", + } + executor = SetValueExecutor(action_def) + + # Execute + await executor.handle_action(ActionTrigger(), mock_context) + + # Verify action complete was sent + mock_context.send_message.assert_called_once() + message = mock_context.send_message.call_args[0][0] + assert isinstance(message, ActionComplete) + + @pytest.mark.asyncio + async def test_send_activity_executor(self, mock_context, mock_shared_state): + """Test SendActivityExecutor.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + action_def = { + "kind": "SendActivity", + "activity": {"text": "Hello, world!"}, + } + executor = SendActivityExecutor(action_def) + + # Execute + await executor.handle_action(ActionTrigger(), mock_context) + + # Verify output was yielded + mock_context.yield_output.assert_called_once_with("Hello, world!") + + # Note: ConditionEvaluatorExecutor tests removed - conditions are now evaluated on edges + + async def test_foreach_init_with_items(self, mock_context, mock_shared_state): + """Test ForeachInitExecutor with items.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + await state.set("turn.items", ["a", "b", "c"]) + + action_def = { + "kind": "Foreach", + "itemsSource": "=turn.items", + "iteratorVariable": "turn.item", + } + executor = ForeachInitExecutor(action_def) + + # Execute + await executor.handle_action(ActionTrigger(), mock_context) + + # Verify result + mock_context.send_message.assert_called_once() + message = mock_context.send_message.call_args[0][0] + assert isinstance(message, LoopIterationResult) + assert message.has_next is True + assert message.current_index == 0 + assert message.current_item == "a" + + @pytest.mark.asyncio + async def test_foreach_init_empty(self, mock_context, mock_shared_state): + """Test ForeachInitExecutor with empty items.""" + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + action_def = { + "kind": "Foreach", + "itemsSource": "=turn.empty", + "iteratorVariable": "turn.item", + } + executor = ForeachInitExecutor(action_def) + + # Execute + await executor.handle_action(ActionTrigger(), mock_context) + + # Verify result + mock_context.send_message.assert_called_once() + message = mock_context.send_message.call_args[0][0] + assert isinstance(message, LoopIterationResult) + assert message.has_next is False + + +class TestDeclarativeGraphBuilder: + """Tests for DeclarativeGraphBuilder.""" + + def test_all_action_executors_available(self): + """Test that all expected action types have executors.""" + expected_actions = [ + "SetValue", + "SetVariable", + "SendActivity", + "EmitEvent", + "EndWorkflow", + "InvokeAzureAgent", + "Question", + ] + + for action in expected_actions: + assert action in ALL_ACTION_EXECUTORS, f"Missing executor for {action}" + + def test_build_empty_workflow(self): + """Test building a workflow with no actions raises an error.""" + yaml_def = {"name": "empty_workflow", "actions": []} + builder = DeclarativeGraphBuilder(yaml_def) + + with pytest.raises(ValueError, match="Cannot build workflow with no actions"): + builder.build() + + def test_build_simple_workflow(self): + """Test building a workflow with simple sequential actions.""" + yaml_def = { + "name": "simple_workflow", + "actions": [ + {"kind": "SendActivity", "id": "greet", "activity": {"text": "Hello!"}}, + {"kind": "SetValue", "id": "set_count", "path": "turn.count", "value": 1}, + ], + } + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + assert workflow is not None + # Verify executors were created + assert "greet" in builder._executors + assert "set_count" in builder._executors + + def test_build_workflow_with_if(self): + """Test building a workflow with If control flow.""" + yaml_def = { + "name": "conditional_workflow", + "actions": [ + { + "kind": "If", + "id": "check_flag", + "condition": "=turn.flag", + "then": [ + {"kind": "SendActivity", "id": "say_yes", "activity": {"text": "Yes!"}}, + ], + "else": [ + {"kind": "SendActivity", "id": "say_no", "activity": {"text": "No!"}}, + ], + }, + ], + } + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + assert workflow is not None + # Verify branch executors were created + # Note: No join executors - branches wire directly to successor + assert "say_yes" in builder._executors + assert "say_no" in builder._executors + # Entry node is created when If is first action + assert "_workflow_entry" in builder._executors + + def test_build_workflow_with_foreach(self): + """Test building a workflow with Foreach loop.""" + yaml_def = { + "name": "loop_workflow", + "actions": [ + { + "kind": "Foreach", + "id": "process_items", + "itemsSource": "=turn.items", + "iteratorVariable": "turn.item", + "actions": [ + {"kind": "SendActivity", "id": "show_item", "activity": {"text": "=turn.item"}}, + ], + }, + ], + } + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + assert workflow is not None + # Verify loop executors were created + assert "process_items_init" in builder._executors + assert "process_items_next" in builder._executors + assert "process_items_exit" in builder._executors + assert "show_item" in builder._executors + + def test_build_workflow_with_switch(self): + """Test building a workflow with Switch control flow.""" + yaml_def = { + "name": "switch_workflow", + "actions": [ + { + "kind": "Switch", + "id": "check_status", + "conditions": [ + { + "condition": '=turn.status = "active"', + "actions": [ + {"kind": "SendActivity", "id": "say_active", "activity": {"text": "Active"}}, + ], + }, + { + "condition": '=turn.status = "pending"', + "actions": [ + {"kind": "SendActivity", "id": "say_pending", "activity": {"text": "Pending"}}, + ], + }, + ], + "else": [ + {"kind": "SendActivity", "id": "say_unknown", "activity": {"text": "Unknown"}}, + ], + }, + ], + } + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + assert workflow is not None + # Verify switch executors were created + # Note: No join executors - branches wire directly to successor + assert "say_active" in builder._executors + assert "say_pending" in builder._executors + assert "say_unknown" in builder._executors + # Entry node is created when Switch is first action + assert "_workflow_entry" in builder._executors + + +class TestAgentExecutors: + """Tests for agent-related executors.""" + + @pytest.fixture + def mock_context(self, mock_shared_state): + """Create a mock workflow context.""" + ctx = MagicMock() + ctx.shared_state = mock_shared_state + ctx.send_message = AsyncMock() + ctx.yield_output = AsyncMock() + return ctx + + @pytest.fixture + def mock_shared_state(self): + """Create a mock shared state.""" + shared_state = MagicMock() + shared_state._data = {} + + async def mock_get(key): + if key not in shared_state._data: + raise KeyError(key) + return shared_state._data[key] + + async def mock_set(key, value): + shared_state._data[key] = value + + shared_state.get = AsyncMock(side_effect=mock_get) + shared_state.set = AsyncMock(side_effect=mock_set) + + return shared_state + + @pytest.mark.asyncio + async def test_invoke_agent_not_found(self, mock_context, mock_shared_state): + """Test InvokeAzureAgentExecutor when agent not found.""" + from agent_framework_declarative._workflows._graph._executors_agents import ( + InvokeAzureAgentExecutor, + ) + + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + action_def = { + "kind": "InvokeAzureAgent", + "agent": "non_existent_agent", + "input": "test input", + } + executor = InvokeAzureAgentExecutor(action_def) + + # Execute + await executor.handle_action(ActionTrigger(), mock_context) + + # Verify it completed (with error stored in state) + mock_context.send_message.assert_called_once() + + +class TestHumanInputExecutors: + """Tests for human input executors.""" + + @pytest.fixture + def mock_context(self, mock_shared_state): + """Create a mock workflow context.""" + ctx = MagicMock() + ctx.shared_state = mock_shared_state + ctx.send_message = AsyncMock() + ctx.yield_output = AsyncMock() + return ctx + + @pytest.fixture + def mock_shared_state(self): + """Create a mock shared state.""" + shared_state = MagicMock() + shared_state._data = {} + + async def mock_get(key): + if key not in shared_state._data: + raise KeyError(key) + return shared_state._data[key] + + async def mock_set(key, value): + shared_state._data[key] = value + + shared_state.get = AsyncMock(side_effect=mock_get) + shared_state.set = AsyncMock(side_effect=mock_set) + + return shared_state + + @pytest.mark.asyncio + async def test_question_executor(self, mock_context, mock_shared_state): + """Test QuestionExecutor.""" + from agent_framework_declarative._workflows._graph._executors_human_input import ( + HumanInputRequest, + QuestionExecutor, + ) + + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + action_def = { + "kind": "Question", + "text": "What is your name?", + "property": "turn.name", + "defaultValue": "Anonymous", + } + executor = QuestionExecutor(action_def) + + # Execute + await executor.handle_action(ActionTrigger(), mock_context) + + # Verify human input request was yielded + assert mock_context.yield_output.called + request = mock_context.yield_output.call_args_list[0][0][0] + assert isinstance(request, HumanInputRequest) + assert request.request_type == "question" + assert "What is your name?" in request.message + + @pytest.mark.asyncio + async def test_confirmation_executor(self, mock_context, mock_shared_state): + """Test ConfirmationExecutor.""" + from agent_framework_declarative._workflows._graph._executors_human_input import ( + ConfirmationExecutor, + HumanInputRequest, + ) + + state = DeclarativeWorkflowState(mock_shared_state) + await state.initialize() + + action_def = { + "kind": "Confirmation", + "text": "Do you want to continue?", + "property": "turn.confirmed", + "yesLabel": "Yes, continue", + "noLabel": "No, stop", + } + executor = ConfirmationExecutor(action_def) + + # Execute + await executor.handle_action(ActionTrigger(), mock_context) + + # Verify confirmation request was yielded + assert mock_context.yield_output.called + request = mock_context.yield_output.call_args_list[0][0][0] + assert isinstance(request, HumanInputRequest) + assert request.request_type == "confirmation" + assert "continue" in request.message.lower() diff --git a/python/packages/declarative/tests/test_graph_workflow_integration.py b/python/packages/declarative/tests/test_graph_workflow_integration.py new file mode 100644 index 0000000000..ecb1c76092 --- /dev/null +++ b/python/packages/declarative/tests/test_graph_workflow_integration.py @@ -0,0 +1,332 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Integration tests for declarative workflows. + +These tests verify: +- End-to-end workflow execution +- Checkpointing at action boundaries +- WorkflowFactory creating graph-based workflows +- Pause/resume capabilities +""" + +import pytest + +from agent_framework_declarative._workflows._factory import WorkflowFactory +from agent_framework_declarative._workflows._graph import ( + ActionTrigger, + DeclarativeGraphBuilder, +) + + +class TestGraphBasedWorkflowExecution: + """Integration tests for graph-based workflow execution.""" + + @pytest.mark.asyncio + async def test_simple_sequential_workflow(self): + """Test a simple sequential workflow with SendActivity actions.""" + yaml_def = { + "name": "simple_workflow", + "actions": [ + {"kind": "SendActivity", "id": "greet", "activity": {"text": "Hello!"}}, + {"kind": "SetValue", "id": "set_count", "path": "turn.count", "value": 1}, + {"kind": "SendActivity", "id": "done", "activity": {"text": "Done!"}}, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + # Run the workflow + events = await workflow.run(ActionTrigger()) + + # Verify outputs were produced + outputs = events.get_outputs() + assert "Hello!" in outputs + assert "Done!" in outputs + + @pytest.mark.asyncio + async def test_workflow_with_conditional(self): + """Test workflow with If conditional branching.""" + yaml_def = { + "name": "conditional_workflow", + "actions": [ + {"kind": "SetValue", "id": "set_flag", "path": "turn.flag", "value": True}, + { + "kind": "If", + "id": "check_flag", + "condition": "=turn.flag", + "then": [ + {"kind": "SendActivity", "id": "say_yes", "activity": {"text": "Flag is true!"}}, + ], + "else": [ + {"kind": "SendActivity", "id": "say_no", "activity": {"text": "Flag is false!"}}, + ], + }, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + # Run the workflow + events = await workflow.run(ActionTrigger()) + outputs = events.get_outputs() + + # Should take the "then" branch since flag is True + assert "Flag is true!" in outputs + assert "Flag is false!" not in outputs + + @pytest.mark.asyncio + async def test_workflow_with_foreach_loop(self): + """Test workflow with Foreach loop.""" + yaml_def = { + "name": "loop_workflow", + "actions": [ + {"kind": "SetValue", "id": "set_items", "path": "turn.items", "value": ["a", "b", "c"]}, + { + "kind": "Foreach", + "id": "process_items", + "itemsSource": "=turn.items", + "iteratorVariable": "turn.item", + "actions": [ + {"kind": "SendActivity", "id": "show_item", "activity": {"text": "=turn.item"}}, + ], + }, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + # Run the workflow + events = await workflow.run(ActionTrigger()) + outputs = events.get_outputs() + + # Should output each item + assert "a" in outputs + assert "b" in outputs + assert "c" in outputs + + @pytest.mark.asyncio + async def test_workflow_with_switch(self): + """Test workflow with Switch/ConditionGroup.""" + yaml_def = { + "name": "switch_workflow", + "actions": [ + {"kind": "SetValue", "id": "set_level", "path": "turn.level", "value": 2}, + { + "kind": "Switch", + "id": "check_level", + "conditions": [ + { + "condition": "=turn.level == 1", + "actions": [ + {"kind": "SendActivity", "id": "level_1", "activity": {"text": "Level 1"}}, + ], + }, + { + "condition": "=turn.level == 2", + "actions": [ + {"kind": "SendActivity", "id": "level_2", "activity": {"text": "Level 2"}}, + ], + }, + ], + "else": [ + {"kind": "SendActivity", "id": "default", "activity": {"text": "Other level"}}, + ], + }, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + # Run the workflow + events = await workflow.run(ActionTrigger()) + outputs = events.get_outputs() + + # Should take the level 2 branch + assert "Level 2" in outputs + assert "Level 1" not in outputs + assert "Other level" not in outputs + + +class TestWorkflowFactory: + """Tests for WorkflowFactory.""" + + def test_factory_creates_workflow(self): + """Test creating workflow.""" + factory = WorkflowFactory() + + yaml_content = """ +name: test_workflow +actions: + - kind: SendActivity + id: greet + activity: + text: "Hello from graph mode!" + - kind: SetValue + id: set_val + path: turn.result + value: 42 +""" + workflow = factory.create_workflow_from_yaml(yaml_content) + + assert workflow is not None + assert hasattr(workflow, "_declarative_agents") + + @pytest.mark.asyncio + async def test_workflow_execution(self): + """Test executing a workflow.""" + factory = WorkflowFactory() + + yaml_content = """ +name: graph_execution_test +actions: + - kind: SendActivity + id: start + activity: + text: "Starting workflow" + - kind: SetValue + id: set_message + path: turn.message + value: "Hello World" + - kind: SendActivity + id: end + activity: + text: "Workflow complete" +""" + workflow = factory.create_workflow_from_yaml(yaml_content) + + # Execute the workflow + events = await workflow.run(ActionTrigger()) + outputs = events.get_outputs() + + assert "Starting workflow" in outputs + assert "Workflow complete" in outputs + + +class TestGraphWorkflowCheckpointing: + """Tests for checkpointing capabilities of graph-based workflows.""" + + def test_workflow_has_multiple_executors(self): + """Test that graph-based workflow creates multiple executor nodes.""" + yaml_def = { + "name": "multi_executor_workflow", + "actions": [ + {"kind": "SetValue", "id": "step1", "path": "turn.a", "value": 1}, + {"kind": "SetValue", "id": "step2", "path": "turn.b", "value": 2}, + {"kind": "SetValue", "id": "step3", "path": "turn.c", "value": 3}, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + _workflow = builder.build() # noqa: F841 + + # Verify multiple executors were created + assert "step1" in builder._executors + assert "step2" in builder._executors + assert "step3" in builder._executors + assert len(builder._executors) == 3 + + def test_workflow_executor_connectivity(self): + """Test that executors are properly connected in sequence.""" + yaml_def = { + "name": "connected_workflow", + "actions": [ + {"kind": "SendActivity", "id": "a", "activity": {"text": "A"}}, + {"kind": "SendActivity", "id": "b", "activity": {"text": "B"}}, + {"kind": "SendActivity", "id": "c", "activity": {"text": "C"}}, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + # Verify all executors exist + assert len(builder._executors) == 3 + + # Verify the workflow can be inspected + assert workflow is not None + + +class TestGraphWorkflowVisualization: + """Tests for workflow visualization capabilities.""" + + def test_workflow_can_be_built(self): + """Test that complex workflows can be built successfully.""" + yaml_def = { + "name": "complex_workflow", + "actions": [ + {"kind": "SendActivity", "id": "intro", "activity": {"text": "Starting"}}, + { + "kind": "If", + "id": "branch", + "condition": "=true", + "then": [ + {"kind": "SendActivity", "id": "then_msg", "activity": {"text": "Then branch"}}, + ], + "else": [ + {"kind": "SendActivity", "id": "else_msg", "activity": {"text": "Else branch"}}, + ], + }, + {"kind": "SendActivity", "id": "outro", "activity": {"text": "Done"}}, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + # Verify the workflow was built + assert workflow is not None + + # Verify expected executors exist + # intro, branch_condition, then_msg, else_msg, branch_join, outro + assert "intro" in builder._executors + assert "then_msg" in builder._executors + assert "else_msg" in builder._executors + assert "outro" in builder._executors + + +class TestGraphWorkflowStateManagement: + """Tests for state management across graph executor nodes.""" + + @pytest.mark.asyncio + async def test_state_persists_across_executors(self): + """Test that state set in one executor is available in the next.""" + yaml_def = { + "name": "state_test", + "actions": [ + {"kind": "SetValue", "id": "set", "path": "turn.value", "value": "test_data"}, + {"kind": "SendActivity", "id": "send", "activity": {"text": "=turn.value"}}, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + events = await workflow.run(ActionTrigger()) + outputs = events.get_outputs() + + # The SendActivity should have access to the value set by SetValue + assert "test_data" in outputs + + @pytest.mark.asyncio + async def test_multiple_variables(self): + """Test setting and using multiple variables.""" + yaml_def = { + "name": "multi_var_test", + "actions": [ + {"kind": "SetValue", "id": "set_a", "path": "turn.a", "value": "Hello"}, + {"kind": "SetValue", "id": "set_b", "path": "turn.b", "value": "World"}, + {"kind": "SendActivity", "id": "send", "activity": {"text": "=turn.a"}}, + ], + } + + builder = DeclarativeGraphBuilder(yaml_def) + workflow = builder.build() + + events = await workflow.run(ActionTrigger()) + outputs = events.get_outputs() + + assert "Hello" in outputs diff --git a/python/packages/declarative/tests/test_human_input_handlers.py b/python/packages/declarative/tests/test_human_input_handlers.py new file mode 100644 index 0000000000..34b0b4e675 --- /dev/null +++ b/python/packages/declarative/tests/test_human_input_handlers.py @@ -0,0 +1,286 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Tests for human-in-the-loop action handlers.""" + +import pytest + +from agent_framework_declarative._workflows._handlers import ActionContext, get_action_handler +from agent_framework_declarative._workflows._human_input import ( + ExternalInputRequest, + process_external_loop, + validate_input_response, +) +from agent_framework_declarative._workflows._state import WorkflowState + + +def create_action_context(action: dict, state: WorkflowState | None = None): + """Create a minimal action context for testing.""" + if state is None: + state = WorkflowState() + + async def execute_actions(actions, state): + for act in actions: + handler = get_action_handler(act.get("kind")) + if handler: + async for event in handler( + ActionContext( + state=state, + action=act, + execute_actions=execute_actions, + agents={}, + bindings={}, + ) + ): + yield event + + return ActionContext( + state=state, + action=action, + execute_actions=execute_actions, + agents={}, + bindings={}, + ) + + +class TestQuestionHandler: + """Tests for Question action handler.""" + + @pytest.mark.asyncio + async def test_question_emits_request_info_event(self): + """Test that Question handler emits ExternalInputRequest.""" + ctx = create_action_context({ + "kind": "Question", + "id": "ask_name", + "variable": "Local.userName", + "prompt": "What is your name?", + }) + + handler = get_action_handler("Question") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + assert isinstance(events[0], ExternalInputRequest) + assert events[0].request_id == "ask_name" + assert events[0].prompt == "What is your name?" + assert events[0].variable == "Local.userName" + + @pytest.mark.asyncio + async def test_question_with_choices(self): + """Test Question with multiple choice options.""" + ctx = create_action_context({ + "kind": "Question", + "id": "ask_choice", + "variable": "Local.selection", + "prompt": "Select an option:", + "choices": ["Option A", "Option B", "Option C"], + "default": "Option A", + }) + + handler = get_action_handler("Question") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + event = events[0] + assert isinstance(event, ExternalInputRequest) + assert event.choices == ["Option A", "Option B", "Option C"] + assert event.default_value == "Option A" + + @pytest.mark.asyncio + async def test_question_with_validation(self): + """Test Question with validation rules.""" + ctx = create_action_context({ + "kind": "Question", + "id": "ask_email", + "variable": "Local.email", + "prompt": "Enter your email:", + "validation": { + "required": True, + "pattern": r"^[\w\.-]+@[\w\.-]+\.\w+$", + }, + }) + + handler = get_action_handler("Question") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + event = events[0] + assert event.validation == { + "required": True, + "pattern": r"^[\w\.-]+@[\w\.-]+\.\w+$", + } + + +class TestRequestExternalInputHandler: + """Tests for RequestExternalInput action handler.""" + + @pytest.mark.asyncio + async def test_request_external_input(self): + """Test RequestExternalInput handler emits event.""" + ctx = create_action_context({ + "kind": "RequestExternalInput", + "id": "get_approval", + "variable": "Local.approval", + "prompt": "Please approve or reject", + "timeout": 300, + }) + + handler = get_action_handler("RequestExternalInput") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + event = events[0] + assert isinstance(event, ExternalInputRequest) + assert event.request_id == "get_approval" + assert event.variable == "Local.approval" + assert event.validation == {"timeout": 300} + + +class TestWaitForInputHandler: + """Tests for WaitForInput action handler.""" + + @pytest.mark.asyncio + async def test_wait_for_input(self): + """Test WaitForInput handler.""" + ctx = create_action_context({ + "kind": "WaitForInput", + "id": "wait", + "variable": "Local.response", + "message": "Waiting...", + }) + + handler = get_action_handler("WaitForInput") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + event = events[0] + assert isinstance(event, ExternalInputRequest) + assert event.request_id == "wait" + assert event.prompt == "Waiting..." + + +class TestProcessExternalLoop: + """Tests for process_external_loop helper function.""" + + def test_no_external_loop(self): + """Test when no external loop is configured.""" + state = WorkflowState() + result, expr = process_external_loop({}, state) + + assert result is False + assert expr is None + + def test_external_loop_true_condition(self): + """Test when external loop condition evaluates to true.""" + state = WorkflowState() + state.set("turn.isComplete", False) + + input_config = { + "externalLoop": { + "when": "=!Local.isComplete", + }, + } + + result, expr = process_external_loop(input_config, state) + + # !False = True, so loop should continue + assert result is True + assert expr == "=!Local.isComplete" + + def test_external_loop_false_condition(self): + """Test when external loop condition evaluates to false.""" + state = WorkflowState() + state.set("turn.isComplete", True) + + input_config = { + "externalLoop": { + "when": "=!Local.isComplete", + }, + } + + result, expr = process_external_loop(input_config, state) + + # !True = False, so loop should stop + assert result is False + + +class TestValidateInputResponse: + """Tests for validate_input_response helper function.""" + + def test_no_validation(self): + """Test with no validation rules.""" + is_valid, error = validate_input_response("any value", None) + assert is_valid is True + assert error is None + + def test_required_valid(self): + """Test required validation with valid value.""" + is_valid, error = validate_input_response("value", {"required": True}) + assert is_valid is True + assert error is None + + def test_required_empty_string(self): + """Test required validation with empty string.""" + is_valid, error = validate_input_response("", {"required": True}) + assert is_valid is False + assert "required" in error.lower() + + def test_required_none(self): + """Test required validation with None.""" + is_valid, error = validate_input_response(None, {"required": True}) + assert is_valid is False + assert "required" in error.lower() + + def test_min_length_valid(self): + """Test minLength validation with valid value.""" + is_valid, error = validate_input_response("hello", {"minLength": 3}) + assert is_valid is True + + def test_min_length_invalid(self): + """Test minLength validation with too short value.""" + is_valid, error = validate_input_response("hi", {"minLength": 3}) + assert is_valid is False + assert "minimum length" in error.lower() + + def test_max_length_valid(self): + """Test maxLength validation with valid value.""" + is_valid, error = validate_input_response("hello", {"maxLength": 10}) + assert is_valid is True + + def test_max_length_invalid(self): + """Test maxLength validation with too long value.""" + is_valid, error = validate_input_response("hello world", {"maxLength": 5}) + assert is_valid is False + assert "maximum length" in error.lower() + + def test_min_value_valid(self): + """Test min validation for numbers.""" + is_valid, error = validate_input_response(10, {"min": 5}) + assert is_valid is True + + def test_min_value_invalid(self): + """Test min validation with too small number.""" + is_valid, error = validate_input_response(3, {"min": 5}) + assert is_valid is False + assert "minimum value" in error.lower() + + def test_max_value_valid(self): + """Test max validation for numbers.""" + is_valid, error = validate_input_response(5, {"max": 10}) + assert is_valid is True + + def test_max_value_invalid(self): + """Test max validation with too large number.""" + is_valid, error = validate_input_response(15, {"max": 10}) + assert is_valid is False + assert "maximum value" in error.lower() + + def test_pattern_valid(self): + """Test pattern validation with matching value.""" + is_valid, error = validate_input_response("test@example.com", {"pattern": r"^[\w\.-]+@[\w\.-]+\.\w+$"}) + assert is_valid is True + + def test_pattern_invalid(self): + """Test pattern validation with non-matching value.""" + is_valid, error = validate_input_response("not-an-email", {"pattern": r"^[\w\.-]+@[\w\.-]+\.\w+$"}) + assert is_valid is False + assert "pattern" in error.lower() diff --git a/python/packages/declarative/tests/test_powerfx_functions.py b/python/packages/declarative/tests/test_powerfx_functions.py new file mode 100644 index 0000000000..050fa96786 --- /dev/null +++ b/python/packages/declarative/tests/test_powerfx_functions.py @@ -0,0 +1,242 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Tests for custom PowerFx-like functions.""" + +from agent_framework_declarative._workflows._powerfx_functions import ( + CUSTOM_FUNCTIONS, + assistant_message, + concat_text, + count_rows, + find, + first, + is_blank, + last, + lower, + message_text, + search_table, + system_message, + upper, + user_message, +) + + +class TestMessageText: + """Tests for MessageText function.""" + + def test_message_text_from_string(self): + """Test extracting text from a plain string.""" + assert message_text("Hello") == "Hello" + + def test_message_text_from_single_dict(self): + """Test extracting text from a single message dict.""" + msg = {"role": "assistant", "content": "Hello world"} + assert message_text(msg) == "Hello world" + + def test_message_text_from_list(self): + """Test extracting text from a list of messages.""" + msgs = [ + {"role": "user", "content": "Hi"}, + {"role": "assistant", "content": "Hello"}, + ] + assert message_text(msgs) == "Hi Hello" + + def test_message_text_from_none(self): + """Test that None returns empty string.""" + assert message_text(None) == "" + + def test_message_text_empty_list(self): + """Test that empty list returns empty string.""" + assert message_text([]) == "" + + +class TestUserMessage: + """Tests for UserMessage function.""" + + def test_user_message_creates_dict(self): + """Test that UserMessage creates correct dict.""" + msg = user_message("Hello") + assert msg == {"role": "user", "content": "Hello"} + + def test_user_message_with_none(self): + """Test UserMessage with None.""" + msg = user_message(None) + assert msg == {"role": "user", "content": ""} + + +class TestAssistantMessage: + """Tests for AssistantMessage function.""" + + def test_assistant_message_creates_dict(self): + """Test that AssistantMessage creates correct dict.""" + msg = assistant_message("Hello") + assert msg == {"role": "assistant", "content": "Hello"} + + +class TestSystemMessage: + """Tests for SystemMessage function.""" + + def test_system_message_creates_dict(self): + """Test that SystemMessage creates correct dict.""" + msg = system_message("You are helpful") + assert msg == {"role": "system", "content": "You are helpful"} + + +class TestIsBlank: + """Tests for IsBlank function.""" + + def test_is_blank_none(self): + """Test that None is blank.""" + assert is_blank(None) is True + + def test_is_blank_empty_string(self): + """Test that empty string is blank.""" + assert is_blank("") is True + + def test_is_blank_whitespace(self): + """Test that whitespace-only string is blank.""" + assert is_blank(" ") is True + + def test_is_blank_empty_list(self): + """Test that empty list is blank.""" + assert is_blank([]) is True + + def test_is_blank_non_empty(self): + """Test that non-empty values are not blank.""" + assert is_blank("hello") is False + assert is_blank([1, 2, 3]) is False + assert is_blank(0) is False + + +class TestCountRows: + """Tests for CountRows function.""" + + def test_count_rows_list(self): + """Test counting list items.""" + assert count_rows([1, 2, 3]) == 3 + + def test_count_rows_empty(self): + """Test counting empty list.""" + assert count_rows([]) == 0 + + def test_count_rows_none(self): + """Test counting None.""" + assert count_rows(None) == 0 + + +class TestFirstLast: + """Tests for First and Last functions.""" + + def test_first_returns_first_item(self): + """Test that First returns first item.""" + assert first([1, 2, 3]) == 1 + + def test_last_returns_last_item(self): + """Test that Last returns last item.""" + assert last([1, 2, 3]) == 3 + + def test_first_empty_returns_none(self): + """Test that First returns None for empty list.""" + assert first([]) is None + + def test_last_empty_returns_none(self): + """Test that Last returns None for empty list.""" + assert last([]) is None + + +class TestFind: + """Tests for Find function.""" + + def test_find_substring(self): + """Test finding a substring.""" + result = find("world", "Hello world") + assert result == 7 # 1-based index + + def test_find_not_found(self): + """Test when substring not found - returns Blank (None) per PowerFx semantics.""" + result = find("xyz", "Hello world") + assert result is None + + def test_find_at_start(self): + """Test finding at start of string.""" + result = find("Hello", "Hello world") + assert result == 1 + + +class TestUpperLower: + """Tests for Upper and Lower functions.""" + + def test_upper(self): + """Test uppercase conversion.""" + assert upper("hello") == "HELLO" + + def test_lower(self): + """Test lowercase conversion.""" + assert lower("HELLO") == "hello" + + def test_upper_none(self): + """Test upper with None.""" + assert upper(None) == "" + + +class TestConcatText: + """Tests for Concat function.""" + + def test_concat_simple_list(self): + """Test concatenating simple list.""" + assert concat_text(["a", "b", "c"], separator=", ") == "a, b, c" + + def test_concat_with_field(self): + """Test concatenating with field extraction.""" + items = [{"name": "Alice"}, {"name": "Bob"}] + assert concat_text(items, field="name", separator=", ") == "Alice, Bob" + + +class TestSearchTable: + """Tests for Search function.""" + + def test_search_finds_matching(self): + """Test search finds matching items.""" + items = [ + {"name": "Alice", "age": 30}, + {"name": "Bob", "age": 25}, + {"name": "Charlie", "age": 35}, + ] + result = search_table(items, "Bob", "name") + assert len(result) == 1 + assert result[0]["name"] == "Bob" + + def test_search_case_insensitive(self): + """Test search is case insensitive.""" + items = [{"name": "Alice"}] + result = search_table(items, "alice", "name") + assert len(result) == 1 + + def test_search_partial_match(self): + """Test search finds partial matches.""" + items = [{"name": "Alice Smith"}, {"name": "Bob Jones"}] + result = search_table(items, "Smith", "name") + assert len(result) == 1 + + +class TestCustomFunctionsRegistry: + """Tests for the CUSTOM_FUNCTIONS registry.""" + + def test_all_functions_registered(self): + """Test that all functions are in the registry.""" + expected = [ + "MessageText", + "UserMessage", + "AssistantMessage", + "SystemMessage", + "IsBlank", + "CountRows", + "First", + "Last", + "Find", + "Upper", + "Lower", + "Concat", + "Search", + ] + for name in expected: + assert name in CUSTOM_FUNCTIONS diff --git a/python/packages/declarative/tests/test_workflow_factory.py b/python/packages/declarative/tests/test_workflow_factory.py new file mode 100644 index 0000000000..80b3342983 --- /dev/null +++ b/python/packages/declarative/tests/test_workflow_factory.py @@ -0,0 +1,279 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Unit tests for WorkflowFactory.""" + +import pytest + +from agent_framework_declarative._workflows._factory import ( + DeclarativeWorkflowError, + WorkflowFactory, +) + + +class TestWorkflowFactoryValidation: + """Tests for workflow definition validation.""" + + def test_missing_actions_raises(self): + """Test that missing 'actions' field raises an error.""" + factory = WorkflowFactory() + with pytest.raises(DeclarativeWorkflowError, match="must have 'actions' field"): + factory.create_workflow_from_yaml(""" +name: test-workflow +description: A test +# Missing 'actions' field +""") + + def test_actions_not_list_raises(self): + """Test that non-list 'actions' field raises an error.""" + factory = WorkflowFactory() + with pytest.raises(DeclarativeWorkflowError, match="'actions' must be a list"): + factory.create_workflow_from_yaml(""" +name: test-workflow +actions: "not a list" +""") + + def test_action_missing_kind_raises(self): + """Test that actions without 'kind' field raise an error.""" + factory = WorkflowFactory() + with pytest.raises(DeclarativeWorkflowError, match="missing 'kind' field"): + factory.create_workflow_from_yaml(""" +name: test-workflow +actions: + - path: turn.value + value: test +""") + + def test_valid_minimal_workflow(self): + """Test creating a valid minimal workflow.""" + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: minimal-workflow +actions: + - kind: SetValue + path: turn.result + value: done +""") + + assert workflow is not None + assert workflow.name == "minimal-workflow" + + +class TestWorkflowFactoryExecution: + """Tests for workflow execution.""" + + @pytest.mark.asyncio + async def test_execute_set_value_workflow(self): + """Test executing a simple SetValue workflow.""" + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: set-value-test +actions: + - kind: SetValue + path: turn.greeting + value: Hello + - kind: SendActivity + activity: + text: Done +""") + + result = await workflow.run({"input": "test"}) + outputs = result.get_outputs() + + # The workflow should produce output from SendActivity + assert len(outputs) > 0 + + @pytest.mark.asyncio + async def test_execute_send_activity_workflow(self): + """Test executing a workflow that sends activities.""" + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: send-activity-test +actions: + - kind: SendActivity + activity: + text: Hello, world! +""") + + result = await workflow.run({"input": "test"}) + outputs = result.get_outputs() + + # Should have a TextOutputEvent + assert len(outputs) >= 1 + + @pytest.mark.asyncio + async def test_execute_foreach_workflow(self): + """Test executing a workflow with foreach.""" + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: foreach-test +actions: + - kind: Foreach + source: + - apple + - banana + - cherry + itemName: fruit + actions: + - kind: AppendValue + path: turn.fruits + value: processed +""") + + _result = await workflow.run({}) # noqa: F841 + # The foreach should have processed 3 items + # We can check this by examining the workflow outputs + + @pytest.mark.asyncio + async def test_execute_if_workflow(self): + """Test executing a workflow with conditional branching.""" + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: if-test +actions: + - kind: If + condition: true + then: + - kind: SendActivity + activity: + text: Condition was true + else: + - kind: SendActivity + activity: + text: Condition was false +""") + + result = await workflow.run({}) + outputs = result.get_outputs() + + # Check for the expected text in WorkflowOutputEvent + _text_outputs = [str(o) for o in outputs if isinstance(o, str) or hasattr(o, "data")] # noqa: F841 + assert any("Condition was true" in str(o) for o in outputs) + + +class TestWorkflowFactoryAgentRegistration: + """Tests for agent registration.""" + + def test_register_agent(self): + """Test registering an agent with the factory.""" + + class MockAgent: + name = "mock-agent" + + factory = WorkflowFactory() + factory.register_agent("myAgent", MockAgent()) + + assert "myAgent" in factory._agents + + def test_register_binding(self): + """Test registering a binding with the factory.""" + + def my_function(x): + return x * 2 + + factory = WorkflowFactory() + factory.register_binding("double", my_function) + + assert "double" in factory._bindings + assert factory._bindings["double"](5) == 10 + + +class TestWorkflowFactoryFromPath: + """Tests for loading workflows from file paths.""" + + def test_nonexistent_file_raises(self, tmp_path): + """Test that loading from a nonexistent file raises FileNotFoundError.""" + factory = WorkflowFactory() + with pytest.raises(FileNotFoundError): + factory.create_workflow_from_yaml_path(tmp_path / "nonexistent.yaml") + + def test_load_from_file(self, tmp_path): + """Test loading a workflow from a file.""" + workflow_file = tmp_path / "workflow.yaml" + workflow_file.write_text(""" +name: file-workflow +actions: + - kind: SetValue + path: turn.loaded + value: true +""") + + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml_path(workflow_file) + + assert workflow is not None + assert workflow.name == "file-workflow" + + +class TestDisplayNameMetadata: + """Tests for displayName metadata support.""" + + @pytest.mark.asyncio + async def test_action_with_display_name(self): + """Test executing an action with displayName metadata.""" + factory = WorkflowFactory() + workflow = factory.create_workflow_from_yaml(""" +name: display-name-test +actions: + - kind: SetValue + id: set_greeting + displayName: Set the greeting message + path: turn.greeting + value: Hello + - kind: SendActivity + id: send_greeting + displayName: Send greeting to user + activity: + text: Hello, world! +""") + + result = await workflow.run({"input": "test"}) + outputs = result.get_outputs() + + # Should execute successfully with displayName metadata + assert len(outputs) >= 1 + + def test_action_context_display_name_property(self): + """Test that ActionContext provides displayName property.""" + from agent_framework_declarative._workflows._handlers import ActionContext + from agent_framework_declarative._workflows._state import WorkflowState + + state = WorkflowState() + ctx = ActionContext( + state=state, + action={ + "kind": "SetValue", + "id": "test_action", + "displayName": "Test Action Display Name", + "path": "turn.value", + "value": "test", + }, + execute_actions=lambda a, s: None, + agents={}, + bindings={}, + ) + + assert ctx.action_id == "test_action" + assert ctx.display_name == "Test Action Display Name" + assert ctx.action_kind == "SetValue" + + def test_action_context_without_display_name(self): + """Test ActionContext when displayName is not provided.""" + from agent_framework_declarative._workflows._handlers import ActionContext + from agent_framework_declarative._workflows._state import WorkflowState + + state = WorkflowState() + ctx = ActionContext( + state=state, + action={ + "kind": "SetValue", + "path": "turn.value", + "value": "test", + }, + execute_actions=lambda a, s: None, + agents={}, + bindings={}, + ) + + assert ctx.action_id is None + assert ctx.display_name is None + assert ctx.action_kind == "SetValue" diff --git a/python/packages/declarative/tests/test_workflow_handlers.py b/python/packages/declarative/tests/test_workflow_handlers.py new file mode 100644 index 0000000000..35055b3836 --- /dev/null +++ b/python/packages/declarative/tests/test_workflow_handlers.py @@ -0,0 +1,424 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Unit tests for action handlers.""" + +from collections.abc import AsyncGenerator +from typing import Any + +import pytest + +# Import handlers to register them +from agent_framework_declarative._workflows import ( + _actions_basic, # noqa: F401 + _actions_control_flow, # noqa: F401 + _actions_error, # noqa: F401 +) +from agent_framework_declarative._workflows._handlers import ( + ActionContext, + CustomEvent, + TextOutputEvent, + WorkflowEvent, + get_action_handler, + list_action_handlers, +) +from agent_framework_declarative._workflows._state import WorkflowState + + +def create_action_context( + action: dict[str, Any], + inputs: dict[str, Any] | None = None, + agents: dict[str, Any] | None = None, + bindings: dict[str, Any] | None = None, +) -> ActionContext: + """Helper to create an ActionContext for testing.""" + state = WorkflowState(inputs=inputs or {}) + + async def execute_actions( + actions: list[dict[str, Any]], state: WorkflowState + ) -> AsyncGenerator[WorkflowEvent, None]: + """Mock execute_actions that runs handlers for nested actions.""" + for nested_action in actions: + action_kind = nested_action.get("kind") + handler = get_action_handler(action_kind) + if handler: + ctx = ActionContext( + state=state, + action=nested_action, + execute_actions=execute_actions, + agents=agents or {}, + bindings=bindings or {}, + ) + async for event in handler(ctx): + yield event + + return ActionContext( + state=state, + action=action, + execute_actions=execute_actions, + agents=agents or {}, + bindings=bindings or {}, + ) + + +class TestActionHandlerRegistry: + """Tests for action handler registration.""" + + def test_basic_handlers_registered(self): + """Test that basic handlers are registered.""" + handlers = list_action_handlers() + assert "SetValue" in handlers + assert "AppendValue" in handlers + assert "SendActivity" in handlers + assert "EmitEvent" in handlers + + def test_control_flow_handlers_registered(self): + """Test that control flow handlers are registered.""" + handlers = list_action_handlers() + assert "Foreach" in handlers + assert "If" in handlers + assert "Switch" in handlers + assert "RepeatUntil" in handlers + assert "BreakLoop" in handlers + assert "ContinueLoop" in handlers + + def test_error_handlers_registered(self): + """Test that error handlers are registered.""" + handlers = list_action_handlers() + assert "ThrowException" in handlers + assert "TryCatch" in handlers + + def test_get_unknown_handler_returns_none(self): + """Test that getting an unknown handler returns None.""" + assert get_action_handler("UnknownAction") is None + + +class TestSetValueHandler: + """Tests for SetValue action handler.""" + + @pytest.mark.asyncio + async def test_set_simple_value(self): + """Test setting a simple value.""" + ctx = create_action_context({ + "kind": "SetValue", + "path": "turn.result", + "value": "test value", + }) + + handler = get_action_handler("SetValue") + events = [e async for e in handler(ctx)] + + assert len(events) == 0 # SetValue doesn't emit events + assert ctx.state.get("turn.result") == "test value" + + @pytest.mark.asyncio + async def test_set_value_from_input(self): + """Test setting a value from workflow inputs.""" + ctx = create_action_context( + { + "kind": "SetValue", + "path": "turn.copy", + "value": "literal", + }, + inputs={"original": "from input"}, + ) + + handler = get_action_handler("SetValue") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.copy") == "literal" + + +class TestAppendValueHandler: + """Tests for AppendValue action handler.""" + + @pytest.mark.asyncio + async def test_append_to_new_list(self): + """Test appending to a non-existent list creates it.""" + ctx = create_action_context({ + "kind": "AppendValue", + "path": "turn.results", + "value": "item1", + }) + + handler = get_action_handler("AppendValue") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.results") == ["item1"] + + @pytest.mark.asyncio + async def test_append_to_existing_list(self): + """Test appending to an existing list.""" + ctx = create_action_context({ + "kind": "AppendValue", + "path": "turn.results", + "value": "item2", + }) + ctx.state.set("turn.results", ["item1"]) + + handler = get_action_handler("AppendValue") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.results") == ["item1", "item2"] + + +class TestSendActivityHandler: + """Tests for SendActivity action handler.""" + + @pytest.mark.asyncio + async def test_send_text_activity(self): + """Test sending a text activity.""" + ctx = create_action_context({ + "kind": "SendActivity", + "activity": { + "text": "Hello, world!", + }, + }) + + handler = get_action_handler("SendActivity") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + assert isinstance(events[0], TextOutputEvent) + assert events[0].text == "Hello, world!" + + +class TestEmitEventHandler: + """Tests for EmitEvent action handler.""" + + @pytest.mark.asyncio + async def test_emit_custom_event(self): + """Test emitting a custom event.""" + ctx = create_action_context({ + "kind": "EmitEvent", + "event": { + "name": "myEvent", + "data": {"key": "value"}, + }, + }) + + handler = get_action_handler("EmitEvent") + events = [e async for e in handler(ctx)] + + assert len(events) == 1 + assert isinstance(events[0], CustomEvent) + assert events[0].name == "myEvent" + assert events[0].data == {"key": "value"} + + +class TestForeachHandler: + """Tests for Foreach action handler.""" + + @pytest.mark.asyncio + async def test_foreach_basic_iteration(self): + """Test basic foreach iteration.""" + ctx = create_action_context({ + "kind": "Foreach", + "source": ["a", "b", "c"], + "itemName": "letter", + "actions": [ + { + "kind": "AppendValue", + "path": "turn.results", + "value": "processed", + } + ], + }) + + handler = get_action_handler("Foreach") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.results") == ["processed", "processed", "processed"] + + @pytest.mark.asyncio + async def test_foreach_sets_item_and_index(self): + """Test that foreach sets item and index variables.""" + ctx = create_action_context({ + "kind": "Foreach", + "source": ["x", "y"], + "itemName": "item", + "indexName": "idx", + "actions": [], + }) + + # We'll check the last values after iteration + handler = get_action_handler("Foreach") + _events = [e async for e in handler(ctx)] # noqa: F841 + + # After iteration, the last item/index should be set + assert ctx.state.get("turn.item") == "y" + assert ctx.state.get("turn.idx") == 1 + + +class TestIfHandler: + """Tests for If action handler.""" + + @pytest.mark.asyncio + async def test_if_true_branch(self): + """Test that the 'then' branch executes when condition is true.""" + ctx = create_action_context({ + "kind": "If", + "condition": True, + "then": [ + {"kind": "SetValue", "path": "turn.branch", "value": "then"}, + ], + "else": [ + {"kind": "SetValue", "path": "turn.branch", "value": "else"}, + ], + }) + + handler = get_action_handler("If") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.branch") == "then" + + @pytest.mark.asyncio + async def test_if_false_branch(self): + """Test that the 'else' branch executes when condition is false.""" + ctx = create_action_context({ + "kind": "If", + "condition": False, + "then": [ + {"kind": "SetValue", "path": "turn.branch", "value": "then"}, + ], + "else": [ + {"kind": "SetValue", "path": "turn.branch", "value": "else"}, + ], + }) + + handler = get_action_handler("If") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.branch") == "else" + + +class TestSwitchHandler: + """Tests for Switch action handler.""" + + @pytest.mark.asyncio + async def test_switch_matching_case(self): + """Test switch with a matching case.""" + ctx = create_action_context({ + "kind": "Switch", + "value": "option2", + "cases": [ + { + "match": "option1", + "actions": [{"kind": "SetValue", "path": "turn.result", "value": "one"}], + }, + { + "match": "option2", + "actions": [{"kind": "SetValue", "path": "turn.result", "value": "two"}], + }, + ], + "default": [{"kind": "SetValue", "path": "turn.result", "value": "default"}], + }) + + handler = get_action_handler("Switch") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.result") == "two" + + @pytest.mark.asyncio + async def test_switch_default_case(self): + """Test switch falls through to default.""" + ctx = create_action_context({ + "kind": "Switch", + "value": "unknown", + "cases": [ + { + "match": "option1", + "actions": [{"kind": "SetValue", "path": "turn.result", "value": "one"}], + }, + ], + "default": [{"kind": "SetValue", "path": "turn.result", "value": "default"}], + }) + + handler = get_action_handler("Switch") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.result") == "default" + + +class TestRepeatUntilHandler: + """Tests for RepeatUntil action handler.""" + + @pytest.mark.asyncio + async def test_repeat_until_condition_met(self): + """Test repeat until condition becomes true.""" + ctx = create_action_context({ + "kind": "RepeatUntil", + "condition": False, # Will be evaluated each iteration + "maxIterations": 3, + "actions": [ + {"kind": "SetValue", "path": "turn.count", "value": 1}, + ], + }) + # Set up a counter that will cause the loop to exit + ctx.state.set("turn.count", 0) + + handler = get_action_handler("RepeatUntil") + _events = [e async for e in handler(ctx)] # noqa: F841 + + # With condition=False (literal), it will run maxIterations times + assert ctx.state.get("turn.iteration") == 3 + + +class TestTryCatchHandler: + """Tests for TryCatch action handler.""" + + @pytest.mark.asyncio + async def test_try_without_error(self): + """Test try block without errors.""" + ctx = create_action_context({ + "kind": "TryCatch", + "try": [ + {"kind": "SetValue", "path": "turn.result", "value": "success"}, + ], + "catch": [ + {"kind": "SetValue", "path": "turn.result", "value": "caught"}, + ], + }) + + handler = get_action_handler("TryCatch") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.result") == "success" + + @pytest.mark.asyncio + async def test_try_with_throw_exception(self): + """Test catching a thrown exception.""" + ctx = create_action_context({ + "kind": "TryCatch", + "try": [ + {"kind": "ThrowException", "message": "Test error", "code": "ERR001"}, + ], + "catch": [ + {"kind": "SetValue", "path": "turn.result", "value": "caught"}, + ], + }) + + handler = get_action_handler("TryCatch") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.result") == "caught" + assert ctx.state.get("turn.error.message") == "Test error" + assert ctx.state.get("turn.error.code") == "ERR001" + + @pytest.mark.asyncio + async def test_finally_always_executes(self): + """Test that finally block always executes.""" + ctx = create_action_context({ + "kind": "TryCatch", + "try": [ + {"kind": "SetValue", "path": "turn.try", "value": "ran"}, + ], + "finally": [ + {"kind": "SetValue", "path": "turn.finally", "value": "ran"}, + ], + }) + + handler = get_action_handler("TryCatch") + _events = [e async for e in handler(ctx)] # noqa: F841 + + assert ctx.state.get("turn.try") == "ran" + assert ctx.state.get("turn.finally") == "ran" diff --git a/python/packages/declarative/tests/test_workflow_samples_integration.py b/python/packages/declarative/tests/test_workflow_samples_integration.py new file mode 100644 index 0000000000..fc0ece9ac5 --- /dev/null +++ b/python/packages/declarative/tests/test_workflow_samples_integration.py @@ -0,0 +1,268 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Integration tests for workflow samples. + +These tests verify that the workflow samples from workflow-samples/ directory +can be parsed and validated by the WorkflowFactory. +""" + +from pathlib import Path + +import pytest +import yaml + +# Path to workflow samples - navigate from tests dir up to repo root +# tests/test_*.py -> packages/declarative/tests/ -> packages/declarative/ -> packages/ -> python/ -> repo root +WORKFLOW_SAMPLES_DIR = Path(__file__).parent.parent.parent.parent.parent / "workflow-samples" + + +def get_workflow_sample_files(): + """Get all .yaml files from the workflow-samples directory.""" + if not WORKFLOW_SAMPLES_DIR.exists(): + return [] + return list(WORKFLOW_SAMPLES_DIR.glob("*.yaml")) + + +class TestWorkflowSampleParsing: + """Tests that verify workflow samples can be parsed correctly.""" + + @pytest.fixture + def sample_files(self): + """Get list of sample files.""" + return get_workflow_sample_files() + + def test_samples_directory_exists(self): + """Verify the workflow-samples directory exists.""" + assert WORKFLOW_SAMPLES_DIR.exists(), f"Workflow samples directory not found at {WORKFLOW_SAMPLES_DIR}" + + def test_samples_exist(self, sample_files): + """Verify there are workflow sample files.""" + assert len(sample_files) > 0, "No workflow sample files found" + + @pytest.mark.parametrize("yaml_file", get_workflow_sample_files(), ids=lambda f: f.name) + def test_sample_yaml_is_valid(self, yaml_file): + """Test that each sample YAML file can be parsed.""" + with open(yaml_file) as f: + data = yaml.safe_load(f) + + assert data is not None, f"Failed to parse {yaml_file.name}" + assert "kind" in data, f"Missing 'kind' field in {yaml_file.name}" + assert data["kind"] == "Workflow", f"Expected kind: Workflow in {yaml_file.name}" + + @pytest.mark.parametrize("yaml_file", get_workflow_sample_files(), ids=lambda f: f.name) + def test_sample_has_trigger(self, yaml_file): + """Test that each sample has a trigger defined.""" + with open(yaml_file) as f: + data = yaml.safe_load(f) + + assert "trigger" in data, f"Missing 'trigger' field in {yaml_file.name}" + trigger = data["trigger"] + assert trigger is not None, f"Trigger is empty in {yaml_file.name}" + + @pytest.mark.parametrize("yaml_file", get_workflow_sample_files(), ids=lambda f: f.name) + def test_sample_has_actions(self, yaml_file): + """Test that each sample has actions defined.""" + with open(yaml_file) as f: + data = yaml.safe_load(f) + + trigger = data.get("trigger", {}) + actions = trigger.get("actions", []) + assert len(actions) > 0, f"No actions defined in {yaml_file.name}" + + @pytest.mark.parametrize("yaml_file", get_workflow_sample_files(), ids=lambda f: f.name) + def test_sample_actions_have_kind(self, yaml_file): + """Test that each action has a 'kind' field.""" + with open(yaml_file) as f: + data = yaml.safe_load(f) + + def check_actions(actions, path=""): + for i, action in enumerate(actions): + action_path = f"{path}[{i}]" + assert "kind" in action, f"Action missing 'kind' at {action_path} in {yaml_file.name}" + + # Check nested actions + for nested_key in ["actions", "elseActions", "thenActions"]: + if nested_key in action: + check_actions(action[nested_key], f"{action_path}.{nested_key}") + + # Check conditions + if "conditions" in action: + for j, cond in enumerate(action["conditions"]): + if "actions" in cond: + check_actions(cond["actions"], f"{action_path}.conditions[{j}].actions") + + # Check cases + if "cases" in action: + for j, case in enumerate(action["cases"]): + if "actions" in case: + check_actions(case["actions"], f"{action_path}.cases[{j}].actions") + + trigger = data.get("trigger", {}) + actions = trigger.get("actions", []) + check_actions(actions, "trigger.actions") + + +class TestWorkflowDefinitionParsing: + """Tests for parsing workflow definitions into structured objects.""" + + @pytest.mark.parametrize("yaml_file", get_workflow_sample_files(), ids=lambda f: f.name) + def test_extract_actions_from_sample(self, yaml_file): + """Test extracting all actions from a workflow sample.""" + with open(yaml_file) as f: + data = yaml.safe_load(f) + + # Collect all action kinds used + action_kinds: set[str] = set() + + def collect_actions(actions): + for action in actions: + action_kinds.add(action.get("kind", "Unknown")) + + # Collect from nested actions + for nested_key in ["actions", "elseActions", "thenActions"]: + if nested_key in action: + collect_actions(action[nested_key]) + + if "conditions" in action: + for cond in action["conditions"]: + if "actions" in cond: + collect_actions(cond["actions"]) + + if "cases" in action: + for case in action["cases"]: + if "actions" in case: + collect_actions(case["actions"]) + + trigger = data.get("trigger", {}) + actions = trigger.get("actions", []) + collect_actions(actions) + + # Verify we found some actions + assert len(action_kinds) > 0, f"No action kinds found in {yaml_file.name}" + + @pytest.mark.parametrize("yaml_file", get_workflow_sample_files(), ids=lambda f: f.name) + def test_extract_agent_names_from_sample(self, yaml_file): + """Test extracting agent names referenced in a workflow sample.""" + with open(yaml_file) as f: + data = yaml.safe_load(f) + + agent_names: set[str] = set() + + def collect_agents(actions): + for action in actions: + kind = action.get("kind", "") + + if kind in ("InvokeAzureAgent", "InvokePromptAgent"): + agent_config = action.get("agent", {}) + name = agent_config.get("name") if isinstance(agent_config, dict) else agent_config + if name and not str(name).startswith("="): + agent_names.add(name) + + # Collect from nested actions + for nested_key in ["actions", "elseActions", "thenActions"]: + if nested_key in action: + collect_agents(action[nested_key]) + + if "conditions" in action: + for cond in action["conditions"]: + if "actions" in cond: + collect_agents(cond["actions"]) + + if "cases" in action: + for case in action["cases"]: + if "actions" in case: + collect_agents(case["actions"]) + + trigger = data.get("trigger", {}) + actions = trigger.get("actions", []) + collect_agents(actions) + + # Log the agents found (some workflows may not use agents) + # Agent names: {agent_names} + + +class TestHandlerCoverage: + """Tests to verify handler coverage for workflow actions.""" + + @pytest.fixture + def all_action_kinds(self): + """Collect all action kinds used across all samples.""" + action_kinds: set[str] = set() + + def collect_actions(actions): + for action in actions: + action_kinds.add(action.get("kind", "Unknown")) + + for nested_key in ["actions", "elseActions", "thenActions"]: + if nested_key in action: + collect_actions(action[nested_key]) + + if "conditions" in action: + for cond in action["conditions"]: + if "actions" in cond: + collect_actions(cond["actions"]) + + if "cases" in action: + for case in action["cases"]: + if "actions" in case: + collect_actions(case["actions"]) + + for yaml_file in get_workflow_sample_files(): + with open(yaml_file) as f: + data = yaml.safe_load(f) + trigger = data.get("trigger", {}) + actions = trigger.get("actions", []) + collect_actions(actions) + + return action_kinds + + def test_handlers_exist_for_sample_actions(self, all_action_kinds): + """Test that handlers exist for all action kinds in samples.""" + from agent_framework_declarative._workflows._handlers import list_action_handlers + + registered_handlers = set(list_action_handlers()) + + # Handlers we expect but may not be in samples + expected_handlers = { + "SetValue", + "SetVariable", + "SetTextVariable", + "SetMultipleVariables", + "ResetVariable", + "ClearAllVariables", + "AppendValue", + "SendActivity", + "EmitEvent", + "Foreach", + "If", + "Switch", + "ConditionGroup", + "GotoAction", + "BreakLoop", + "ContinueLoop", + "RepeatUntil", + "TryCatch", + "ThrowException", + "EndWorkflow", + "EndConversation", + "InvokeAzureAgent", + "InvokePromptAgent", + "CreateConversation", + "AddConversationMessage", + "CopyConversationMessages", + "RetrieveConversationMessages", + "Question", + "RequestExternalInput", + "WaitForInput", + } + + # Check that sample action kinds have handlers + missing_handlers = all_action_kinds - registered_handlers - {"OnConversationStart"} # Trigger kind, not action + + if missing_handlers: + # Informational, not a failure, as some actions may be future work + pass + + # Check that we have handlers for the expected core set + core_handlers = registered_handlers & expected_handlers + assert len(core_handlers) > 10, "Expected more core handlers to be registered" diff --git a/python/packages/declarative/tests/test_workflow_state.py b/python/packages/declarative/tests/test_workflow_state.py new file mode 100644 index 0000000000..caf75f925d --- /dev/null +++ b/python/packages/declarative/tests/test_workflow_state.py @@ -0,0 +1,225 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Unit tests for WorkflowState class.""" + +import pytest + +from agent_framework_declarative._workflows._state import WorkflowState + + +class TestWorkflowStateInitialization: + """Tests for WorkflowState initialization.""" + + def test_empty_initialization(self): + """Test creating a WorkflowState with no inputs.""" + state = WorkflowState() + assert state.inputs == {} + assert state.outputs == {} + assert state.turn == {} + assert state.agent == {} + + def test_initialization_with_inputs(self): + """Test creating a WorkflowState with inputs.""" + state = WorkflowState(inputs={"query": "Hello", "count": 5}) + assert state.inputs == {"query": "Hello", "count": 5} + assert state.outputs == {} + + def test_inputs_are_immutable(self): + """Test that inputs cannot be modified through set().""" + state = WorkflowState(inputs={"query": "Hello"}) + with pytest.raises(ValueError, match="Cannot modify workflow.inputs"): + state.set("workflow.inputs.query", "Modified") + + +class TestWorkflowStateGetSet: + """Tests for get and set operations.""" + + def test_set_and_get_turn_variable(self): + """Test setting and getting a turn variable.""" + state = WorkflowState() + state.set("turn.counter", 10) + assert state.get("turn.counter") == 10 + + def test_set_and_get_nested_turn_variable(self): + """Test setting and getting a nested turn variable.""" + state = WorkflowState() + state.set("turn.data.nested.value", "test") + assert state.get("turn.data.nested.value") == "test" + + def test_set_and_get_workflow_output(self): + """Test setting and getting workflow output.""" + state = WorkflowState() + state.set("workflow.outputs.result", "success") + assert state.get("workflow.outputs.result") == "success" + assert state.outputs["result"] == "success" + + def test_get_with_default(self): + """Test get with default value.""" + state = WorkflowState() + assert state.get("turn.nonexistent") is None + assert state.get("turn.nonexistent", "default") == "default" + + def test_get_workflow_inputs(self): + """Test getting workflow inputs.""" + state = WorkflowState(inputs={"query": "test"}) + assert state.get("workflow.inputs.query") == "test" + + def test_set_custom_namespace(self): + """Test setting a custom namespace variable.""" + state = WorkflowState() + state.set("custom.myvar", "value") + assert state.get("custom.myvar") == "value" + + +class TestWorkflowStateAppend: + """Tests for append operation.""" + + def test_append_to_nonexistent_list(self): + """Test appending to a path that doesn't exist yet.""" + state = WorkflowState() + state.append("turn.results", "item1") + assert state.get("turn.results") == ["item1"] + + def test_append_to_existing_list(self): + """Test appending to an existing list.""" + state = WorkflowState() + state.set("turn.results", ["item1"]) + state.append("turn.results", "item2") + assert state.get("turn.results") == ["item1", "item2"] + + def test_append_to_non_list_raises(self): + """Test that appending to a non-list raises ValueError.""" + state = WorkflowState() + state.set("turn.value", "not a list") + with pytest.raises(ValueError, match="Cannot append to non-list"): + state.append("turn.value", "item") + + +class TestWorkflowStateAgentResult: + """Tests for agent result management.""" + + def test_set_agent_result(self): + """Test setting agent result.""" + state = WorkflowState() + state.set_agent_result( + text="Agent response", + messages=[{"role": "assistant", "content": "Hello"}], + tool_calls=[{"name": "tool1"}], + ) + assert state.agent["text"] == "Agent response" + assert len(state.agent["messages"]) == 1 + assert len(state.agent["toolCalls"]) == 1 + + def test_get_agent_result_via_path(self): + """Test getting agent result via path.""" + state = WorkflowState() + state.set_agent_result(text="Response") + assert state.get("agent.text") == "Response" + + def test_reset_agent(self): + """Test resetting agent result.""" + state = WorkflowState() + state.set_agent_result(text="Response") + state.reset_agent() + assert state.agent == {} + + +class TestWorkflowStateConversation: + """Tests for conversation management.""" + + def test_add_conversation_message(self): + """Test adding a conversation message.""" + state = WorkflowState() + message = {"role": "user", "content": "Hello"} + state.add_conversation_message(message) + assert len(state.conversation["messages"]) == 1 + assert state.conversation["messages"][0] == message + + def test_get_conversation_history(self): + """Test getting conversation history.""" + state = WorkflowState() + state.add_conversation_message({"role": "user", "content": "Hi"}) + state.add_conversation_message({"role": "assistant", "content": "Hello"}) + assert len(state.get("conversation.history")) == 2 + + +class TestWorkflowStatePowerFx: + """Tests for PowerFx expression evaluation.""" + + def test_eval_non_expression(self): + """Test that non-expressions are returned as-is.""" + state = WorkflowState() + assert state.eval("plain text") == "plain text" + + def test_eval_if_expression_with_literal(self): + """Test eval_if_expression with a literal value.""" + state = WorkflowState() + assert state.eval_if_expression(42) == 42 + assert state.eval_if_expression(["a", "b"]) == ["a", "b"] + + def test_eval_if_expression_with_non_expression_string(self): + """Test eval_if_expression with a non-expression string.""" + state = WorkflowState() + assert state.eval_if_expression("plain text") == "plain text" + + def test_to_powerfx_symbols(self): + """Test converting state to PowerFx symbols.""" + state = WorkflowState(inputs={"query": "test"}) + state.set("turn.counter", 5) + state.set("workflow.outputs.result", "done") + + symbols = state.to_powerfx_symbols() + assert symbols["workflow"]["inputs"]["query"] == "test" + assert symbols["workflow"]["outputs"]["result"] == "done" + assert symbols["turn"]["counter"] == 5 + + +class TestWorkflowStateClone: + """Tests for state cloning.""" + + def test_clone_creates_copy(self): + """Test that clone creates a copy of the state.""" + state = WorkflowState(inputs={"query": "test"}) + state.set("turn.counter", 5) + + cloned = state.clone() + assert cloned.get("workflow.inputs.query") == "test" + assert cloned.get("turn.counter") == 5 + + def test_clone_is_independent(self): + """Test that modifications to clone don't affect original.""" + state = WorkflowState() + state.set("turn.value", "original") + + cloned = state.clone() + cloned.set("turn.value", "modified") + + assert state.get("turn.value") == "original" + assert cloned.get("turn.value") == "modified" + + +class TestWorkflowStateResetTurn: + """Tests for turn reset.""" + + def test_reset_turn_clears_turn_variables(self): + """Test that reset_turn clears turn variables.""" + state = WorkflowState() + state.set("turn.var1", "value1") + state.set("turn.var2", "value2") + + state.reset_turn() + + assert state.get("turn.var1") is None + assert state.get("turn.var2") is None + assert state.turn == {} + + def test_reset_turn_preserves_other_state(self): + """Test that reset_turn preserves other state.""" + state = WorkflowState(inputs={"query": "test"}) + state.set("workflow.outputs.result", "done") + state.set("turn.temp", "will be cleared") + + state.reset_turn() + + assert state.get("workflow.inputs.query") == "test" + assert state.get("workflow.outputs.result") == "done" diff --git a/python/packages/devui/agent_framework_devui/_executor.py b/python/packages/devui/agent_framework_devui/_executor.py index 99379f6bf9..e823e99c25 100644 --- a/python/packages/devui/agent_framework_devui/_executor.py +++ b/python/packages/devui/agent_framework_devui/_executor.py @@ -507,7 +507,9 @@ async def _execute_workflow( # First run - pass DevUI's checkpoint storage to enable checkpointing logger.info(f"Starting fresh workflow in session {conversation_id}") + logger.info(f"Raw request.input: {request.input!r} (type: {type(request.input).__name__})") parsed_input = await self._parse_workflow_input(workflow, request.input) + logger.info(f"Parsed workflow input: {parsed_input!r} (type: {type(parsed_input).__name__})") async for event in workflow.run_stream(parsed_input, checkpoint_storage=checkpoint_storage): if isinstance(event, RequestInfoEvent): diff --git a/python/packages/devui/agent_framework_devui/_mapper.py b/python/packages/devui/agent_framework_devui/_mapper.py index 9d9e0e5ccd..d18d06542e 100644 --- a/python/packages/devui/agent_framework_devui/_mapper.py +++ b/python/packages/devui/agent_framework_devui/_mapper.py @@ -916,6 +916,11 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> if context.get("current_executor_id") == executor_id: context.pop("current_executor_id", None) + # Serialize the result data to ensure JSON compatibility + # This handles declarative workflow types like ActionComplete + raw_result = getattr(event, "data", None) + serialized_result = self._serialize_value(raw_result) if raw_result is not None else None + # Create ExecutorActionItem with completed status # ExecutorCompletedEvent uses 'data' field, not 'result' executor_item = ExecutorActionItem( @@ -923,7 +928,7 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) -> id=item_id, executor_id=executor_id, status="completed", - result=getattr(event, "data", None), + result=serialized_result, ) # Use our custom event type diff --git a/python/packages/devui/agent_framework_devui/_server.py b/python/packages/devui/agent_framework_devui/_server.py index 284164cefb..6b86a3e780 100644 --- a/python/packages/devui/agent_framework_devui/_server.py +++ b/python/packages/devui/agent_framework_devui/_server.py @@ -480,34 +480,53 @@ async def get_entity_info(entity_id: str) -> EntityInfo: workflow_dump = {k: v for k, v in entity_obj.__dict__.items() if not k.startswith("_")} # Get input schema information - input_schema = {} + # First, check if the workflow has an explicit input_schema (e.g., declarative workflows) + input_schema: dict[str, Any] = {} input_type_name = "Unknown" start_executor_id = "" - try: - from ._utils import ( - extract_executor_message_types, - generate_input_schema, - select_primary_input_type, - ) + # Check for explicit input_schema on workflow (declarative workflows set this) + if hasattr(entity_obj, "input_schema") and entity_obj.input_schema: + input_schema = entity_obj.input_schema + input_type_name = "Inputs" - start_executor = entity_obj.get_start_executor() - except Exception as e: - logger.debug(f"Could not extract input info for workflow {entity_id}: {e}") - else: - if start_executor: - start_executor_id = getattr(start_executor, "executor_id", "") or getattr( - start_executor, "id", "" + # Fall back to introspecting the start executor's message types + if not input_schema: + try: + from ._utils import ( + extract_executor_message_types, + generate_input_schema, + select_primary_input_type, ) - message_types = extract_executor_message_types(start_executor) - input_type = select_primary_input_type(message_types) + start_executor = entity_obj.get_start_executor() + except Exception as e: + logger.debug(f"Could not extract input info for workflow {entity_id}: {e}") + else: + if start_executor: + start_executor_id = getattr(start_executor, "executor_id", "") or getattr( + start_executor, "id", "" + ) + + message_types = extract_executor_message_types(start_executor) + input_type = select_primary_input_type(message_types) + + if input_type: + input_type_name = getattr(input_type, "__name__", str(input_type)) - if input_type: - input_type_name = getattr(input_type, "__name__", str(input_type)) + # Generate schema using comprehensive schema generation + input_schema = generate_input_schema(input_type) - # Generate schema using comprehensive schema generation - input_schema = generate_input_schema(input_type) + # Get start executor ID if not already set + if not start_executor_id: + try: + start_executor = entity_obj.get_start_executor() + if start_executor: + start_executor_id = getattr(start_executor, "executor_id", "") or getattr( + start_executor, "id", "" + ) + except Exception: + logger.debug(f"Could not get start executor for workflow {entity_id}") if not input_schema: input_schema = {"type": "string"} diff --git a/python/packages/devui/agent_framework_devui/_utils.py b/python/packages/devui/agent_framework_devui/_utils.py index 3c17c072f7..fdf39dc0a6 100644 --- a/python/packages/devui/agent_framework_devui/_utils.py +++ b/python/packages/devui/agent_framework_devui/_utils.py @@ -439,6 +439,34 @@ def generate_input_schema(input_type: type) -> dict[str, Any]: # ============================================================================ +def _safe_isinstance(obj: Any, target_type: type) -> bool: + """Safely check isinstance, handling parameterized generics. + + In Python 3.9+, isinstance() cannot be called with parameterized generics + like list[str] or Mapping[str, Any]. This helper extracts the origin type + for such cases. + + Args: + obj: Object to check + target_type: Type to check against (may be parameterized generic) + + Returns: + True if obj is an instance of target_type (or its origin for generics) + """ + # Get the origin type for parameterized generics (e.g., list for list[str]) + origin = get_origin(target_type) + if origin is not None: + # Use the origin type for isinstance check + return isinstance(obj, origin) + + # For regular types, use isinstance directly + try: + return isinstance(obj, target_type) + except TypeError: + # Fallback: if isinstance fails, return False + return False + + def parse_input_for_type(input_data: Any, target_type: type) -> Any: """Parse input data to match the target type. @@ -455,8 +483,8 @@ def parse_input_for_type(input_data: Any, target_type: type) -> Any: Returns: Parsed input matching target_type, or original input if parsing fails """ - # If already correct type, return as-is - if isinstance(input_data, target_type): + # If already correct type, return as-is (use safe isinstance for generics) + if _safe_isinstance(input_data, target_type): return input_data # Handle string input 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 203ffd294e..505ba27ccf 100644 --- a/python/packages/devui/agent_framework_devui/ui/assets/index.js +++ b/python/packages/devui/agent_framework_devui/ui/assets/index.js @@ -436,7 +436,7 @@ Error generating stack: `+i.message+` * * This source code is licensed under the ISC license. * See the LICENSE file in the root directory of this source tree. - */const lT=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],Xp=je("zap",lT);function ud({...e}){return o.jsx(hA,{"data-slot":"dropdown-menu",...e})}function dd({...e}){return o.jsx(pA,{"data-slot":"dropdown-menu-trigger",...e})}function fd({className:e,sideOffset:n=4,...r}){return o.jsx(gA,{children:o.jsx(xA,{"data-slot":"dropdown-menu-content",sideOffset:n,className:Ze("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",e),...r})})}function Lt({className:e,inset:n,variant:r="default",...a}){return o.jsx(vA,{"data-slot":"dropdown-menu-item","data-inset":n,"data-variant":r,className:Ze("focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",e),...a})}function dh({className:e,inset:n,...r}){return o.jsx(yA,{"data-slot":"dropdown-menu-label","data-inset":n,className:Ze("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",e),...r})}function ma({className:e,...n}){return o.jsx(bA,{"data-slot":"dropdown-menu-separator",className:Ze("bg-border -mx-1 my-1 h-px",e),...n})}function Ou({size:e="md",className:n}){return o.jsx(Cr,{className:Ze("animate-spin",{"h-4 w-4":e==="sm","h-6 w-6":e==="md","h-8 w-8":e==="lg"},n)})}const cT=e=>e==="workflow"?Is:Hs;function uT({agents:e,workflows:n,entities:r,selectedItem:a,onSelect:l,onBrowseGallery:c,isLoading:d=!1}){const[f,m]=w.useState(!1),h=r||[...e,...n],g=S=>{l(S),m(!1)},y=a?cT(a.type):Hs,x=a?.name||a?.id||"Select Agent or Workflow",b=a?.metadata?.lazy_loaded!==!1;return o.jsxs(ud,{open:f,onOpenChange:m,children:[o.jsx(dd,{asChild:!0,children:o.jsx(Te,{variant:"outline",className:"w-64 justify-between font-mono text-sm",disabled:d,children:d?o.jsxs("div",{className:"flex items-center gap-2",children:[o.jsx(Ou,{size:"sm"}),o.jsx("span",{className:"text-muted-foreground",children:"Loading..."})]}):o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[o.jsx(y,{className:"h-4 w-4 flex-shrink-0"}),o.jsx("span",{className:"truncate",children:x}),a&&!b&&o.jsx(Cr,{className:"h-3 w-3 text-muted-foreground animate-spin ml-auto flex-shrink-0"})]}),o.jsx(Gt,{className:"h-4 w-4 opacity-50"})]})})}),o.jsxs(fd,{className:"w-80 font-mono",children:[(()=>{const S=h.filter(_=>_.type==="workflow"),N=h.filter(_=>_.type==="agent"),j=h[0]?.type;return o.jsxs(o.Fragment,{children:[j==="workflow"&&S.length>0&&o.jsxs(o.Fragment,{children:[o.jsxs(dh,{className:"flex items-center gap-2",children:[o.jsx(Is,{className:"h-4 w-4"}),"Workflows (",S.length,")"]}),S.map(_=>{const M=_.metadata?.lazy_loaded!==!1;return o.jsx(Lt,{className:"cursor-pointer group",onClick:()=>g(_),children:o.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[o.jsx(Is,{className:"h-4 w-4 flex-shrink-0"}),o.jsxs("div",{className:"min-w-0 flex-1",children:[o.jsx("span",{className:"truncate font-medium block",children:_.name||_.id}),M&&_.description&&o.jsx("div",{className:"text-xs text-muted-foreground line-clamp-2",children:_.description})]})]})},_.id)})]}),S.length>0&&N.length>0&&o.jsx(ma,{}),N.length>0&&o.jsxs(o.Fragment,{children:[o.jsxs(dh,{className:"flex items-center gap-2",children:[o.jsx(Hs,{className:"h-4 w-4"}),"Agents (",N.length,")"]}),N.map(_=>{const M=_.metadata?.lazy_loaded!==!1;return o.jsx(Lt,{className:"cursor-pointer group",onClick:()=>g(_),children:o.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[o.jsx(Hs,{className:"h-4 w-4 flex-shrink-0"}),o.jsxs("div",{className:"min-w-0 flex-1",children:[o.jsx("span",{className:"truncate font-medium block",children:_.name||_.id}),M&&_.description&&o.jsx("div",{className:"text-xs text-muted-foreground line-clamp-2",children:_.description})]})]})},_.id)})]}),j==="agent"&&S.length>0&&o.jsxs(o.Fragment,{children:[N.length>0&&o.jsx(ma,{}),o.jsxs(dh,{className:"flex items-center gap-2",children:[o.jsx(Is,{className:"h-4 w-4"}),"Workflows (",S.length,")"]}),S.map(_=>{const M=_.metadata?.lazy_loaded!==!1;return o.jsx(Lt,{className:"cursor-pointer group",onClick:()=>g(_),children:o.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[o.jsx(Is,{className:"h-4 w-4 flex-shrink-0"}),o.jsxs("div",{className:"min-w-0 flex-1",children:[o.jsx("span",{className:"truncate font-medium block",children:_.name||_.id}),M&&_.description&&o.jsx("div",{className:"text-xs text-muted-foreground line-clamp-2",children:_.description})]})]})},_.id)})]})]})})(),h.length===0&&o.jsx(Lt,{disabled:!0,children:o.jsx("div",{className:"text-center text-muted-foreground py-2",children:d?"Loading agents and workflows...":"No agents or workflows found"})}),o.jsx(ma,{}),o.jsxs(Lt,{className:"cursor-pointer text-primary",onClick:()=>{c?.(),m(!1)},children:[o.jsx(qp,{className:"h-4 w-4 mr-2"}),"Browse Gallery"]})]})]})}var dT=(e,n,r,a,l,c,d,f)=>{let m=document.documentElement,h=["light","dark"];function g(b){(Array.isArray(e)?e:[e]).forEach(S=>{let N=S==="class",j=N&&c?l.map(_=>c[_]||_):l;N?(m.classList.remove(...j),m.classList.add(c&&c[b]?c[b]:b)):m.setAttribute(S,b)}),y(b)}function y(b){f&&h.includes(b)&&(m.style.colorScheme=b)}function x(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}if(a)g(a);else try{let b=localStorage.getItem(n)||r,S=d&&b==="system"?x():b;g(S)}catch{}},$v=["light","dark"],G1="(prefers-color-scheme: dark)",fT=typeof window>"u",Zp=w.createContext(void 0),mT={setTheme:e=>{},themes:[]},hT=()=>{var e;return(e=w.useContext(Zp))!=null?e:mT},pT=e=>w.useContext(Zp)?w.createElement(w.Fragment,null,e.children):w.createElement(xT,{...e}),gT=["light","dark"],xT=({forcedTheme:e,disableTransitionOnChange:n=!1,enableSystem:r=!0,enableColorScheme:a=!0,storageKey:l="theme",themes:c=gT,defaultTheme:d=r?"system":"light",attribute:f="data-theme",value:m,children:h,nonce:g,scriptProps:y})=>{let[x,b]=w.useState(()=>vT(l,d)),[S,N]=w.useState(()=>x==="system"?fh():x),j=m?Object.values(m):c,_=w.useCallback(R=>{let D=R;if(!D)return;R==="system"&&r&&(D=fh());let O=m?m[D]:D,P=n?bT(g):null,q=document.documentElement,Q=ee=>{ee==="class"?(q.classList.remove(...j),O&&q.classList.add(O)):ee.startsWith("data-")&&(O?q.setAttribute(ee,O):q.removeAttribute(ee))};if(Array.isArray(f)?f.forEach(Q):Q(f),a){let ee=$v.includes(d)?d:null,G=$v.includes(D)?D:ee;q.style.colorScheme=G}P?.()},[g]),M=w.useCallback(R=>{let D=typeof R=="function"?R(x):R;b(D);try{localStorage.setItem(l,D)}catch{}},[x]),E=w.useCallback(R=>{let D=fh(R);N(D),x==="system"&&r&&!e&&_("system")},[x,e]);w.useEffect(()=>{let R=window.matchMedia(G1);return R.addListener(E),E(R),()=>R.removeListener(E)},[E]),w.useEffect(()=>{let R=D=>{D.key===l&&(D.newValue?b(D.newValue):M(d))};return window.addEventListener("storage",R),()=>window.removeEventListener("storage",R)},[M]),w.useEffect(()=>{_(e??x)},[e,x]);let T=w.useMemo(()=>({theme:x,setTheme:M,forcedTheme:e,resolvedTheme:x==="system"?S:x,themes:r?[...c,"system"]:c,systemTheme:r?S:void 0}),[x,M,e,S,r,c]);return w.createElement(Zp.Provider,{value:T},w.createElement(yT,{forcedTheme:e,storageKey:l,attribute:f,enableSystem:r,enableColorScheme:a,defaultTheme:d,value:m,themes:c,nonce:g,scriptProps:y}),h)},yT=w.memo(({forcedTheme:e,storageKey:n,attribute:r,enableSystem:a,enableColorScheme:l,defaultTheme:c,value:d,themes:f,nonce:m,scriptProps:h})=>{let g=JSON.stringify([r,n,c,e,f,d,a,l]).slice(1,-1);return w.createElement("script",{...h,suppressHydrationWarning:!0,nonce:typeof window>"u"?m:"",dangerouslySetInnerHTML:{__html:`(${dT.toString()})(${g})`}})}),vT=(e,n)=>{if(fT)return;let r;try{r=localStorage.getItem(e)||void 0}catch{}return r||n},bT=e=>{let n=document.createElement("style");return e&&n.setAttribute("nonce",e),n.appendChild(document.createTextNode("*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}")),document.head.appendChild(n),()=>{window.getComputedStyle(document.body),setTimeout(()=>{document.head.removeChild(n)},1)}},fh=e=>(e||(e=window.matchMedia(G1)),e.matches?"dark":"light");function wT(){const{setTheme:e}=hT();return o.jsxs(ud,{children:[o.jsx(dd,{asChild:!0,children:o.jsxs(Te,{variant:"ghost",size:"sm",children:[o.jsx(QM,{className:"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"}),o.jsx(AM,{className:"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"}),o.jsx("span",{className:"sr-only",children:"Toggle theme"})]})}),o.jsxs(fd,{align:"end",children:[o.jsx(Lt,{onClick:()=>e("light"),children:"Light"}),o.jsx(Lt,{onClick:()=>e("dark"),children:"Dark"}),o.jsx(Lt,{onClick:()=>e("system"),children:"System"})]})]})}const Pv=e=>{let n;const r=new Set,a=(h,g)=>{const y=typeof h=="function"?h(n):h;if(!Object.is(y,n)){const x=n;n=g??(typeof y!="object"||y===null)?y:Object.assign({},n,y),r.forEach(b=>b(n,x))}},l=()=>n,f={setState:a,getState:l,getInitialState:()=>m,subscribe:h=>(r.add(h),()=>r.delete(h))},m=n=e(a,l,f);return f},NT=(e=>e?Pv(e):Pv),ST=e=>e;function jT(e,n=ST){const r=xn.useSyncExternalStore(e.subscribe,xn.useCallback(()=>n(e.getState()),[e,n]),xn.useCallback(()=>n(e.getInitialState()),[e,n]));return xn.useDebugValue(r),r}const _T=e=>{const n=NT(e),r=a=>jT(n,a);return Object.assign(r,n),r},ET=(e=>_T),Bv={BASE_URL:"./",DEV:!1,MODE:"production",PROD:!0,SSR:!1,VITE_API_BASE_URL:""},rl=new Map,Jc=e=>{const n=rl.get(e);return n?Object.fromEntries(Object.entries(n.stores).map(([r,a])=>[r,a.getState()])):{}},CT=(e,n,r)=>{if(e===void 0)return{type:"untracked",connection:n.connect(r)};const a=rl.get(r.name);if(a)return{type:"tracked",store:e,...a};const l={connection:n.connect(r),stores:{}};return rl.set(r.name,l),{type:"tracked",store:e,...l}},kT=(e,n)=>{if(n===void 0)return;const r=rl.get(e);r&&(delete r.stores[n],Object.keys(r.stores).length===0&&rl.delete(e))},AT=e=>{var n,r;if(!e)return;const a=e.split(` + */const lT=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],Xp=je("zap",lT);function ud({...e}){return o.jsx(hA,{"data-slot":"dropdown-menu",...e})}function dd({...e}){return o.jsx(pA,{"data-slot":"dropdown-menu-trigger",...e})}function fd({className:e,sideOffset:n=4,...r}){return o.jsx(gA,{children:o.jsx(xA,{"data-slot":"dropdown-menu-content",sideOffset:n,className:Ze("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",e),...r})})}function Lt({className:e,inset:n,variant:r="default",...a}){return o.jsx(vA,{"data-slot":"dropdown-menu-item","data-inset":n,"data-variant":r,className:Ze("focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",e),...a})}function dh({className:e,inset:n,...r}){return o.jsx(yA,{"data-slot":"dropdown-menu-label","data-inset":n,className:Ze("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",e),...r})}function ma({className:e,...n}){return o.jsx(bA,{"data-slot":"dropdown-menu-separator",className:Ze("bg-border -mx-1 my-1 h-px",e),...n})}function Ou({size:e="md",className:n}){return o.jsx(Cr,{className:Ze("animate-spin",{"h-4 w-4":e==="sm","h-6 w-6":e==="md","h-8 w-8":e==="lg"},n)})}const cT=e=>e==="workflow"?Is:Hs;function uT({agents:e,workflows:n,entities:r,selectedItem:a,onSelect:l,onBrowseGallery:c,isLoading:d=!1}){const[f,m]=w.useState(!1),h=r||[...e,...n],g=S=>{l(S),m(!1)},y=a?cT(a.type):Hs,x=a?.name||a?.id||"Select Agent or Workflow",b=a?.metadata?.lazy_loaded!==!1;return o.jsxs(ud,{open:f,onOpenChange:m,children:[o.jsx(dd,{asChild:!0,children:o.jsx(Te,{variant:"outline",className:"w-64 justify-between font-mono text-sm",disabled:d,children:d?o.jsxs("div",{className:"flex items-center gap-2",children:[o.jsx(Ou,{size:"sm"}),o.jsx("span",{className:"text-muted-foreground",children:"Loading..."})]}):o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[o.jsx(y,{className:"h-4 w-4 flex-shrink-0"}),o.jsx("span",{className:"truncate",children:x}),a&&!b&&o.jsx(Cr,{className:"h-3 w-3 text-muted-foreground animate-spin ml-auto flex-shrink-0"})]}),o.jsx(Gt,{className:"h-4 w-4 opacity-50"})]})})}),o.jsxs(fd,{className:"w-80 font-mono",children:[(()=>{const S=h.filter(_=>_.type==="workflow"),N=h.filter(_=>_.type==="agent"),j=h[0]?.type;return o.jsxs(o.Fragment,{children:[j==="workflow"&&S.length>0&&o.jsxs(o.Fragment,{children:[o.jsxs(dh,{className:"flex items-center gap-2",children:[o.jsx(Is,{className:"h-4 w-4"}),"Workflows (",S.length,")"]}),S.map(_=>{const M=_.metadata?.lazy_loaded!==!1;return o.jsx(Lt,{className:"cursor-pointer group",onClick:()=>g(_),children:o.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[o.jsx(Is,{className:"h-4 w-4 flex-shrink-0"}),o.jsxs("div",{className:"min-w-0 flex-1",children:[o.jsx("span",{className:"truncate font-medium block",children:_.name||_.id}),M&&_.description&&o.jsx("div",{className:"text-xs text-muted-foreground line-clamp-2",children:_.description})]})]})},_.id)})]}),S.length>0&&N.length>0&&o.jsx(ma,{}),N.length>0&&o.jsxs(o.Fragment,{children:[o.jsxs(dh,{className:"flex items-center gap-2",children:[o.jsx(Hs,{className:"h-4 w-4"}),"Agents (",N.length,")"]}),N.map(_=>{const M=_.metadata?.lazy_loaded!==!1;return o.jsx(Lt,{className:"cursor-pointer group",onClick:()=>g(_),children:o.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[o.jsx(Hs,{className:"h-4 w-4 flex-shrink-0"}),o.jsxs("div",{className:"min-w-0 flex-1",children:[o.jsx("span",{className:"truncate font-medium block",children:_.name||_.id}),M&&_.description&&o.jsx("div",{className:"text-xs text-muted-foreground line-clamp-2",children:_.description})]})]})},_.id)})]}),j==="agent"&&S.length>0&&o.jsxs(o.Fragment,{children:[N.length>0&&o.jsx(ma,{}),o.jsxs(dh,{className:"flex items-center gap-2",children:[o.jsx(Is,{className:"h-4 w-4"}),"Workflows (",S.length,")"]}),S.map(_=>{const M=_.metadata?.lazy_loaded!==!1;return o.jsx(Lt,{className:"cursor-pointer group",onClick:()=>g(_),children:o.jsxs("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[o.jsx(Is,{className:"h-4 w-4 flex-shrink-0"}),o.jsxs("div",{className:"min-w-0 flex-1",children:[o.jsx("span",{className:"truncate font-medium block",children:_.name||_.id}),M&&_.description&&o.jsx("div",{className:"text-xs text-muted-foreground line-clamp-2",children:_.description})]})]})},_.id)})]})]})})(),h.length===0&&o.jsx(Lt,{disabled:!0,children:o.jsx("div",{className:"text-center text-muted-foreground py-2",children:d?"Loading agents and workflows...":"No agents or workflows found"})}),o.jsx(ma,{}),o.jsxs(Lt,{className:"cursor-pointer text-primary",onClick:()=>{c?.(),m(!1)},children:[o.jsx(qp,{className:"h-4 w-4 mr-2"}),"Browse Gallery"]})]})]})}var dT=(e,n,r,a,l,c,d,f)=>{let m=document.documentElement,h=["light","dark"];function g(b){(Array.isArray(e)?e:[e]).forEach(S=>{let N=S==="class",j=N&&c?l.map(_=>c[_]||_):l;N?(m.classList.remove(...j),m.classList.add(c&&c[b]?c[b]:b)):m.setAttribute(S,b)}),y(b)}function y(b){f&&h.includes(b)&&(m.style.colorScheme=b)}function x(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}if(a)g(a);else try{let b=localStorage.getItem(n)||r,S=d&&b==="system"?x():b;g(S)}catch{}},$v=["light","dark"],G1="(prefers-color-scheme: dark)",fT=typeof window>"u",Zp=w.createContext(void 0),mT={setTheme:e=>{},themes:[]},hT=()=>{var e;return(e=w.useContext(Zp))!=null?e:mT},pT=e=>w.useContext(Zp)?w.createElement(w.Fragment,null,e.children):w.createElement(xT,{...e}),gT=["light","dark"],xT=({forcedTheme:e,disableTransitionOnChange:n=!1,enableSystem:r=!0,enableColorScheme:a=!0,storageKey:l="theme",themes:c=gT,defaultTheme:d=r?"system":"light",attribute:f="data-theme",value:m,children:h,nonce:g,scriptProps:y})=>{let[x,b]=w.useState(()=>vT(l,d)),[S,N]=w.useState(()=>x==="system"?fh():x),j=m?Object.values(m):c,_=w.useCallback(R=>{let D=R;if(!D)return;R==="system"&&r&&(D=fh());let O=m?m[D]:D,P=n?bT(g):null,q=document.documentElement,Q=ee=>{ee==="class"?(q.classList.remove(...j),O&&q.classList.add(O)):ee.startsWith("data-")&&(O?q.setAttribute(ee,O):q.removeAttribute(ee))};if(Array.isArray(f)?f.forEach(Q):Q(f),a){let ee=$v.includes(d)?d:null,G=$v.includes(D)?D:ee;q.style.colorScheme=G}P?.()},[g]),M=w.useCallback(R=>{let D=typeof R=="function"?R(x):R;b(D);try{localStorage.setItem(l,D)}catch{}},[x]),E=w.useCallback(R=>{let D=fh(R);N(D),x==="system"&&r&&!e&&_("system")},[x,e]);w.useEffect(()=>{let R=window.matchMedia(G1);return R.addListener(E),E(R),()=>R.removeListener(E)},[E]),w.useEffect(()=>{let R=D=>{D.key===l&&(D.newValue?b(D.newValue):M(d))};return window.addEventListener("storage",R),()=>window.removeEventListener("storage",R)},[M]),w.useEffect(()=>{_(e??x)},[e,x]);let T=w.useMemo(()=>({theme:x,setTheme:M,forcedTheme:e,resolvedTheme:x==="system"?S:x,themes:r?[...c,"system"]:c,systemTheme:r?S:void 0}),[x,M,e,S,r,c]);return w.createElement(Zp.Provider,{value:T},w.createElement(yT,{forcedTheme:e,storageKey:l,attribute:f,enableSystem:r,enableColorScheme:a,defaultTheme:d,value:m,themes:c,nonce:g,scriptProps:y}),h)},yT=w.memo(({forcedTheme:e,storageKey:n,attribute:r,enableSystem:a,enableColorScheme:l,defaultTheme:c,value:d,themes:f,nonce:m,scriptProps:h})=>{let g=JSON.stringify([r,n,c,e,f,d,a,l]).slice(1,-1);return w.createElement("script",{...h,suppressHydrationWarning:!0,nonce:typeof window>"u"?m:"",dangerouslySetInnerHTML:{__html:`(${dT.toString()})(${g})`}})}),vT=(e,n)=>{if(fT)return;let r;try{r=localStorage.getItem(e)||void 0}catch{}return r||n},bT=e=>{let n=document.createElement("style");return e&&n.setAttribute("nonce",e),n.appendChild(document.createTextNode("*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}")),document.head.appendChild(n),()=>{window.getComputedStyle(document.body),setTimeout(()=>{document.head.removeChild(n)},1)}},fh=e=>(e||(e=window.matchMedia(G1)),e.matches?"dark":"light");function wT(){const{setTheme:e}=hT();return o.jsxs(ud,{children:[o.jsx(dd,{asChild:!0,children:o.jsxs(Te,{variant:"ghost",size:"sm",children:[o.jsx(QM,{className:"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"}),o.jsx(AM,{className:"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"}),o.jsx("span",{className:"sr-only",children:"Toggle theme"})]})}),o.jsxs(fd,{align:"end",children:[o.jsx(Lt,{onClick:()=>e("light"),children:"Light"}),o.jsx(Lt,{onClick:()=>e("dark"),children:"Dark"}),o.jsx(Lt,{onClick:()=>e("system"),children:"System"})]})]})}const Pv=e=>{let n;const r=new Set,a=(h,g)=>{const y=typeof h=="function"?h(n):h;if(!Object.is(y,n)){const x=n;n=g??(typeof y!="object"||y===null)?y:Object.assign({},n,y),r.forEach(b=>b(n,x))}},l=()=>n,f={setState:a,getState:l,getInitialState:()=>m,subscribe:h=>(r.add(h),()=>r.delete(h))},m=n=e(a,l,f);return f},NT=(e=>e?Pv(e):Pv),ST=e=>e;function jT(e,n=ST){const r=xn.useSyncExternalStore(e.subscribe,xn.useCallback(()=>n(e.getState()),[e,n]),xn.useCallback(()=>n(e.getInitialState()),[e,n]));return xn.useDebugValue(r),r}const _T=e=>{const n=NT(e),r=a=>jT(n,a);return Object.assign(r,n),r},ET=(e=>_T),Bv={BASE_URL:"./",DEV:!1,MODE:"production",PROD:!0,SSR:!1},rl=new Map,Jc=e=>{const n=rl.get(e);return n?Object.fromEntries(Object.entries(n.stores).map(([r,a])=>[r,a.getState()])):{}},CT=(e,n,r)=>{if(e===void 0)return{type:"untracked",connection:n.connect(r)};const a=rl.get(r.name);if(a)return{type:"tracked",store:e,...a};const l={connection:n.connect(r),stores:{}};return rl.set(r.name,l),{type:"tracked",store:e,...l}},kT=(e,n)=>{if(n===void 0)return;const r=rl.get(e);r&&(delete r.stores[n],Object.keys(r.stores).length===0&&rl.delete(e))},AT=e=>{var n,r;if(!e)return;const a=e.split(` `),l=a.findIndex(d=>d.includes("api.setState"));if(l<0)return;const c=((n=a[l+1])==null?void 0:n.trim())||"";return(r=/.+ (.+) .+/.exec(c))==null?void 0:r[1]},MT=(e,n={})=>(r,a,l)=>{const{enabled:c,anonymousActionType:d,store:f,...m}=n;let h;try{h=(c??(Bv?"production":void 0)!=="production")&&window.__REDUX_DEVTOOLS_EXTENSION__}catch{}if(!h)return e(r,a,l);const{connection:g,...y}=CT(f,h,m);let x=!0;l.setState=((N,j,_)=>{const M=r(N,j);if(!x)return M;const E=_===void 0?{type:d||AT(new Error().stack)||"anonymous"}:typeof _=="string"?{type:_}:_;return f===void 0?(g?.send(E,a()),M):(g?.send({...E,type:`${f}/${E.type}`},{...Jc(m.name),[f]:l.getState()}),M)}),l.devtools={cleanup:()=>{g&&typeof g.unsubscribe=="function"&&g.unsubscribe(),kT(m.name,f)}};const b=(...N)=>{const j=x;x=!1,r(...N),x=j},S=e(l.setState,a,l);if(y.type==="untracked"?g?.init(S):(y.stores[y.store]=l,g?.init(Object.fromEntries(Object.entries(y.stores).map(([N,j])=>[N,N===y.store?S:j.getState()])))),l.dispatchFromDevtools&&typeof l.dispatch=="function"){let N=!1;const j=l.dispatch;l.dispatch=(..._)=>{(Bv?"production":void 0)!=="production"&&_[0].type==="__setState"&&!N&&(console.warn('[zustand devtools middleware] "__setState" action type is reserved to set state from the devtools. Avoid using it.'),N=!0),j(..._)}}return g.subscribe(N=>{var j;switch(N.type){case"ACTION":if(typeof N.payload!="string"){console.error("[zustand devtools middleware] Unsupported action format");return}return mh(N.payload,_=>{if(_.type==="__setState"){if(f===void 0){b(_.state);return}Object.keys(_.state).length!==1&&console.error(` [zustand devtools middleware] Unsupported __setState action format. When using 'store' option in devtools(), the 'state' should have only one key, which is a value of 'store' that was passed in devtools(), @@ -534,7 +534,7 @@ asyncio.run(main())`})]})]}),o.jsxs("div",{className:"flex gap-2 pt-4 border-t", bg-background hover:bg-accent text-muted-foreground hover:text-foreground transition-all duration-200 ease-in-out - opacity-0 group-hover:opacity-100`,title:c?"Copied!":"Copy message",children:c?o.jsx(IA,{className:"h-3.5 w-3.5 text-green-600 dark:text-green-400"}):o.jsx(no,{className:"h-3.5 w-3.5"})})]}),o.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground font-mono",children:[o.jsx("span",{children:e.created_at?new Date(e.created_at*1e3).toLocaleTimeString():new Date().toLocaleTimeString()}),!x&&e.usage&&o.jsxs(o.Fragment,{children:[o.jsx("span",{children:"•"}),o.jsxs("span",{className:"flex items-center gap-1",children:[o.jsxs("span",{className:"text-blue-600 dark:text-blue-400",children:["↑",e.usage.input_tokens]}),o.jsxs("span",{className:"text-green-600 dark:text-green-400",children:["↓",e.usage.output_tokens]}),o.jsxs("span",{children:["(",e.usage.total_tokens," tokens)"]})]})]}),!x&&h&&n.length>0&&o.jsxs(o.Fragment,{children:[o.jsx("span",{children:"•"}),o.jsxs("button",{onClick:()=>m(!f),className:"flex items-center gap-1 hover:text-foreground transition-colors",title:`${n.length} tool call${n.length>1?"s":""} - click to ${f?"hide":"show"} details`,children:[o.jsx(ba,{className:"h-3 w-3"}),o.jsx("span",{children:n.length})]})]})]}),!x&&f&&n.length>0&&o.jsx("div",{className:"mt-2 ml-0 p-3 bg-muted/30 rounded-md border border-muted",children:o.jsx("div",{className:"space-y-2",children:n.map(j=>{const _=r.find(M=>M.call_id===j.call_id);return o.jsx("div",{className:"text-xs",children:o.jsxs("div",{className:"flex items-start gap-2",children:[o.jsx(ba,{className:"h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0"}),o.jsxs("div",{className:"flex-1 min-w-0",children:[o.jsxs("div",{className:"font-mono text-muted-foreground",children:[o.jsx("span",{className:"text-blue-600 dark:text-blue-400",children:j.name}),o.jsx("span",{className:"text-muted-foreground/60 ml-1",children:j.arguments&&o.jsxs("span",{className:"break-all",children:["(",j.arguments,")"]})})]}),_&&_.output&&o.jsx("div",{className:"mt-1 pl-5 border-l-2 border-green-600/20",children:o.jsxs("div",{className:"flex items-start gap-1",children:[o.jsx(vo,{className:"h-3 w-3 text-green-600 dark:text-green-400 mt-0.5 flex-shrink-0"}),o.jsx("pre",{className:"font-mono text-muted-foreground whitespace-pre-wrap break-all",children:_.output.substring(0,200)+(_.output.length>200?"...":"")})]})}),j.status==="incomplete"&&o.jsx("div",{className:"mt-1 pl-5 border-l-2 border-orange-600/20",children:o.jsxs("div",{className:"flex items-start gap-1",children:[o.jsx(wa,{className:"h-3 w-3 text-orange-600 dark:text-orange-400 mt-0.5 flex-shrink-0"}),o.jsx("span",{className:"font-mono text-orange-600 dark:text-orange-400",children:"Failed"})]})})]})]})},j.id)})})})]})]})}return e.type==="function_call"||e.type==="function_call_output",null}function BR({selectedAgent:e,onDebugEvent:n}){const r=de(ne=>ne.currentConversation),a=de(ne=>ne.availableConversations),l=de(ne=>ne.chatItems),c=de(ne=>ne.isStreaming),d=de(ne=>ne.isSubmitting),f=de(ne=>ne.loadingConversations),m=de(ne=>ne.uiMode),h=de(ne=>ne.conversationUsage),g=de(ne=>ne.pendingApprovals),y=de(ne=>ne.oaiMode),x=de(ne=>ne.setCurrentConversation),b=de(ne=>ne.setAvailableConversations),S=de(ne=>ne.setChatItems),N=de(ne=>ne.setIsStreaming),j=de(ne=>ne.setIsSubmitting),_=de(ne=>ne.setLoadingConversations),M=de(ne=>ne.updateConversationUsage),E=de(ne=>ne.setPendingApprovals),[T,R]=w.useState(!1),[D,O]=w.useState(null),[P,q]=w.useState(!1),[Q,ee]=w.useState(!1),{isCancelling:G,createAbortSignal:W,handleCancel:B,resetCancelling:U}=ON(),{isDragOver:k,droppedFiles:L,clearDroppedFiles:I,dragHandlers:H}=S5({disabled:d||c}),C=w.useRef(null),$=w.useRef(null),Y=w.useRef(null),V=w.useRef(!1),K=w.useRef("");w.useEffect(()=>{if(!$.current)return;const ne=C.current?.querySelector("[data-radix-scroll-area-viewport]");let he=!1;if(ne){const{scrollTop:X,scrollHeight:pe,clientHeight:Ne}=ne,ye=pe-X-Ne<100;he=V.current||ye}else he=!0;he&&$.current.scrollIntoView({behavior:c?"instant":"smooth"}),V.current&&!c&&(V.current=!1)},[l,c]),w.useEffect(()=>{},[c,d]),w.useEffect(()=>{const ne=async(X,pe,Ne)=>{const ye=ha(pe.id);if(!ye||!ye.responseId){N(!1);return}try{const Oe={model:Ne.id,input:[],stream:!0,conversation:pe.id},Se=Je.streamAgentExecutionOpenAIDirect(Ne.id,Oe,pe.id,void 0,ye.responseId);for await(const He of Se){if(n(He),He.type==="response.completed"){const Ve=He.response?.usage;Ve&&(Y.current={input_tokens:Ve.input_tokens,output_tokens:Ve.output_tokens,total_tokens:Ve.total_tokens});continue}if(He.type==="response.failed"){const Ve=He.response?.error,_e=Ve?typeof Ve=="object"&&"message"in Ve?Ve.message:JSON.stringify(Ve):"Request failed",$e=de.getState().chatItems;S($e.map(Fe=>Fe.id===X.id&&Fe.type==="message"?{...Fe,content:[{type:"text",text:K.current||_e}],status:"incomplete"}:Fe)),N(!1);return}if(He.type==="response.function_approval.requested"){const Re=He;E([...de.getState().pendingApprovals,{request_id:Re.request_id,function_call:Re.function_call}]);continue}if(He.type==="response.function_approval.responded"){const Re=He;E(de.getState().pendingApprovals.filter(Ve=>Ve.request_id!==Re.request_id));continue}if(He.type==="error"){const Ve=He.message||"An error occurred",_e=de.getState().chatItems;S(_e.map($e=>$e.id===X.id&&$e.type==="message"?{...$e,content:[{type:"text",text:K.current||Ve}],status:"incomplete"}:$e)),N(!1);return}if(He.type==="response.output_text.delta"&&"delta"in He&&He.delta){K.current+=He.delta;const Re=de.getState().chatItems;S(Re.map(Ve=>Ve.id===X.id&&Ve.type==="message"?{...Ve,content:[{type:"text",text:K.current}],status:"in_progress"}:Ve))}}const Ie=Y.current,Xe=de.getState().chatItems;S(Xe.map(He=>He.id===X.id&&He.type==="message"?{...He,status:"completed",usage:Ie||void 0}:He)),N(!1),Ie&&M(Ie.total_tokens),Y.current=null}catch(Oe){const Se=de.getState().chatItems;S(Se.map(Ie=>Ie.id===X.id&&Ie.type==="message"?{...Ie,content:[{type:"text",text:`Error resuming stream: ${Oe instanceof Error?Oe.message:"Unknown error"}`}],status:"incomplete"}:Ie)),N(!1)}},he=async()=>{if(e){_(!0);try{try{const{data:ye}=await Je.listConversations(e.id);if(b(ye),ye.length>0){const Oe=ye[0];x(Oe);try{let Se=[],Ie=!0,Xe;for(;Ie;){const Re=await Je.listConversationItems(Oe.id,{order:"asc",after:Xe});Se=Se.concat(Re.data),Ie=Re.has_more,Ie&&Re.data.length>0&&(Xe=Re.data[Re.data.length-1].id)}S(Se),N(!1);const He=ha(Oe.id);if(He&&!He.completed){K.current=He.accumulatedText||"";const Re={id:He.lastMessageId||`assistant-${Date.now()}`,type:"message",role:"assistant",content:He.accumulatedText?[{type:"text",text:He.accumulatedText}]:[],status:"in_progress"};S([...Se,Re]),N(!0),setTimeout(()=>{ne(Re,Oe,e)},100)}setTimeout(()=>{$.current?.scrollIntoView({behavior:"smooth"})},100)}catch{console.debug(`No items found for conversation ${Oe.id}, starting fresh`),S([]),N(!1)}return}}catch{}const X=`devui_convs_${e.id}`,pe=localStorage.getItem(X);if(pe)try{const ye=JSON.parse(pe);if(ye.length>0)try{await Je.listConversationItems(ye[0].id),b(ye),x(ye[0]),S([]),N(!1);return}catch{console.debug(`Cached conversation ${ye[0].id} no longer exists, clearing cache`),localStorage.removeItem(X)}}catch{localStorage.removeItem(X)}const Ne=await Je.createConversation({agent_id:e.id});x(Ne),b([Ne]),S([]),N(!1),O(null),localStorage.setItem(X,JSON.stringify([Ne]))}catch(X){b([]),S([]),N(!1);const pe=X instanceof Error?X.message:"Failed to create conversation";O({message:pe,type:"conversation_creation_error"})}finally{_(!1)}}};S([]),N(!1),x(void 0),K.current="",he()},[e,n,S,N,_,b,x,E,M]);const fe=w.useCallback(async()=>{if(e)try{const ne=await Je.createConversation({agent_id:e.id});x(ne),b([ne,...de.getState().availableConversations]),S([]),N(!1),O(null),de.setState({conversationUsage:{total_tokens:0,message_count:0}}),K.current="";const he=`devui_convs_${e.id}`,X=[ne,...a];localStorage.setItem(he,JSON.stringify(X))}catch(ne){const he=ne instanceof Error?ne.message:"Failed to create conversation";O({message:he,type:"conversation_creation_error"})}},[e,x,b,S,N]),ue=w.useCallback(async(ne,he)=>{if(he&&(he.preventDefault(),he.stopPropagation()),!!confirm("Delete this conversation? This cannot be undone."))try{if(await Je.deleteConversation(ne)){const pe=a.filter(Ne=>Ne.id!==ne);if(b(pe),r?.id===ne)if(pe.length>0){const Ne=pe[0];x(Ne),S([]),N(!1)}else x(void 0),S([]),N(!1),de.setState({conversationUsage:{total_tokens:0,message_count:0}}),K.current="";n("clear")}}catch{alert("Failed to delete conversation. Please try again.")}},[a,r,n,b,x,S,N]),te=w.useCallback(async()=>{if(P||!e)return;q(!0);const ne=de.getState().addToast,he=de.getState().updateAgent;try{await Je.reloadEntity(e.id);const X=await Je.getAgentInfo(e.id);he(X),ne({message:`${e.name} has been reloaded successfully`,type:"success"})}catch(X){const pe=X instanceof Error?X.message:"Failed to reload entity";ne({message:`Failed to reload: ${pe}`,type:"error",duration:6e3})}finally{q(!1)}},[P,e]),ie=w.useCallback(async ne=>{const he=a.find(X=>X.id===ne);if(he){x(he),n("clear");try{let X=[],pe=!0,Ne;for(;pe;){const Se=await Je.listConversationItems(ne,{order:"asc",after:Ne});X=X.concat(Se.data),pe=Se.has_more,pe&&Se.data.length>0&&(Ne=Se.data[Se.data.length-1].id)}const ye=X;S(ye),N(!1),de.setState({conversationUsage:{total_tokens:0,message_count:ye.length}});const Oe=ha(ne);if(Oe?.accumulatedText){K.current=Oe.accumulatedText;const Se={id:`assistant-${Date.now()}`,type:"message",role:"assistant",content:[{type:"output_text",text:Oe.accumulatedText}],status:"in_progress"};S([...ye,Se]),N(!0)}setTimeout(()=>{$.current?.scrollIntoView({behavior:"smooth"})},100)}catch{console.debug(`No items found for conversation ${ne}, starting with empty chat`),S([]),N(!1),de.setState({conversationUsage:{total_tokens:0,message_count:0}})}K.current=""}},[a,n,x,S,N]),xe=async(ne,he)=>{const X=g.find(Ie=>Ie.request_id===ne);if(!X)return;const pe=Math.floor(Date.now()/1e3),Ne={id:`user-approval-${Date.now()}`,type:"message",role:"user",content:[{type:"function_approval_request",request_id:ne,status:he?"approved":"rejected",function_call:X.function_call}],status:"completed",created_at:pe},ye=de.getState().chatItems;S([...ye,Ne]);const Se={input:[{type:"message",role:"user",content:[{type:"function_approval_response",request_id:ne,approved:he,function_call:X.function_call}]}],conversation_id:r?.id};return E(de.getState().pendingApprovals.filter(Ie=>Ie.request_id!==ne)),Se},ve=w.useCallback(async ne=>{if(!e)return;const he=ne.input.some(ye=>ye.type==="message"&&Array.isArray(ye.content)&&ye.content.some(Oe=>Oe.type==="function_approval_response")),X=[];for(const ye of ne.input)if(ye.type==="message"&&Array.isArray(ye.content)){for(const Oe of ye.content)if(Oe.type==="input_text")X.push({type:"text",text:Oe.text});else if(Oe.type==="input_image")X.push({type:"input_image",image_url:Oe.image_url||"",detail:"auto"});else if(Oe.type==="input_file"){const Se=Oe;X.push({type:"input_file",file_data:Se.file_data,filename:Se.filename})}}const pe=Math.floor(Date.now()/1e3);if(!he&&X.length>0){const ye={id:`user-${Date.now()}`,type:"message",role:"user",content:X,status:"completed",created_at:pe};S([...de.getState().chatItems,ye])}N(!0);const Ne={id:`assistant-${Date.now()}`,type:"message",role:"assistant",content:[],status:"in_progress",created_at:pe};S([...de.getState().chatItems,Ne]);try{let ye=r;if(!ye)try{ye=await Je.createConversation({agent_id:e.id}),x(ye),b([ye,...de.getState().availableConversations]),O(null)}catch(Re){const Ve=Re instanceof Error?Re.message:"Failed to create conversation";O({message:Ve,type:"conversation_creation_error"}),j(!1),N(!1);return}ye?.id&&Je.clearStreamingState(ye.id);const Oe={input:ne.input,conversation_id:ye?.id};K.current="";const Se=W(),Ie=Je.streamAgentExecutionOpenAI(e.id,Oe,Se);for await(const Re of Ie){if(n(Re),Re.type==="response.completed"){const _e=Re.response?.usage;_e&&(Y.current={input_tokens:_e.input_tokens,output_tokens:_e.output_tokens,total_tokens:_e.total_tokens});continue}if(Re.type==="response.failed"){const _e=Re.response?.error;let $e="Request failed";_e&&(typeof _e=="object"&&"message"in _e?($e=_e.message,"code"in _e&&_e.code&&($e+=` (Code: ${_e.code})`)):typeof _e=="string"&&($e=_e));const Fe=de.getState().chatItems;S(Fe.map(Nt=>Nt.id===Ne.id&&Nt.type==="message"?{...Nt,content:[{type:"text",text:K.current||$e}],status:"incomplete"}:Nt)),N(!1);return}if(Re.type==="response.function_approval.requested"){const Ve=Re;E([...de.getState().pendingApprovals,{request_id:Ve.request_id,function_call:Ve.function_call}]);const _e=de.getState().chatItems;S(_e.map($e=>$e.id===Ne.id&&$e.type==="message"?{...$e,content:[...$e.content,{type:"function_approval_request",request_id:Ve.request_id,status:"pending",function_call:Ve.function_call}],status:"in_progress"}:$e));continue}if(Re.type==="response.function_call_arguments.delta"){const Ve=Re,_e=de.getState().chatItems;S(_e.map($e=>$e.type==="function_call"&&$e.call_id===Ve.item_id?{...$e,arguments:($e.arguments||"")+(Ve.delta||"")}:$e));continue}if(Re.type==="response.function_result.complete"){const Ve=Re,_e={id:`result-${Date.now()}`,type:"function_call_output",call_id:Ve.call_id,output:Ve.output,status:Ve.status==="completed"?"completed":"incomplete",created_at:Math.floor(Date.now()/1e3)},$e=de.getState().chatItems;S([...$e,_e]);continue}if(Re.type==="error"){const _e=Re.message||"An error occurred",$e=de.getState().chatItems;S($e.map(Fe=>Fe.id===Ne.id&&Fe.type==="message"?{...Fe,content:[{type:"text",text:_e}],status:"incomplete"}:Fe)),N(!1);return}if(Re.type==="response.output_item.added"){const _e=Re.item;if(_e.type==="function_call"){const Fe={id:_e.id||`call-${Date.now()}`,type:"function_call",name:_e.name,arguments:_e.arguments||"",call_id:_e.call_id,status:(_e.status==="failed"||_e.status==="cancelled"?"incomplete":_e.status)||"in_progress",created_at:Math.floor(Date.now()/1e3)},Nt=de.getState().chatItems;S([...Nt,Fe]);continue}const $e=de.getState().chatItems;S($e.map(Fe=>{if(Fe.id===Ne.id&&Fe.type==="message"){const Nt=Fe.content;let yt=null;if(_e.type==="output_image"?yt={type:"output_image",image_url:_e.image_url,alt_text:_e.alt_text,mime_type:_e.mime_type}:_e.type==="output_file"?yt={type:"output_file",filename:_e.filename,file_url:_e.file_url,file_data:_e.file_data,mime_type:_e.mime_type}:_e.type==="output_data"&&(yt={type:"output_data",data:_e.data,mime_type:_e.mime_type,description:_e.description}),yt)return{...Fe,content:[...Nt,yt],status:"in_progress"}}return Fe}));continue}if(Re.type==="response.output_text.delta"&&"delta"in Re&&Re.delta){K.current+=Re.delta;const Ve=de.getState().chatItems;S(Ve.map(_e=>{if(_e.id===Ne.id&&_e.type==="message"){const $e=_e.content.filter(Fe=>Fe.type!=="text");return{..._e,content:[...$e,{type:"text",text:K.current}],status:"in_progress"}}return _e}))}}const Xe=Y.current,He=de.getState().chatItems;S(He.map(Re=>Re.id===Ne.id&&Re.type==="message"?{...Re,status:"completed",usage:Xe||void 0}:Re)),N(!1),Xe&&M(Xe.total_tokens),Y.current=null}catch(ye){if(Hu(ye)){ee(!0);const Oe=de.getState().chatItems;S(Oe.map(Se=>Se.id===Ne.id&&Se.type==="message"?{...Se,status:K.current?"completed":"incomplete",content:Se.content}:Se))}else{const Oe=de.getState().chatItems;S(Oe.map(Se=>Se.id===Ne.id&&Se.type==="message"?{...Se,content:[{type:"text",text:`Error: ${ye instanceof Error?ye.message:"Failed to get response"}`}],status:"incomplete"}:Se))}N(!1),U()}},[e,r,n,S,N,x,b,E,M,W,U]),be=async ne=>{if(!(!e||ne.length===0)){V.current=!0,ee(!1),j(!0);try{await ve({input:[{type:"message",role:"user",content:ne}],conversation_id:r?.id})}finally{j(!1)}}};return o.jsxs("div",{className:"flex h-[calc(100vh-3.5rem)] flex-col relative",...H,children:[k&&o.jsx("div",{className:"absolute inset-0 z-50 bg-blue-50/95 dark:bg-blue-950/95 backdrop-blur-sm flex items-center justify-center border-2 border-dashed border-blue-400 dark:border-blue-500 rounded-lg m-2",children:o.jsxs("div",{className:"text-center p-8",children:[o.jsx("div",{className:"text-blue-600 dark:text-blue-400 text-lg font-medium mb-2",children:"Drop files here"}),o.jsx("div",{className:"text-blue-500/80 dark:text-blue-400/70 text-sm",children:"Images, PDFs, audio, and other files"})]})}),o.jsxs("div",{className:"border-b pb-2 p-4 flex-shrink-0",children:[o.jsxs("div",{className:"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3 mb-3",children:[o.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[o.jsx("h2",{className:"font-semibold text-sm truncate",children:o.jsxs("div",{className:"flex items-center gap-2",children:[o.jsx(Hs,{className:"h-4 w-4 flex-shrink-0"}),o.jsx("span",{className:"truncate",children:y.enabled?`Chat with ${y.model}`:`Chat with ${e.name||e.id}`})]})}),!y.enabled&&m==="developer"&&o.jsxs(o.Fragment,{children:[o.jsx(Te,{variant:"ghost",size:"sm",onClick:()=>R(!0),className:"h-6 w-6 p-0 flex-shrink-0",title:"View agent details",children:o.jsx(va,{className:"h-4 w-4"})}),e.source!=="in_memory"&&o.jsx(Te,{variant:"ghost",size:"sm",onClick:te,disabled:P,className:"h-6 w-6 p-0 flex-shrink-0",title:P?"Reloading...":"Reload entity code (hot reload)",children:o.jsx(Fp,{className:`h-4 w-4 ${P?"animate-spin":""}`})})]})]}),o.jsxs("div",{className:"flex flex-col sm:flex-row items-stretch sm:items-center gap-2 flex-shrink-0",children:[o.jsxs(bd,{value:r?.id||"",onValueChange:ie,disabled:f||d,children:[o.jsx(Nd,{className:"w-full sm:w-64",children:o.jsx(wd,{placeholder:f?"Loading...":a.length===0?"No conversations":r?`Conversation ${r.id.slice(-8)}`:"Select conversation",children:r&&o.jsxs("div",{className:"flex items-center gap-2 text-xs",children:[o.jsxs("span",{children:["Conversation ",r.id.slice(-8)]}),h.total_tokens>0&&o.jsxs(o.Fragment,{children:[o.jsx("span",{className:"text-muted-foreground",children:"•"}),o.jsxs("span",{className:"text-muted-foreground",children:[h.total_tokens>=1e3?`${(h.total_tokens/1e3).toFixed(1)}k`:h.total_tokens," ","tokens"]})]})]})})}),o.jsx(Sd,{children:a.map(ne=>o.jsx(jd,{value:ne.id,children:o.jsxs("div",{className:"flex items-center justify-between w-full",children:[o.jsxs("span",{children:["Conversation ",ne.id.slice(-8)]}),ne.created_at&&o.jsx("span",{className:"text-xs text-muted-foreground ml-3",children:new Date(ne.created_at*1e3).toLocaleDateString()})]})},ne.id))})]}),o.jsx(Te,{variant:"outline",size:"icon",onClick:()=>r&&ue(r.id),disabled:!r||d,title:r?`Delete Conversation ${r.id.slice(-8)}`:"No conversation selected",children:o.jsx(Gp,{className:"h-4 w-4"})}),o.jsxs(Te,{variant:"outline",size:"lg",onClick:fe,disabled:!e||d,className:"whitespace-nowrap ",children:[o.jsx(qp,{className:"h-4 w-4 mr-2"}),o.jsx("span",{className:"hidden md:inline",children:" New Conversation"})]})]})]}),y.enabled?o.jsx("p",{className:"text-sm text-muted-foreground",children:"Using OpenAI model directly. Local agent tools and instructions are not applied."}):e.description&&o.jsx("p",{className:"text-sm text-muted-foreground",children:e.description})]}),D&&o.jsxs("div",{className:"mx-4 mt-2 p-3 bg-destructive/10 border border-destructive/30 rounded-md flex items-start gap-2",children:[o.jsx(is,{className:"h-4 w-4 text-destructive mt-0.5 flex-shrink-0"}),o.jsxs("div",{className:"flex-1 min-w-0",children:[o.jsx("div",{className:"text-sm font-medium text-destructive",children:"Failed to Create Conversation"}),o.jsx("div",{className:"text-xs text-destructive/90 mt-1 break-words",children:D.message}),D.code&&o.jsxs("div",{className:"text-xs text-destructive/70 mt-1",children:["Error Code: ",D.code]})]}),o.jsx("button",{onClick:()=>O(null),className:"text-destructive hover:text-destructive/80 flex-shrink-0",title:"Dismiss error",children:o.jsx(wa,{className:"h-4 w-4"})})]}),o.jsx(ls,{className:"flex-1 p-4 h-0",ref:C,children:o.jsxs("div",{className:"space-y-4",children:[l.length===0?o.jsxs("div",{className:"flex flex-col items-center justify-center h-32 text-center",children:[o.jsxs("div",{className:"text-muted-foreground text-sm",children:["Start a conversation with"," ",e.name||e.id]}),o.jsx("div",{className:"text-xs text-muted-foreground mt-1",children:"Type a message below to begin"})]}):(()=>{const ne=[],he=new Map,X=new Map;let pe=null;const Ne=[],ye=[];for(let Oe=0;Oe0){const Ie=he.get(Se.id)||[];Ie.push(...Ne),he.set(Se.id,Ie),Ne.length=0}if(ye.length>0){const Ie=X.get(Se.id)||[];Ie.push(...ye),X.set(Se.id,Ie),ye.length=0}pe=Se.id}else if(Se.type==="function_call")if(pe){const Ie=he.get(pe)||[];Ie.push(Se),he.set(pe,Ie)}else Ne.push(Se);else if(Se.type==="function_call_output")if(pe){const Ie=X.get(pe)||[];Ie.push(Se),X.set(pe,Ie)}else ye.push(Se);else Se.type==="message"&&Se.role==="user"&&(pe=null)}for(const Oe of l)if(Oe.type==="message"){const Se=he.get(Oe.id)||[],Ie=X.get(Oe.id)||[];ne.push(o.jsx(PR,{item:Oe,toolCalls:Se,toolResults:Ie},Oe.id))}return ne})(),Q&&!c&&o.jsx("div",{className:"px-4 py-2",children:o.jsx("div",{className:"border rounded-lg border-orange-500/40 bg-orange-500/5 dark:bg-orange-500/10",children:o.jsxs("div",{className:"px-4 py-3 flex items-center gap-2",children:[o.jsx(cd,{className:"w-4 h-4 text-orange-500 dark:text-orange-400 fill-current"}),o.jsx("span",{className:"font-medium text-sm text-orange-700 dark:text-orange-300",children:"Response stopped by user"})]})})}),o.jsx("div",{ref:$})]})}),g.length>0&&o.jsx("div",{className:"border-t bg-amber-50 dark:bg-amber-950/20 p-4 flex-shrink-0",children:o.jsxs("div",{className:"flex items-start gap-3",children:[o.jsx(is,{className:"h-5 w-5 text-amber-600 dark:text-amber-500 mt-0.5 flex-shrink-0"}),o.jsxs("div",{className:"flex-1 min-w-0",children:[o.jsx("h4",{className:"font-medium text-sm mb-2",children:"Approval Required"}),o.jsx("div",{className:"space-y-2",children:g.map(ne=>o.jsxs("div",{className:"bg-white dark:bg-gray-900 rounded-lg p-3 border border-amber-200 dark:border-amber-900",children:[o.jsxs("div",{className:"font-mono text-xs mb-3 break-all",children:[o.jsx("span",{className:"text-blue-600 dark:text-blue-400 font-semibold",children:ne.function_call.name}),o.jsx("span",{className:"text-gray-500",children:"("}),o.jsx("span",{className:"text-gray-700 dark:text-gray-300",children:JSON.stringify(ne.function_call.arguments)}),o.jsx("span",{className:"text-gray-500",children:")"})]}),o.jsxs("div",{className:"flex gap-2",children:[o.jsxs(Te,{size:"sm",onClick:async()=>{const he=await xe(ne.request_id,!0);he&&await ve(he)},variant:"default",className:"flex-1 sm:flex-none",children:[o.jsx(vo,{className:"h-4 w-4 mr-1"}),"Approve"]}),o.jsxs(Te,{size:"sm",onClick:async()=>{const he=await xe(ne.request_id,!1);he&&await ve(he)},variant:"outline",className:"flex-1 sm:flex-none",children:[o.jsx(wa,{className:"h-4 w-4 mr-1"}),"Reject"]})]})]},ne.request_id))})]})]})}),o.jsx("div",{className:"border-t flex-shrink-0",children:o.jsx("div",{className:"p-4",children:o.jsx(PN,{onSubmit:be,isSubmitting:d,isStreaming:c,onCancel:B,isCancelling:G,placeholder:`Message ${e.name||e.id}... (Shift+Enter for new line)`,showFileUpload:!0,entityName:e.name||e.id,disabled:!e,externalFiles:L,onExternalFilesProcessed:I})})}),o.jsx($R,{agent:e,open:T,onOpenChange:R})]})}function Gv({message:e="Loading...",description:n,size:r="md",className:a,fullPage:l=!1}){const c=o.jsxs("div",{className:Ze("flex flex-col items-center justify-center gap-3",l?"min-h-[50vh]":"py-8",a),children:[o.jsx(Ou,{size:r,className:"text-muted-foreground"}),o.jsxs("div",{className:"text-center space-y-1",children:[o.jsx("p",{className:Ze("font-medium text-muted-foreground",r==="sm"&&"text-sm",r==="lg"&&"text-lg"),children:e}),n&&o.jsx("p",{className:"text-sm text-muted-foreground/80",children:n})]})]});return l?o.jsx("div",{className:"flex items-center justify-center min-h-screen bg-background",children:c}):c}var _d="Checkbox",[UR,I7]=us(_d),[VR,og]=UR(_d);function qR(e){const{__scopeCheckbox:n,checked:r,children:a,defaultChecked:l,disabled:c,form:d,name:f,onCheckedChange:m,required:h,value:g="on",internal_do_not_use_render:y}=e,[x,b]=io({prop:r,defaultProp:l??!1,onChange:m,caller:_d}),[S,N]=w.useState(null),[j,_]=w.useState(null),M=w.useRef(!1),E=S?!!d||!!S.closest("form"):!0,T={checked:x,disabled:c,setChecked:b,control:S,setControl:N,name:f,form:d,value:g,hasConsumerStoppedPropagationRef:M,required:h,defaultChecked:Sr(l)?!1:l,isFormControl:E,bubbleInput:j,setBubbleInput:_};return o.jsx(VR,{scope:n,...T,children:FR(y)?y(T):a})}var h2="CheckboxTrigger",p2=w.forwardRef(({__scopeCheckbox:e,onKeyDown:n,onClick:r,...a},l)=>{const{control:c,value:d,disabled:f,checked:m,required:h,setControl:g,setChecked:y,hasConsumerStoppedPropagationRef:x,isFormControl:b,bubbleInput:S}=og(h2,e),N=rt(l,g),j=w.useRef(m);return w.useEffect(()=>{const _=c?.form;if(_){const M=()=>y(j.current);return _.addEventListener("reset",M),()=>_.removeEventListener("reset",M)}},[c,y]),o.jsx(Ye.button,{type:"button",role:"checkbox","aria-checked":Sr(m)?"mixed":m,"aria-required":h,"data-state":w2(m),"data-disabled":f?"":void 0,disabled:f,value:d,...a,ref:N,onKeyDown:Ae(n,_=>{_.key==="Enter"&&_.preventDefault()}),onClick:Ae(r,_=>{y(M=>Sr(M)?!0:!M),S&&b&&(x.current=_.isPropagationStopped(),x.current||_.stopPropagation())})})});p2.displayName=h2;var g2=w.forwardRef((e,n)=>{const{__scopeCheckbox:r,name:a,checked:l,defaultChecked:c,required:d,disabled:f,value:m,onCheckedChange:h,form:g,...y}=e;return o.jsx(qR,{__scopeCheckbox:r,checked:l,defaultChecked:c,disabled:f,required:d,onCheckedChange:h,name:a,form:g,value:m,internal_do_not_use_render:({isFormControl:x})=>o.jsxs(o.Fragment,{children:[o.jsx(p2,{...y,ref:n,__scopeCheckbox:r}),x&&o.jsx(b2,{__scopeCheckbox:r})]})})});g2.displayName=_d;var x2="CheckboxIndicator",y2=w.forwardRef((e,n)=>{const{__scopeCheckbox:r,forceMount:a,...l}=e,c=og(x2,r);return o.jsx(Fn,{present:a||Sr(c.checked)||c.checked===!0,children:o.jsx(Ye.span,{"data-state":w2(c.checked),"data-disabled":c.disabled?"":void 0,...l,ref:n,style:{pointerEvents:"none",...e.style}})})});y2.displayName=x2;var v2="CheckboxBubbleInput",b2=w.forwardRef(({__scopeCheckbox:e,...n},r)=>{const{control:a,hasConsumerStoppedPropagationRef:l,checked:c,defaultChecked:d,required:f,disabled:m,name:h,value:g,form:y,bubbleInput:x,setBubbleInput:b}=og(v2,e),S=rt(r,b),N=sg(c),j=Ap(a);w.useEffect(()=>{const M=x;if(!M)return;const E=window.HTMLInputElement.prototype,R=Object.getOwnPropertyDescriptor(E,"checked").set,D=!l.current;if(N!==c&&R){const O=new Event("click",{bubbles:D});M.indeterminate=Sr(c),R.call(M,Sr(c)?!1:c),M.dispatchEvent(O)}},[x,N,c,l]);const _=w.useRef(Sr(c)?!1:c);return o.jsx(Ye.input,{type:"checkbox","aria-hidden":!0,defaultChecked:d??_.current,required:f,disabled:m,name:h,value:g,form:y,...n,tabIndex:-1,ref:S,style:{...n.style,...j,position:"absolute",pointerEvents:"none",opacity:0,margin:0,transform:"translateX(-100%)"}})});b2.displayName=v2;function FR(e){return typeof e=="function"}function Sr(e){return e==="indeterminate"}function w2(e){return Sr(e)?"indeterminate":e?"checked":"unchecked"}function to({className:e,...n}){return o.jsx(g2,{"data-slot":"checkbox",className:Ze("peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",e),...n,children:o.jsx(y2,{"data-slot":"checkbox-indicator",className:"flex items-center justify-center text-current transition-none",children:o.jsx(vo,{className:"size-3.5"})})})}function YR(e){return["name","title","id","key","label","type","status","tag","category","code","username","password"].includes(e.toLowerCase())}function xr({name:e,schema:n,value:r,onChange:a,isRequired:l=!1}){const{type:c,description:d,enum:f,default:m}=n,h=n.format==="textarea"||d&&d.length>100||c==="string"&&!f&&!YR(e),g=h||d&&d.length>150,y=h||d&&d.length>80||c==="array",x=(()=>{switch(c){case"string":return f?o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsxs(bd,{value:typeof r=="string"&&r?r:typeof m=="string"?m:f[0],onValueChange:S=>a(S),children:[o.jsx(Nd,{children:o.jsx(wd,{placeholder:`Select ${e}`})}),o.jsx(Sd,{children:f.map(S=>o.jsx(jd,{value:S,children:S},S))})]}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]}):h||d&&d.length>100?o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(ro,{id:e,value:typeof r=="string"?r:"",onChange:S=>a(S.target.value),placeholder:typeof m=="string"?m:`Enter ${e}`,rows:h?4:2,className:"min-w-[300px] w-full"}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]}):o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(An,{id:e,type:"text",value:typeof r=="string"?r:"",onChange:S=>a(S.target.value),placeholder:typeof m=="string"?m:`Enter ${e}`}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"number":return o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(An,{id:e,type:"number",value:typeof r=="number"?r:"",onChange:S=>{const N=parseFloat(S.target.value);a(isNaN(N)?"":N)},placeholder:typeof m=="number"?m.toString():`Enter ${e}`}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"boolean":return o.jsxs("div",{className:"space-y-2",children:[o.jsxs("div",{className:"flex items-center space-x-2",children:[o.jsx(to,{id:e,checked:!!r,onCheckedChange:S=>a(S)}),o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]})]}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"array":return o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(ro,{id:e,value:Array.isArray(r)?r.join(", "):typeof r=="string"?r:"",onChange:S=>{const N=S.target.value.split(",").map(j=>j.trim()).filter(j=>j.length>0);a(N)},placeholder:"Enter items separated by commas",rows:2}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"object":default:return o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(ro,{id:e,value:typeof r=="object"&&r!==null?JSON.stringify(r,null,2):typeof r=="string"?r:"",onChange:S=>{try{const N=JSON.parse(S.target.value);a(N)}catch{a(S.target.value)}},placeholder:'{"key": "value"}',rows:3}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]})}})(),b=()=>g?"md:col-span-2 lg:col-span-3 xl:col-span-4":y?"xl:col-span-2":"";return o.jsx("div",{className:b(),children:x})}function GR({inputSchema:e,inputTypeName:n,onSubmit:r,isSubmitting:a=!1,className:l}){const[c,d]=w.useState(!1),[f,m]=w.useState(!1),h=l?.includes("embedded"),[g,y]=w.useState({}),[x,b]=w.useState(!1),S=e.properties||{},N=Object.keys(S),j=e.required||[],_=e.type==="string"&&!e.enum,M=N.filter(k=>!j.includes(k)),E=j.includes("role")&&M.some(k=>["text","message","content"].includes(k))&&S.role?.type==="string",T=N.filter(k=>j.includes(k)&&!(E&&k==="role")),R=M,D=E?[...R].sort((k,L)=>{const I=H=>["text","message","content"].includes(H)?1:0;return I(L)-I(k)}):R,P=Math.max(0,(E?1:6)-T.length),q=D.slice(0,P),Q=D.slice(P),ee=Q.length>0,G=T.length>0,W=_?g.value!==void 0&&g.value!=="":j.length>0?j.every(k=>E&&k==="role"&&g.role==="user"?!0:g[k]!==void 0&&g[k]!==""):Object.keys(g).length>0;w.useEffect(()=>{if(e.type==="string")y({value:e.default||""});else if(e.type==="object"&&e.properties){const k={};Object.entries(e.properties).forEach(([L,I])=>{I.default!==void 0?k[L]=I.default:I.enum&&I.enum.length>0&&(k[L]=I.enum[0])}),E&&!k.role&&(k.role="user"),y(k)}},[e,E]);const B=k=>{if(k.preventDefault(),b(!0),e.type==="string")r({input:g.value||""});else if(e.type==="object"){const L=e.properties||{},I=Object.keys(L);if(I.length===1){const H=I[0];r({[H]:g[H]||""})}else{const H={};Object.keys(g).forEach(C=>{const $=g[C];(j.includes(C)||$!==void 0&&$!==""&&$!==null)&&(H[C]=$)}),r(H)}}else r(g);h||d(!1),b(!1)},U=(k,L)=>{y(I=>({...I,[k]:L}))};return h?o.jsxs("form",{onSubmit:B,className:l,children:[o.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-4",children:[_&&o.jsx(xr,{name:"Input",schema:e,value:g.value,onChange:k=>U("value",k),isRequired:!1}),!_&&o.jsxs(o.Fragment,{children:[T.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!0},k)),G&&R.length>0&&o.jsx("div",{className:"sm:col-span-2 border-t border-border my-2"}),q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k)),ee&&o.jsx("div",{className:"sm:col-span-2",children:o.jsx(Te,{type:"button",variant:"ghost",size:"sm",onClick:()=>m(!f),className:"w-full justify-center gap-2",children:f?o.jsxs(o.Fragment,{children:[o.jsx(Mu,{className:"h-4 w-4"}),"Hide ",Q.length," optional field",Q.length!==1?"s":""]}):o.jsxs(o.Fragment,{children:[o.jsx(Gt,{className:"h-4 w-4"}),"Show ",Q.length," optional field",Q.length!==1?"s":""]})})}),f&&Q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k))]})]}),o.jsx("div",{className:"flex gap-2 mt-4 justify-end",children:o.jsxs(Te,{type:"submit",disabled:x||!W,size:"default",children:[o.jsx(Zi,{className:"h-4 w-4"}),x?"Running...":"Run Workflow"]})})]}):o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:Ze("flex flex-col",l),children:[o.jsxs("div",{className:"border-b border-border px-4 py-3 bg-muted",children:[o.jsx(zN,{className:"text-sm mb-3",children:"Run Workflow"}),o.jsxs(Te,{onClick:()=>d(!0),disabled:a,className:"w-full",size:"default",children:[o.jsx(Zi,{className:"h-4 w-4 mr-2"}),a?"Running...":"Run Workflow"]})]}),o.jsxs("div",{className:"px-4 py-3",children:[o.jsxs("div",{className:"text-sm text-muted-foreground",children:[o.jsx("strong",{children:"Input Type:"})," ",o.jsx("code",{className:"bg-muted px-1 py-0.5 rounded",children:n}),e.type==="object"&&e.properties&&o.jsxs("span",{className:"ml-2",children:["(",Object.keys(e.properties).length," field",Object.keys(e.properties).length!==1?"s":"",")"]})]}),o.jsx("p",{className:"text-xs text-muted-foreground mt-2",children:'Click "Run Workflow" to configure inputs and execute'})]})]}),o.jsx(kr,{open:c,onOpenChange:d,children:o.jsxs(Ar,{className:"w-full max-w-md sm:max-w-lg md:max-w-2xl lg:max-w-4xl xl:max-w-5xl max-h-[90vh] flex flex-col",children:[o.jsxs(Mr,{children:[o.jsx(Tr,{children:"Run Workflow"}),o.jsx(bo,{onClose:()=>d(!1)})]}),o.jsx("div",{className:"px-8 py-4 border-b flex-shrink-0",children:o.jsx("div",{className:"text-sm text-muted-foreground",children:o.jsxs("div",{className:"flex items-center gap-3",children:[o.jsx("span",{className:"font-medium",children:"Input Type:"}),o.jsx("code",{className:"bg-muted px-3 py-1 text-xs font-mono",children:n}),e.type==="object"&&o.jsxs("span",{className:"text-xs text-muted-foreground",children:[N.length," field",N.length!==1?"s":""]})]})})}),o.jsx("div",{className:"px-8 py-6 overflow-y-auto flex-1 min-h-0",children:o.jsx("form",{id:"workflow-modal-form",onSubmit:B,children:o.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 md:gap-8 max-w-none",children:[_&&o.jsxs("div",{className:"md:col-span-2 lg:col-span-3 xl:col-span-4",children:[o.jsx(xr,{name:"Input",schema:e,value:g.value,onChange:k=>U("value",k),isRequired:!1}),e.description&&o.jsx("p",{className:"text-sm text-muted-foreground mt-2",children:e.description})]}),!_&&o.jsxs(o.Fragment,{children:[T.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!0},k)),G&&R.length>0&&o.jsx("div",{className:"md:col-span-2 lg:col-span-3 xl:col-span-4",children:o.jsx("div",{className:"border-t border-border"})}),q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k)),ee&&o.jsx("div",{className:"md:col-span-2 lg:col-span-3 xl:col-span-4",children:o.jsx(Te,{type:"button",variant:"ghost",size:"sm",onClick:()=>m(!f),className:"w-full justify-center gap-2",children:f?o.jsxs(o.Fragment,{children:[o.jsx(Mu,{className:"h-4 w-4"}),"Hide ",Q.length," optional field",Q.length!==1?"s":""]}):o.jsxs(o.Fragment,{children:[o.jsx(Gt,{className:"h-4 w-4"}),"Show ",Q.length," optional field",Q.length!==1?"s":""]})})}),f&&Q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k))]})]})})}),o.jsx("div",{className:"px-8 py-4 border-t flex-shrink-0",children:o.jsxs(u5,{children:[o.jsx(Te,{variant:"outline",onClick:()=>d(!1),disabled:x,children:"Cancel"}),o.jsxs(Te,{type:"submit",form:"workflow-modal-form",disabled:x||!W,children:[o.jsx(Zi,{className:"h-4 w-4 mr-2"}),x?"Running...":"Run Workflow"]})]})})]})})]})}function XR(e,n,r="LR"){if(e.length===0)return e;if(e.length===1)return e.map(S=>({...S,position:{x:0,y:0}}));const a=new Map,l=new Map;e.forEach(S=>{a.set(S.id,[]),l.set(S.id,[])}),n.forEach(S=>{a.get(S.source)?.push(S.target),l.get(S.target)?.push(S.source)});const c=e.filter(S=>(l.get(S.id)||[]).length===0);c.length===0&&c.push(e[0]);const d=220,f=120,m=r==="LR"?350:280,h=r==="TB"?250:180,g=new Map,y=new Map,x=[],b=new Set;for(c.forEach(S=>{x.push({nodeId:S.id,level:0})});x.length>0;){const{nodeId:S,level:N}=x.shift();if(b.has(S))continue;b.add(S),y.has(N)||y.set(N,[]),y.get(N).push(S),(a.get(S)||[]).forEach(_=>{b.has(_)||x.push({nodeId:_,level:N+1})})}return e.forEach(S=>{if(!b.has(S.id)){const j=Math.max(...Array.from(y.keys()),-1)+1;y.has(j)||y.set(j,[]),y.get(j).push(S.id)}}),y.forEach((S,N)=>{const j=S.length;S.forEach((_,M)=>{let E,T;r==="LR"?(E=N*m,T=-((j-1)*h)/2+M*h):(T=N*h,E=-((j-1)*m)/2+M*m),g.set(_,{x:E,y:T,level:N})})}),e.map(S=>{const N=g.get(S.id)||{x:0,y:0};return{...S,position:{x:N.x-d/2,y:N.y-f/2}}})}function ZR(e){return typeof e=="object"&&e!==null&&"id"in e&&"edge_groups"in e&&"executors"in e&&"start_executor_id"in e&&"max_iterations"in e&&typeof e.id=="string"&&Array.isArray(e.edge_groups)&&typeof e.executors=="object"&&typeof e.start_executor_id=="string"&&typeof e.max_iterations=="number"}function N2(e){return ZR(e)?e:null}function WR(e){if(!e||e.type!=="object"||!e.properties)return!1;const n=e.properties,r="text"in n&&n.text?.type==="string",a="role"in n&&n.role?.type==="string";return!!(r&&a||Object.keys(n).length===1&&r)}function yu(e,n=50,r="..."){return e.length<=n?e:e.substring(0,n)+r}function tp(e,n,r){if(!e)return console.warn("convertWorkflowDumpToNodes: workflowDump is undefined"),[];const a=N2(e);let l,c;return a?(l=Object.values(a.executors).map(f=>({id:f.id,type:f.type,name:f.name||f.id,description:f.description,config:f.config})),c=a.start_executor_id):(l=S2(e),c=e?.start_executor_id),!l||!Array.isArray(l)||l.length===0?(console.warn("No executors found in workflow dump. Available keys:",Object.keys(e)),[]):l.map(f=>({id:f.id,type:"executor",position:{x:0,y:0},data:{executorId:f.id,executorType:f.type,name:f.name||f.id,state:"pending",isStartNode:f.id===c,layoutDirection:r||"LR",onNodeClick:n}}))}function np(e){if(!e)return console.warn("convertWorkflowDumpToEdges: workflowDump is undefined"),[];const n=N2(e);let r;return n?(r=[],n.edge_groups.forEach(l=>{l.edges.forEach(c=>{r.push({source:c.source_id,target:c.target_id,condition:c.condition_name})})})):r=j2(e),!r||!Array.isArray(r)||r.length===0?(console.warn("No connections found in workflow dump. Available keys:",Object.keys(e)),[]):r.map(l=>{const c=l.source===l.target;return{id:`${l.source}-${l.target}`,source:l.source,target:l.target,sourceHandle:"source",targetHandle:"target",type:c?"selfLoop":"default",animated:!1,style:{stroke:"#6b7280",strokeWidth:2}}})}function S2(e){if(e.executors&&typeof e.executors=="object"&&!Array.isArray(e.executors)){const a=e.executors;return Object.entries(a).map(([l,c])=>({id:l,type:c.type_||c.type||"executor",name:c.name||l,description:c.description,config:c.config}))}const n=["executors","agents","steps","nodes"];for(const a of n)if(e[a]&&Array.isArray(e[a]))return e[a];if(e.config&&typeof e.config=="object")return S2(e.config);const r=[];return Object.entries(e).forEach(([a,l])=>{if(typeof l=="object"&&l!==null&&("type"in l||"type_"in l)){const c=l;r.push({id:a,type:c.type_||c.type||"executor",name:c.name||a,description:c.description,config:c.config})}}),r}function j2(e){if(e.edge_groups&&Array.isArray(e.edge_groups)){const r=[];return e.edge_groups.forEach(a=>{if(typeof a=="object"&&a!==null&&"edges"in a){const l=a.edges;Array.isArray(l)&&l.forEach(c=>{if(typeof c=="object"&&c!==null&&"source_id"in c&&"target_id"in c){const d=c;r.push({source:d.source_id,target:d.target_id,condition:d.condition_name||void 0})}})}}),r}const n=["connections","edges","transitions","links"];for(const r of n)if(e[r]&&Array.isArray(e[r]))return e[r];return e.config&&typeof e.config=="object"?j2(e.config):[]}function sp(e,n,r="LR"){return XR(e,n,r)}function KR(e,n){const r={};let a=!1;const l={};return e.forEach(c=>{if(c.type==="response.output_item.added"||c.type==="response.output_item.done"){const d=c.item;if(d&&d.type==="executor_action"&&d.executor_id){const f=d.executor_id,m=d.id;if(c.type==="response.output_item.added"&&(l[f]=m),!(l[f]===m)&&c.type==="response.output_item.done")return;let g="pending",y;c.type==="response.output_item.added"?g="running":c.type==="response.output_item.done"&&(d.status==="completed"?g="completed":d.status==="failed"?(g="failed",y=d.error?typeof d.error=="string"?d.error:JSON.stringify(d.error):"Execution failed"):d.status==="cancelled"&&(g="cancelled")),r[f]={nodeId:f,state:g,data:d.result,error:y,timestamp:new Date().toISOString()}}}else if(c.type==="response.created"||c.type==="response.in_progress")a=!0;else if(c.type==="response.workflow_event.completed"&&"data"in c&&c.data){const f=c.data,m=f.executor_id,h=f.event_type,g=f.data;let y="pending",x;h==="ExecutorInvokedEvent"?y="running":h==="ExecutorCompletedEvent"?y="completed":h?.includes("Error")||h?.includes("Failed")?(y="failed",x=typeof g=="string"?g:"Execution failed"):h?.includes("Cancel")?y="cancelled":h==="WorkflowCompletedEvent"||h==="WorkflowOutputEvent"?y="completed":h==="WorkflowStartedEvent"&&(a=!0),m&&(r[m]={nodeId:m,state:y,data:g,error:x,timestamp:new Date().toISOString()})}}),a&&n&&!r[n]&&(e.some(d=>{if(d.type==="response.output_item.done"){const f=d.item;return f&&f.type==="executor_action"&&f.executor_id===n}if(d.type==="response.workflow_event.completed"&&"data"in d&&d.data){const f=d.data;return f.executor_id===n&&(f.event_type==="ExecutorCompletedEvent"||f.event_type==="ExecutorFailedEvent"||f.event_type?.includes("Error")||f.event_type?.includes("Failed"))}return!1})||(r[n]={nodeId:n,state:"running",data:void 0,error:void 0,timestamp:new Date().toISOString()})),r}function QR(e,n,r=!0){return e.map(a=>{const l=n[a.id];return l?{...a,data:{...a.data,state:l.state,outputData:l.data,error:l.error,isStreaming:r,layoutDirection:a.data.layoutDirection}}:a})}function JR(e){const n={};return e.forEach(a=>{if(a.type==="response.output_item.added"||a.type==="response.output_item.done"){const l=a.item;if(l&&l.type==="executor_action"&&l.executor_id){const c=l.executor_id;n[c]={lastEvent:a.type==="response.output_item.added"?"ExecutorInvokedEvent":"ExecutorCompletedEvent",timestamp:new Date().toISOString()}}}else if(a.type==="response.workflow_event.completed"&&"data"in a&&a.data){const c=a.data,d=c.executor_id,f=c.event_type;d&&(f==="ExecutorInvokedEvent"||f==="ExecutorCompletedEvent")&&(n[d]={lastEvent:f,timestamp:new Date().toISOString()})}}),Object.entries(n).filter(([,a])=>a.lastEvent==="ExecutorInvokedEvent").map(([a])=>a)}function eD(e,n){const r=JR(n),a={};return n.forEach(l=>{if(l.type==="response.workflow_event.completed"&&"data"in l&&l.data){const d=l.data,f=d.executor_id,m=d.event_type;f&&m&&(a[f]||(a[f]={completed:!1,invoked:!1}),m==="ExecutorInvokedEvent"?a[f].invoked=!0:m==="ExecutorCompletedEvent"&&(a[f].completed=!0))}}),e.map(l=>{const c=a[l.source],d=a[l.target],f=r.includes(l.target);let m={...l.style},h=!1;return c?.completed&&f?(m={stroke:"#643FB2",strokeWidth:3,strokeDasharray:"5,5"},h=!0):c?.completed&&d?.completed?m={stroke:"#10b981",strokeWidth:2}:c?.completed&&d?.invoked?m={stroke:"#f59e0b",strokeWidth:2}:m={stroke:"#6b7280",strokeWidth:2},{...l,style:m,animated:h}})}function yh(e){const n=new Map,r=new Set;return e.forEach(a=>{const l=`${a.source}-${a.target}`,c=`${a.target}-${a.source}`;if(a.source===a.target){n.set(l,a);return}if(n.has(c)){r.add(c),r.add(l);const d=n.get(c);n.set(c,{...d,markerStart:{type:"arrow",width:20,height:20},markerEnd:{type:"arrow",width:20,height:20},data:{...d.data,isBidirectional:!0}})}else r.has(l)||n.set(l,a)}),Array.from(n.values())}function _2({inputSchema:e,onRun:n,onCancel:r,isSubmitting:a,isCancelling:l=!1,workflowState:c,checkpoints:d=[],showCheckpoints:f=!0}){const[m,h]=w.useState(!1);w.useEffect(()=>{const R=D=>{D.key==="Escape"&&m&&h(!1)};if(m)return document.addEventListener("keydown",R),()=>document.removeEventListener("keydown",R)},[m]);const g=w.useMemo(()=>{const R=WR(e);if(!e)return{needsInput:!1,hasDefaults:!1,fieldCount:0,canRunDirectly:!0,isChatMessage:!1};if(e.type==="string")return{needsInput:!e.default,hasDefaults:!!e.default,fieldCount:1,canRunDirectly:!!e.default,isChatMessage:!1};if(e.type==="object"&&e.properties){const D=e.properties,O=Object.entries(D),P=O.filter(([,q])=>q.default!==void 0||q.enum&&q.enum.length>0);return{needsInput:O.length>0,hasDefaults:P.length>0,fieldCount:O.length,canRunDirectly:O.length===0||P.length===O.length,isChatMessage:R}}return{needsInput:!1,hasDefaults:!1,fieldCount:0,canRunDirectly:!0,isChatMessage:!1}},[e]),y=()=>{if(c==="running"&&r)r();else if(g.canRunDirectly){const R={};e?.type==="string"&&e.default?R.input=e.default:e?.type==="object"&&e.properties&&Object.entries(e.properties).forEach(([D,O])=>{O.default!==void 0?R[D]=O.default:O.enum&&O.enum.length>0&&(R[D]=O.enum[0])}),n(R)}else h(!0)},x=R=>{if(g.canRunDirectly){const D={};e?.type==="string"&&e.default?D.input=e.default:e?.type==="object"&&e.properties&&Object.entries(e.properties).forEach(([O,P])=>{P.default!==void 0?D[O]=P.default:P.enum&&P.enum.length>0&&(D[O]=P.enum[0])}),n(D,R)}else h(!0)},b=f&&d.length>0,S=R=>{if(!R)return"";const D=R/1024;return D<1?`${R} B`:D<1024?`${D.toFixed(1)} KB`:`${(D/1024).toFixed(1)} MB`},N=()=>{const R=l?o.jsx(Cr,{className:"w-4 h-4 animate-spin"}):c==="running"&&r?o.jsx(cd,{className:"w-4 h-4 fill-current"}):c==="running"?o.jsx(Cr,{className:"w-4 h-4 animate-spin"}):c==="error"?o.jsx(Yp,{className:"w-4 h-4"}):g.needsInput&&!g.canRunDirectly?o.jsx(Fh,{className:"w-4 h-4"}):o.jsx(Hv,{className:"w-4 h-4"}),D=l?"Stopping...":c==="running"&&r?"Stop":c==="running"?"Running...":c==="completed"?"Run Again":c==="error"?"Retry":g.fieldCount===0||g.canRunDirectly?"Run Workflow":"Configure & Run";return{icon:R,text:D}},{icon:j,text:_}=N(),M=c==="running"&&!r||l,E=c==="error"?"destructive":"default",T=()=>b||g.needsInput?o.jsxs(ud,{children:[o.jsxs("div",{className:"flex w-full",children:[o.jsxs(Te,{onClick:y,disabled:M,variant:E,className:"gap-2 rounded-r-none flex-1",title:c==="running"&&r?"Stop workflow execution":void 0,children:[j,_]}),o.jsx(dd,{asChild:!0,children:o.jsx(Te,{disabled:M,variant:E,className:"rounded-l-none border-l-0 px-2",title:"More options",children:o.jsx(Gt,{className:"w-4 h-4"})})})]}),o.jsxs(fd,{align:"end",className:"w-80 max-h-[400px] overflow-y-auto",children:[b&&o.jsxs(Lt,{onClick:y,children:[o.jsx(Hv,{className:"w-4 h-4 mr-2"}),"Run Fresh"]}),g.needsInput&&o.jsxs(Lt,{onClick:()=>h(!0),children:[o.jsx(Fh,{className:"w-4 h-4 mr-2"}),"Configure Inputs"]}),b&&o.jsxs(o.Fragment,{children:[o.jsx(ma,{}),o.jsx("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:"Resume from checkpoint"}),d.map((D,O)=>o.jsxs(Lt,{onClick:()=>x(D.checkpoint_id),className:"flex flex-col items-start py-2",children:[o.jsxs("div",{className:"flex items-center gap-2 w-full",children:[o.jsx(Fp,{className:"w-4 h-4 flex-shrink-0"}),o.jsx("span",{className:"font-medium",children:D.metadata.iteration_count===0?"Initial State":`Step ${D.metadata.iteration_count}`}),O===0&&o.jsx(ht,{variant:"secondary",className:"text-[10px] h-4 px-1 ml-auto",children:"Latest"})]}),o.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground ml-6 mt-0.5",children:[o.jsx(Up,{className:"w-3 h-3"}),o.jsx("span",{children:new Date(D.timestamp).toLocaleTimeString()}),D.metadata.size_bytes&&o.jsxs(o.Fragment,{children:[o.jsx("span",{children:"•"}),o.jsx("span",{children:S(D.metadata.size_bytes)})]})]})]},D.checkpoint_id))]})]})]}):o.jsxs(Te,{onClick:y,disabled:M,variant:E,className:"gap-2 w-full",title:c==="running"&&r?"Stop workflow execution":void 0,children:[j,_]});return o.jsxs(o.Fragment,{children:[T(),e&&o.jsx(kr,{open:m,onOpenChange:h,children:o.jsxs(Ar,{className:"w-full min-w-[400px] max-w-md sm:max-w-lg md:max-w-2xl lg:max-w-4xl xl:max-w-5xl max-h-[90vh] flex flex-col",children:[o.jsxs(Mr,{className:"px-8 pt-6",children:[o.jsx(Tr,{children:"Configure Workflow Inputs"}),o.jsx(bo,{onClose:()=>h(!1)})]}),o.jsx("div",{className:"px-8 py-4 border-b flex-shrink-0",children:o.jsx("div",{className:"text-sm text-muted-foreground",children:o.jsxs("div",{className:"flex items-center gap-3",children:[o.jsx("span",{className:"font-medium",children:"Input Type:"}),o.jsx(ht,{variant:"secondary",children:g.isChatMessage?"Chat Message":e.type==="string"?"Simple Text":"Structured Data"})]})})}),o.jsx("div",{className:"flex-1 overflow-y-auto py-4 px-8",children:g.isChatMessage?o.jsx(PN,{onSubmit:async R=>{n([{type:"message",role:"user",content:R}]),h(!1)},isSubmitting:a,placeholder:"Enter your message...",entityName:"workflow",showFileUpload:!0}):o.jsx(GR,{inputSchema:e,inputTypeName:"Input",onSubmit:R=>{n(R),h(!1)},isSubmitting:a,className:"embedded"})})]})})]})}function Dt(e){if(typeof e=="string"||typeof e=="number")return""+e;let n="";if(Array.isArray(e))for(let r=0,a;r{}};function Ed(){for(var e=0,n=arguments.length,r={},a;e=0&&(a=r.slice(l+1),r=r.slice(0,l)),r&&!n.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:a}})}vu.prototype=Ed.prototype={constructor:vu,on:function(e,n){var r=this._,a=nD(e+"",r),l,c=-1,d=a.length;if(arguments.length<2){for(;++c0)for(var r=new Array(l),a=0,l,c;a=0&&(n=e.slice(0,r))!=="xmlns"&&(e=e.slice(r+1)),Zv.hasOwnProperty(n)?{space:Zv[n],local:e}:e}function rD(e){return function(){var n=this.ownerDocument,r=this.namespaceURI;return r===rp&&n.documentElement.namespaceURI===rp?n.createElement(e):n.createElementNS(r,e)}}function oD(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function E2(e){var n=Cd(e);return(n.local?oD:rD)(n)}function aD(){}function ag(e){return e==null?aD:function(){return this.querySelector(e)}}function iD(e){typeof e!="function"&&(e=ag(e));for(var n=this._groups,r=n.length,a=new Array(r),l=0;l=E&&(E=M+1);!(R=j[E])&&++E=0;)(d=a[l])&&(c&&d.compareDocumentPosition(c)^4&&c.parentNode.insertBefore(d,c),c=d);return this}function RD(e){e||(e=DD);function n(y,x){return y&&x?e(y.__data__,x.__data__):!y-!x}for(var r=this._groups,a=r.length,l=new Array(a),c=0;cn?1:e>=n?0:NaN}function OD(){var e=arguments[0];return arguments[0]=this,e.apply(null,arguments),this}function zD(){return Array.from(this)}function ID(){for(var e=this._groups,n=0,r=e.length;n1?this.each((n==null?GD:typeof n=="function"?ZD:XD)(e,n,r??"")):Sa(this.node(),e)}function Sa(e,n){return e.style.getPropertyValue(n)||T2(e).getComputedStyle(e,null).getPropertyValue(n)}function KD(e){return function(){delete this[e]}}function QD(e,n){return function(){this[e]=n}}function JD(e,n){return function(){var r=n.apply(this,arguments);r==null?delete this[e]:this[e]=r}}function e6(e,n){return arguments.length>1?this.each((n==null?KD:typeof n=="function"?JD:QD)(e,n)):this.node()[e]}function R2(e){return e.trim().split(/^|\s+/)}function ig(e){return e.classList||new D2(e)}function D2(e){this._node=e,this._names=R2(e.getAttribute("class")||"")}D2.prototype={add:function(e){var n=this._names.indexOf(e);n<0&&(this._names.push(e),this._node.setAttribute("class",this._names.join(" ")))},remove:function(e){var n=this._names.indexOf(e);n>=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};function O2(e,n){for(var r=ig(e),a=-1,l=n.length;++a=0&&(r=n.slice(a+1),n=n.slice(0,a)),{type:n,name:r}})}function k6(e){return function(){var n=this.__on;if(n){for(var r=0,a=-1,l=n.length,c;r()=>e;function op(e,{sourceEvent:n,subject:r,target:a,identifier:l,active:c,x:d,y:f,dx:m,dy:h,dispatch:g}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},subject:{value:r,enumerable:!0,configurable:!0},target:{value:a,enumerable:!0,configurable:!0},identifier:{value:l,enumerable:!0,configurable:!0},active:{value:c,enumerable:!0,configurable:!0},x:{value:d,enumerable:!0,configurable:!0},y:{value:f,enumerable:!0,configurable:!0},dx:{value:m,enumerable:!0,configurable:!0},dy:{value:h,enumerable:!0,configurable:!0},_:{value:g}})}op.prototype.on=function(){var e=this._.on.apply(this._,arguments);return e===this._?this:e};function H6(e){return!e.ctrlKey&&!e.button}function $6(){return this.parentNode}function P6(e,n){return n??{x:e.x,y:e.y}}function B6(){return navigator.maxTouchPoints||"ontouchstart"in this}function P2(){var e=H6,n=$6,r=P6,a=B6,l={},c=Ed("start","drag","end"),d=0,f,m,h,g,y=0;function x(T){T.on("mousedown.drag",b).filter(a).on("touchstart.drag",j).on("touchmove.drag",_,L6).on("touchend.drag touchcancel.drag",M).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function b(T,R){if(!(g||!e.call(this,T,R))){var D=E(this,n.call(this,T,R),T,R,"mouse");D&&(vn(T.view).on("mousemove.drag",S,ol).on("mouseup.drag",N,ol),H2(T.view),vh(T),h=!1,f=T.clientX,m=T.clientY,D("start",T))}}function S(T){if(pa(T),!h){var R=T.clientX-f,D=T.clientY-m;h=R*R+D*D>y}l.mouse("drag",T)}function N(T){vn(T.view).on("mousemove.drag mouseup.drag",null),$2(T.view,h),pa(T),l.mouse("end",T)}function j(T,R){if(e.call(this,T,R)){var D=T.changedTouches,O=n.call(this,T,R),P=D.length,q,Q;for(q=0;q>8&15|n>>4&240,n>>4&15|n&240,(n&15)<<4|n&15,1):r===8?nu(n>>24&255,n>>16&255,n>>8&255,(n&255)/255):r===4?nu(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|n&240,((n&15)<<4|n&15)/255):null):(n=V6.exec(e))?new ln(n[1],n[2],n[3],1):(n=q6.exec(e))?new ln(n[1]*255/100,n[2]*255/100,n[3]*255/100,1):(n=F6.exec(e))?nu(n[1],n[2],n[3],n[4]):(n=Y6.exec(e))?nu(n[1]*255/100,n[2]*255/100,n[3]*255/100,n[4]):(n=G6.exec(e))?nb(n[1],n[2]/100,n[3]/100,1):(n=X6.exec(e))?nb(n[1],n[2]/100,n[3]/100,n[4]):Wv.hasOwnProperty(e)?Jv(Wv[e]):e==="transparent"?new ln(NaN,NaN,NaN,0):null}function Jv(e){return new ln(e>>16&255,e>>8&255,e&255,1)}function nu(e,n,r,a){return a<=0&&(e=n=r=NaN),new ln(e,n,r,a)}function K6(e){return e instanceof jl||(e=ho(e)),e?(e=e.rgb(),new ln(e.r,e.g,e.b,e.opacity)):new ln}function ap(e,n,r,a){return arguments.length===1?K6(e):new ln(e,n,r,a??1)}function ln(e,n,r,a){this.r=+e,this.g=+n,this.b=+r,this.opacity=+a}lg(ln,ap,B2(jl,{brighter(e){return e=e==null?Bu:Math.pow(Bu,e),new ln(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?al:Math.pow(al,e),new ln(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new ln(oo(this.r),oo(this.g),oo(this.b),Uu(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:eb,formatHex:eb,formatHex8:Q6,formatRgb:tb,toString:tb}));function eb(){return`#${so(this.r)}${so(this.g)}${so(this.b)}`}function Q6(){return`#${so(this.r)}${so(this.g)}${so(this.b)}${so((isNaN(this.opacity)?1:this.opacity)*255)}`}function tb(){const e=Uu(this.opacity);return`${e===1?"rgb(":"rgba("}${oo(this.r)}, ${oo(this.g)}, ${oo(this.b)}${e===1?")":`, ${e})`}`}function Uu(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function oo(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function so(e){return e=oo(e),(e<16?"0":"")+e.toString(16)}function nb(e,n,r,a){return a<=0?e=n=r=NaN:r<=0||r>=1?e=n=NaN:n<=0&&(e=NaN),new Pn(e,n,r,a)}function U2(e){if(e instanceof Pn)return new Pn(e.h,e.s,e.l,e.opacity);if(e instanceof jl||(e=ho(e)),!e)return new Pn;if(e instanceof Pn)return e;e=e.rgb();var n=e.r/255,r=e.g/255,a=e.b/255,l=Math.min(n,r,a),c=Math.max(n,r,a),d=NaN,f=c-l,m=(c+l)/2;return f?(n===c?d=(r-a)/f+(r0&&m<1?0:d,new Pn(d,f,m,e.opacity)}function J6(e,n,r,a){return arguments.length===1?U2(e):new Pn(e,n,r,a??1)}function Pn(e,n,r,a){this.h=+e,this.s=+n,this.l=+r,this.opacity=+a}lg(Pn,J6,B2(jl,{brighter(e){return e=e==null?Bu:Math.pow(Bu,e),new Pn(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?al:Math.pow(al,e),new Pn(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,n=isNaN(e)||isNaN(this.s)?0:this.s,r=this.l,a=r+(r<.5?r:1-r)*n,l=2*r-a;return new ln(bh(e>=240?e-240:e+120,l,a),bh(e,l,a),bh(e<120?e+240:e-120,l,a),this.opacity)},clamp(){return new Pn(sb(this.h),su(this.s),su(this.l),Uu(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=Uu(this.opacity);return`${e===1?"hsl(":"hsla("}${sb(this.h)}, ${su(this.s)*100}%, ${su(this.l)*100}%${e===1?")":`, ${e})`}`}}));function sb(e){return e=(e||0)%360,e<0?e+360:e}function su(e){return Math.max(0,Math.min(1,e||0))}function bh(e,n,r){return(e<60?n+(r-n)*e/60:e<180?r:e<240?n+(r-n)*(240-e)/60:n)*255}const cg=e=>()=>e;function eO(e,n){return function(r){return e+r*n}}function tO(e,n,r){return e=Math.pow(e,r),n=Math.pow(n,r)-e,r=1/r,function(a){return Math.pow(e+a*n,r)}}function nO(e){return(e=+e)==1?V2:function(n,r){return r-n?tO(n,r,e):cg(isNaN(n)?r:n)}}function V2(e,n){var r=n-e;return r?eO(e,r):cg(isNaN(e)?n:e)}const Vu=(function e(n){var r=nO(n);function a(l,c){var d=r((l=ap(l)).r,(c=ap(c)).r),f=r(l.g,c.g),m=r(l.b,c.b),h=V2(l.opacity,c.opacity);return function(g){return l.r=d(g),l.g=f(g),l.b=m(g),l.opacity=h(g),l+""}}return a.gamma=e,a})(1);function sO(e,n){n||(n=[]);var r=e?Math.min(n.length,e.length):0,a=n.slice(),l;return function(c){for(l=0;lr&&(c=n.slice(r,c),f[d]?f[d]+=c:f[++d]=c),(a=a[0])===(l=l[0])?f[d]?f[d]+=l:f[++d]=l:(f[++d]=null,m.push({i:d,x:es(a,l)})),r=wh.lastIndex;return r180?g+=360:g-h>180&&(h+=360),x.push({i:y.push(l(y)+"rotate(",null,a)-2,x:es(h,g)})):g&&y.push(l(y)+"rotate("+g+a)}function f(h,g,y,x){h!==g?x.push({i:y.push(l(y)+"skewX(",null,a)-2,x:es(h,g)}):g&&y.push(l(y)+"skewX("+g+a)}function m(h,g,y,x,b,S){if(h!==y||g!==x){var N=b.push(l(b)+"scale(",null,",",null,")");S.push({i:N-4,x:es(h,y)},{i:N-2,x:es(g,x)})}else(y!==1||x!==1)&&b.push(l(b)+"scale("+y+","+x+")")}return function(h,g){var y=[],x=[];return h=e(h),g=e(g),c(h.translateX,h.translateY,g.translateX,g.translateY,y,x),d(h.rotate,g.rotate,y,x),f(h.skewX,g.skewX,y,x),m(h.scaleX,h.scaleY,g.scaleX,g.scaleY,y,x),h=g=null,function(b){for(var S=-1,N=x.length,j;++S=0&&e._call.call(void 0,n),e=e._next;--ja}function ab(){po=(Fu=ll.now())+kd,ja=Gi=0;try{yO()}finally{ja=0,bO(),po=0}}function vO(){var e=ll.now(),n=e-Fu;n>G2&&(kd-=n,Fu=e)}function bO(){for(var e,n=qu,r,a=1/0;n;)n._call?(a>n._time&&(a=n._time),e=n,n=n._next):(r=n._next,n._next=null,n=e?e._next=r:qu=r);Xi=e,cp(a)}function cp(e){if(!ja){Gi&&(Gi=clearTimeout(Gi));var n=e-po;n>24?(e<1/0&&(Gi=setTimeout(ab,e-ll.now()-kd)),Vi&&(Vi=clearInterval(Vi))):(Vi||(Fu=ll.now(),Vi=setInterval(vO,G2)),ja=1,X2(ab))}}function ib(e,n,r){var a=new Yu;return n=n==null?0:+n,a.restart(l=>{a.stop(),e(l+n)},n,r),a}var wO=Ed("start","end","cancel","interrupt"),NO=[],W2=0,lb=1,up=2,wu=3,cb=4,dp=5,Nu=6;function Ad(e,n,r,a,l,c){var d=e.__transition;if(!d)e.__transition={};else if(r in d)return;SO(e,r,{name:n,index:a,group:l,on:wO,tween:NO,time:c.time,delay:c.delay,duration:c.duration,ease:c.ease,timer:null,state:W2})}function dg(e,n){var r=Yn(e,n);if(r.state>W2)throw new Error("too late; already scheduled");return r}function ms(e,n){var r=Yn(e,n);if(r.state>wu)throw new Error("too late; already running");return r}function Yn(e,n){var r=e.__transition;if(!r||!(r=r[n]))throw new Error("transition not found");return r}function SO(e,n,r){var a=e.__transition,l;a[n]=r,r.timer=Z2(c,0,r.time);function c(h){r.state=lb,r.timer.restart(d,r.delay,r.time),r.delay<=h&&d(h-r.delay)}function d(h){var g,y,x,b;if(r.state!==lb)return m();for(g in a)if(b=a[g],b.name===r.name){if(b.state===wu)return ib(d);b.state===cb?(b.state=Nu,b.timer.stop(),b.on.call("interrupt",e,e.__data__,b.index,b.group),delete a[g]):+gup&&a.state=0&&(n=n.slice(0,r)),!n||n==="start"})}function JO(e,n,r){var a,l,c=QO(n)?dg:ms;return function(){var d=c(this,e),f=d.on;f!==a&&(l=(a=f).copy()).on(n,r),d.on=l}}function e8(e,n){var r=this._id;return arguments.length<2?Yn(this.node(),r).on.on(e):this.each(JO(r,e,n))}function t8(e){return function(){var n=this.parentNode;for(var r in this.__transition)if(+r!==e)return;n&&n.removeChild(this)}}function n8(){return this.on("end.remove",t8(this._id))}function s8(e){var n=this._name,r=this._id;typeof e!="function"&&(e=ag(e));for(var a=this._groups,l=a.length,c=new Array(l),d=0;d()=>e;function k8(e,{sourceEvent:n,target:r,transform:a,dispatch:l}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},transform:{value:a,enumerable:!0,configurable:!0},_:{value:l}})}function Ls(e,n,r){this.k=e,this.x=n,this.y=r}Ls.prototype={constructor:Ls,scale:function(e){return e===1?this:new Ls(this.k*e,this.x,this.y)},translate:function(e,n){return e===0&n===0?this:new Ls(this.k,this.x+this.k*e,this.y+this.k*n)},apply:function(e){return[e[0]*this.k+this.x,e[1]*this.k+this.y]},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return[(e[0]-this.x)/this.k,(e[1]-this.y)/this.k]},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Md=new Ls(1,0,0);eS.prototype=Ls.prototype;function eS(e){for(;!e.__zoom;)if(!(e=e.parentNode))return Md;return e.__zoom}function Nh(e){e.stopImmediatePropagation()}function qi(e){e.preventDefault(),e.stopImmediatePropagation()}function A8(e){return(!e.ctrlKey||e.type==="wheel")&&!e.button}function M8(){var e=this;return e instanceof SVGElement?(e=e.ownerSVGElement||e,e.hasAttribute("viewBox")?(e=e.viewBox.baseVal,[[e.x,e.y],[e.x+e.width,e.y+e.height]]):[[0,0],[e.width.baseVal.value,e.height.baseVal.value]]):[[0,0],[e.clientWidth,e.clientHeight]]}function ub(){return this.__zoom||Md}function T8(e){return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*(e.ctrlKey?10:1)}function R8(){return navigator.maxTouchPoints||"ontouchstart"in this}function D8(e,n,r){var a=e.invertX(n[0][0])-r[0][0],l=e.invertX(n[1][0])-r[1][0],c=e.invertY(n[0][1])-r[0][1],d=e.invertY(n[1][1])-r[1][1];return e.translate(l>a?(a+l)/2:Math.min(0,a)||Math.max(0,l),d>c?(c+d)/2:Math.min(0,c)||Math.max(0,d))}function tS(){var e=A8,n=M8,r=D8,a=T8,l=R8,c=[0,1/0],d=[[-1/0,-1/0],[1/0,1/0]],f=250,m=bu,h=Ed("start","zoom","end"),g,y,x,b=500,S=150,N=0,j=10;function _(B){B.property("__zoom",ub).on("wheel.zoom",P,{passive:!1}).on("mousedown.zoom",q).on("dblclick.zoom",Q).filter(l).on("touchstart.zoom",ee).on("touchmove.zoom",G).on("touchend.zoom touchcancel.zoom",W).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}_.transform=function(B,U,k,L){var I=B.selection?B.selection():B;I.property("__zoom",ub),B!==I?R(B,U,k,L):I.interrupt().each(function(){D(this,arguments).event(L).start().zoom(null,typeof U=="function"?U.apply(this,arguments):U).end()})},_.scaleBy=function(B,U,k,L){_.scaleTo(B,function(){var I=this.__zoom.k,H=typeof U=="function"?U.apply(this,arguments):U;return I*H},k,L)},_.scaleTo=function(B,U,k,L){_.transform(B,function(){var I=n.apply(this,arguments),H=this.__zoom,C=k==null?T(I):typeof k=="function"?k.apply(this,arguments):k,$=H.invert(C),Y=typeof U=="function"?U.apply(this,arguments):U;return r(E(M(H,Y),C,$),I,d)},k,L)},_.translateBy=function(B,U,k,L){_.transform(B,function(){return r(this.__zoom.translate(typeof U=="function"?U.apply(this,arguments):U,typeof k=="function"?k.apply(this,arguments):k),n.apply(this,arguments),d)},null,L)},_.translateTo=function(B,U,k,L,I){_.transform(B,function(){var H=n.apply(this,arguments),C=this.__zoom,$=L==null?T(H):typeof L=="function"?L.apply(this,arguments):L;return r(Md.translate($[0],$[1]).scale(C.k).translate(typeof U=="function"?-U.apply(this,arguments):-U,typeof k=="function"?-k.apply(this,arguments):-k),H,d)},L,I)};function M(B,U){return U=Math.max(c[0],Math.min(c[1],U)),U===B.k?B:new Ls(U,B.x,B.y)}function E(B,U,k){var L=U[0]-k[0]*B.k,I=U[1]-k[1]*B.k;return L===B.x&&I===B.y?B:new Ls(B.k,L,I)}function T(B){return[(+B[0][0]+ +B[1][0])/2,(+B[0][1]+ +B[1][1])/2]}function R(B,U,k,L){B.on("start.zoom",function(){D(this,arguments).event(L).start()}).on("interrupt.zoom end.zoom",function(){D(this,arguments).event(L).end()}).tween("zoom",function(){var I=this,H=arguments,C=D(I,H).event(L),$=n.apply(I,H),Y=k==null?T($):typeof k=="function"?k.apply(I,H):k,V=Math.max($[1][0]-$[0][0],$[1][1]-$[0][1]),K=I.__zoom,fe=typeof U=="function"?U.apply(I,H):U,ue=m(K.invert(Y).concat(V/K.k),fe.invert(Y).concat(V/fe.k));return function(te){if(te===1)te=fe;else{var ie=ue(te),xe=V/ie[2];te=new Ls(xe,Y[0]-ie[0]*xe,Y[1]-ie[1]*xe)}C.zoom(null,te)}})}function D(B,U,k){return!k&&B.__zooming||new O(B,U)}function O(B,U){this.that=B,this.args=U,this.active=0,this.sourceEvent=null,this.extent=n.apply(B,U),this.taps=0}O.prototype={event:function(B){return B&&(this.sourceEvent=B),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(B,U){return this.mouse&&B!=="mouse"&&(this.mouse[1]=U.invert(this.mouse[0])),this.touch0&&B!=="touch"&&(this.touch0[1]=U.invert(this.touch0[0])),this.touch1&&B!=="touch"&&(this.touch1[1]=U.invert(this.touch1[0])),this.that.__zoom=U,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(B){var U=vn(this.that).datum();h.call(B,this.that,new k8(B,{sourceEvent:this.sourceEvent,target:_,transform:this.that.__zoom,dispatch:h}),U)}};function P(B,...U){if(!e.apply(this,arguments))return;var k=D(this,U).event(B),L=this.__zoom,I=Math.max(c[0],Math.min(c[1],L.k*Math.pow(2,a.apply(this,arguments)))),H=$n(B);if(k.wheel)(k.mouse[0][0]!==H[0]||k.mouse[0][1]!==H[1])&&(k.mouse[1]=L.invert(k.mouse[0]=H)),clearTimeout(k.wheel);else{if(L.k===I)return;k.mouse=[H,L.invert(H)],Su(this),k.start()}qi(B),k.wheel=setTimeout(C,S),k.zoom("mouse",r(E(M(L,I),k.mouse[0],k.mouse[1]),k.extent,d));function C(){k.wheel=null,k.end()}}function q(B,...U){if(x||!e.apply(this,arguments))return;var k=B.currentTarget,L=D(this,U,!0).event(B),I=vn(B.view).on("mousemove.zoom",Y,!0).on("mouseup.zoom",V,!0),H=$n(B,k),C=B.clientX,$=B.clientY;H2(B.view),Nh(B),L.mouse=[H,this.__zoom.invert(H)],Su(this),L.start();function Y(K){if(qi(K),!L.moved){var fe=K.clientX-C,ue=K.clientY-$;L.moved=fe*fe+ue*ue>N}L.event(K).zoom("mouse",r(E(L.that.__zoom,L.mouse[0]=$n(K,k),L.mouse[1]),L.extent,d))}function V(K){I.on("mousemove.zoom mouseup.zoom",null),$2(K.view,L.moved),qi(K),L.event(K).end()}}function Q(B,...U){if(e.apply(this,arguments)){var k=this.__zoom,L=$n(B.changedTouches?B.changedTouches[0]:B,this),I=k.invert(L),H=k.k*(B.shiftKey?.5:2),C=r(E(M(k,H),L,I),n.apply(this,U),d);qi(B),f>0?vn(this).transition().duration(f).call(R,C,L,B):vn(this).call(_.transform,C,L,B)}}function ee(B,...U){if(e.apply(this,arguments)){var k=B.touches,L=k.length,I=D(this,U,B.changedTouches.length===L).event(B),H,C,$,Y;for(Nh(B),C=0;C"[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001",error002:()=>"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",error003:e=>`Node type "${e}" not found. Using fallback type "default".`,error004:()=>"The React Flow parent container needs a width and a height to render the graph.",error005:()=>"Only child nodes can use a parent extent.",error006:()=>"Can't create edge. An edge needs a source and a target.",error007:e=>`The old edge with id=${e} does not exist.`,error009:e=>`Marker type "${e}" doesn't exist.`,error008:(e,{id:n,sourceHandle:r,targetHandle:a})=>`Couldn't create edge for ${e} handle id: "${e==="source"?r:a}", edge id: ${n}.`,error010:()=>"Handle: No node id found. Make sure to only use a Handle inside a custom Node.",error011:e=>`Edge type "${e}" not found. Using fallback type "default".`,error012:e=>`Node with id "${e}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,error013:(e="react")=>`It seems that you haven't loaded the styles. Please import '@xyflow/${e}/dist/style.css' or base.css to make sure everything is working properly.`,error014:()=>"useNodeConnections: No node ID found. Call useNodeConnections inside a custom Node or provide a node ID.",error015:()=>"It seems that you are trying to drag a node that is not initialized. Please use onNodesChange as explained in the docs."},cl=[[Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY],[Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY]],nS=["Enter"," ","Escape"],sS={"node.a11yDescription.default":"Press enter or space to select a node. Press delete to remove it and escape to cancel.","node.a11yDescription.keyboardDisabled":"Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.","node.a11yDescription.ariaLiveMessage":({direction:e,x:n,y:r})=>`Moved selected node ${e}. New position, x: ${n}, y: ${r}`,"edge.a11yDescription.default":"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.","controls.ariaLabel":"Control Panel","controls.zoomIn.ariaLabel":"Zoom In","controls.zoomOut.ariaLabel":"Zoom Out","controls.fitView.ariaLabel":"Fit View","controls.interactive.ariaLabel":"Toggle Interactivity","minimap.ariaLabel":"Mini Map","handle.ariaLabel":"Handle"};var _a;(function(e){e.Strict="strict",e.Loose="loose"})(_a||(_a={}));var ao;(function(e){e.Free="free",e.Vertical="vertical",e.Horizontal="horizontal"})(ao||(ao={}));var ul;(function(e){e.Partial="partial",e.Full="full"})(ul||(ul={}));const rS={inProgress:!1,isValid:null,from:null,fromHandle:null,fromPosition:null,fromNode:null,to:null,toHandle:null,toPosition:null,toNode:null};var Nr;(function(e){e.Bezier="default",e.Straight="straight",e.Step="step",e.SmoothStep="smoothstep",e.SimpleBezier="simplebezier"})(Nr||(Nr={}));var Gu;(function(e){e.Arrow="arrow",e.ArrowClosed="arrowclosed"})(Gu||(Gu={}));var ze;(function(e){e.Left="left",e.Top="top",e.Right="right",e.Bottom="bottom"})(ze||(ze={}));const db={[ze.Left]:ze.Right,[ze.Right]:ze.Left,[ze.Top]:ze.Bottom,[ze.Bottom]:ze.Top};function oS(e){return e===null?null:e?"valid":"invalid"}const aS=e=>"id"in e&&"source"in e&&"target"in e,O8=e=>"id"in e&&"position"in e&&!("source"in e)&&!("target"in e),mg=e=>"id"in e&&"internals"in e&&!("source"in e)&&!("target"in e),_l=(e,n=[0,0])=>{const{width:r,height:a}=Vs(e),l=e.origin??n,c=r*l[0],d=a*l[1];return{x:e.position.x-c,y:e.position.y-d}},z8=(e,n={nodeOrigin:[0,0]})=>{if(e.length===0)return{x:0,y:0,width:0,height:0};const r=e.reduce((a,l)=>{const c=typeof l=="string";let d=!n.nodeLookup&&!c?l:void 0;n.nodeLookup&&(d=c?n.nodeLookup.get(l):mg(l)?l:n.nodeLookup.get(l.id));const f=d?Xu(d,n.nodeOrigin):{x:0,y:0,x2:0,y2:0};return Td(a,f)},{x:1/0,y:1/0,x2:-1/0,y2:-1/0});return Rd(r)},El=(e,n={})=>{if(e.size===0)return{x:0,y:0,width:0,height:0};let r={x:1/0,y:1/0,x2:-1/0,y2:-1/0};return e.forEach(a=>{if(n.filter===void 0||n.filter(a)){const l=Xu(a);r=Td(r,l)}}),Rd(r)},hg=(e,n,[r,a,l]=[0,0,1],c=!1,d=!1)=>{const f={...kl(n,[r,a,l]),width:n.width/l,height:n.height/l},m=[];for(const h of e.values()){const{measured:g,selectable:y=!0,hidden:x=!1}=h;if(d&&!y||x)continue;const b=g.width??h.width??h.initialWidth??null,S=g.height??h.height??h.initialHeight??null,N=dl(f,Ca(h)),j=(b??0)*(S??0),_=c&&N>0;(!h.internals.handleBounds||_||N>=j||h.dragging)&&m.push(h)}return m},I8=(e,n)=>{const r=new Set;return e.forEach(a=>{r.add(a.id)}),n.filter(a=>r.has(a.source)||r.has(a.target))};function L8(e,n){const r=new Map,a=n?.nodes?new Set(n.nodes.map(l=>l.id)):null;return e.forEach(l=>{l.measured.width&&l.measured.height&&(n?.includeHiddenNodes||!l.hidden)&&(!a||a.has(l.id))&&r.set(l.id,l)}),r}async function H8({nodes:e,width:n,height:r,panZoom:a,minZoom:l,maxZoom:c},d){if(e.size===0)return Promise.resolve(!0);const f=L8(e,d),m=El(f),h=pg(m,n,r,d?.minZoom??l,d?.maxZoom??c,d?.padding??.1);return await a.setViewport(h,{duration:d?.duration,ease:d?.ease,interpolate:d?.interpolate}),Promise.resolve(!0)}function iS({nodeId:e,nextPosition:n,nodeLookup:r,nodeOrigin:a=[0,0],nodeExtent:l,onError:c}){const d=r.get(e),f=d.parentId?r.get(d.parentId):void 0,{x:m,y:h}=f?f.internals.positionAbsolute:{x:0,y:0},g=d.origin??a;let y=d.extent||l;if(d.extent==="parent"&&!d.expandParent)if(!f)c?.("005",cs.error005());else{const b=f.measured.width,S=f.measured.height;b&&S&&(y=[[m,h],[m+b,h+S]])}else f&&ka(d.extent)&&(y=[[d.extent[0][0]+m,d.extent[0][1]+h],[d.extent[1][0]+m,d.extent[1][1]+h]]);const x=ka(y)?go(n,y,d.measured):n;return(d.measured.width===void 0||d.measured.height===void 0)&&c?.("015",cs.error015()),{position:{x:x.x-m+(d.measured.width??0)*g[0],y:x.y-h+(d.measured.height??0)*g[1]},positionAbsolute:x}}async function $8({nodesToRemove:e=[],edgesToRemove:n=[],nodes:r,edges:a,onBeforeDelete:l}){const c=new Set(e.map(x=>x.id)),d=[];for(const x of r){if(x.deletable===!1)continue;const b=c.has(x.id),S=!b&&x.parentId&&d.find(N=>N.id===x.parentId);(b||S)&&d.push(x)}const f=new Set(n.map(x=>x.id)),m=a.filter(x=>x.deletable!==!1),g=I8(d,m);for(const x of m)f.has(x.id)&&!g.find(S=>S.id===x.id)&&g.push(x);if(!l)return{edges:g,nodes:d};const y=await l({nodes:d,edges:g});return typeof y=="boolean"?y?{edges:g,nodes:d}:{edges:[],nodes:[]}:y}const Ea=(e,n=0,r=1)=>Math.min(Math.max(e,n),r),go=(e={x:0,y:0},n,r)=>({x:Ea(e.x,n[0][0],n[1][0]-(r?.width??0)),y:Ea(e.y,n[0][1],n[1][1]-(r?.height??0))});function lS(e,n,r){const{width:a,height:l}=Vs(r),{x:c,y:d}=r.internals.positionAbsolute;return go(e,[[c,d],[c+a,d+l]],n)}const fb=(e,n,r)=>er?-Ea(Math.abs(e-r),1,n)/n:0,cS=(e,n,r=15,a=40)=>{const l=fb(e.x,a,n.width-a)*r,c=fb(e.y,a,n.height-a)*r;return[l,c]},Td=(e,n)=>({x:Math.min(e.x,n.x),y:Math.min(e.y,n.y),x2:Math.max(e.x2,n.x2),y2:Math.max(e.y2,n.y2)}),fp=({x:e,y:n,width:r,height:a})=>({x:e,y:n,x2:e+r,y2:n+a}),Rd=({x:e,y:n,x2:r,y2:a})=>({x:e,y:n,width:r-e,height:a-n}),Ca=(e,n=[0,0])=>{const{x:r,y:a}=mg(e)?e.internals.positionAbsolute:_l(e,n);return{x:r,y:a,width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}},Xu=(e,n=[0,0])=>{const{x:r,y:a}=mg(e)?e.internals.positionAbsolute:_l(e,n);return{x:r,y:a,x2:r+(e.measured?.width??e.width??e.initialWidth??0),y2:a+(e.measured?.height??e.height??e.initialHeight??0)}},uS=(e,n)=>Rd(Td(fp(e),fp(n))),dl=(e,n)=>{const r=Math.max(0,Math.min(e.x+e.width,n.x+n.width)-Math.max(e.x,n.x)),a=Math.max(0,Math.min(e.y+e.height,n.y+n.height)-Math.max(e.y,n.y));return Math.ceil(r*a)},mb=e=>Bn(e.width)&&Bn(e.height)&&Bn(e.x)&&Bn(e.y),Bn=e=>!isNaN(e)&&isFinite(e),P8=(e,n)=>{},Cl=(e,n=[1,1])=>({x:n[0]*Math.round(e.x/n[0]),y:n[1]*Math.round(e.y/n[1])}),kl=({x:e,y:n},[r,a,l],c=!1,d=[1,1])=>{const f={x:(e-r)/l,y:(n-a)/l};return c?Cl(f,d):f},Zu=({x:e,y:n},[r,a,l])=>({x:e*l+r,y:n*l+a});function la(e,n){if(typeof e=="number")return Math.floor((n-n/(1+e))*.5);if(typeof e=="string"&&e.endsWith("px")){const r=parseFloat(e);if(!Number.isNaN(r))return Math.floor(r)}if(typeof e=="string"&&e.endsWith("%")){const r=parseFloat(e);if(!Number.isNaN(r))return Math.floor(n*r*.01)}return console.error(`[React Flow] The padding value "${e}" is invalid. Please provide a number or a string with a valid unit (px or %).`),0}function B8(e,n,r){if(typeof e=="string"||typeof e=="number"){const a=la(e,r),l=la(e,n);return{top:a,right:l,bottom:a,left:l,x:l*2,y:a*2}}if(typeof e=="object"){const a=la(e.top??e.y??0,r),l=la(e.bottom??e.y??0,r),c=la(e.left??e.x??0,n),d=la(e.right??e.x??0,n);return{top:a,right:d,bottom:l,left:c,x:c+d,y:a+l}}return{top:0,right:0,bottom:0,left:0,x:0,y:0}}function U8(e,n,r,a,l,c){const{x:d,y:f}=Zu(e,[n,r,a]),{x:m,y:h}=Zu({x:e.x+e.width,y:e.y+e.height},[n,r,a]),g=l-m,y=c-h;return{left:Math.floor(d),top:Math.floor(f),right:Math.floor(g),bottom:Math.floor(y)}}const pg=(e,n,r,a,l,c)=>{const d=B8(c,n,r),f=(n-d.x)/e.width,m=(r-d.y)/e.height,h=Math.min(f,m),g=Ea(h,a,l),y=e.x+e.width/2,x=e.y+e.height/2,b=n/2-y*g,S=r/2-x*g,N=U8(e,b,S,g,n,r),j={left:Math.min(N.left-d.left,0),top:Math.min(N.top-d.top,0),right:Math.min(N.right-d.right,0),bottom:Math.min(N.bottom-d.bottom,0)};return{x:b-j.left+j.right,y:S-j.top+j.bottom,zoom:g}},fl=()=>typeof navigator<"u"&&navigator?.userAgent?.indexOf("Mac")>=0;function ka(e){return e!=null&&e!=="parent"}function Vs(e){return{width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}}function dS(e){return(e.measured?.width??e.width??e.initialWidth)!==void 0&&(e.measured?.height??e.height??e.initialHeight)!==void 0}function fS(e,n={width:0,height:0},r,a,l){const c={...e},d=a.get(r);if(d){const f=d.origin||l;c.x+=d.internals.positionAbsolute.x-(n.width??0)*f[0],c.y+=d.internals.positionAbsolute.y-(n.height??0)*f[1]}return c}function hb(e,n){if(e.size!==n.size)return!1;for(const r of e)if(!n.has(r))return!1;return!0}function V8(){let e,n;return{promise:new Promise((a,l)=>{e=a,n=l}),resolve:e,reject:n}}function q8(e){return{...sS,...e||{}}}function Qi(e,{snapGrid:n=[0,0],snapToGrid:r=!1,transform:a,containerBounds:l}){const{x:c,y:d}=ss(e),f=kl({x:c-(l?.left??0),y:d-(l?.top??0)},a),{x:m,y:h}=r?Cl(f,n):f;return{xSnapped:m,ySnapped:h,...f}}const gg=e=>({width:e.offsetWidth,height:e.offsetHeight}),mS=e=>e?.getRootNode?.()||window?.document,F8=["INPUT","SELECT","TEXTAREA"];function hS(e){const n=e.composedPath?.()?.[0]||e.target;return n?.nodeType!==1?!1:F8.includes(n.nodeName)||n.hasAttribute("contenteditable")||!!n.closest(".nokey")}const pS=e=>"clientX"in e,ss=(e,n)=>{const r=pS(e),a=r?e.clientX:e.touches?.[0].clientX,l=r?e.clientY:e.touches?.[0].clientY;return{x:a-(n?.left??0),y:l-(n?.top??0)}},pb=(e,n,r,a,l)=>{const c=n.querySelectorAll(`.${e}`);return!c||!c.length?null:Array.from(c).map(d=>{const f=d.getBoundingClientRect();return{id:d.getAttribute("data-handleid"),type:e,nodeId:l,position:d.getAttribute("data-handlepos"),x:(f.left-r.left)/a,y:(f.top-r.top)/a,...gg(d)}})};function gS({sourceX:e,sourceY:n,targetX:r,targetY:a,sourceControlX:l,sourceControlY:c,targetControlX:d,targetControlY:f}){const m=e*.125+l*.375+d*.375+r*.125,h=n*.125+c*.375+f*.375+a*.125,g=Math.abs(m-e),y=Math.abs(h-n);return[m,h,g,y]}function au(e,n){return e>=0?.5*e:n*25*Math.sqrt(-e)}function gb({pos:e,x1:n,y1:r,x2:a,y2:l,c}){switch(e){case ze.Left:return[n-au(n-a,c),r];case ze.Right:return[n+au(a-n,c),r];case ze.Top:return[n,r-au(r-l,c)];case ze.Bottom:return[n,r+au(l-r,c)]}}function xS({sourceX:e,sourceY:n,sourcePosition:r=ze.Bottom,targetX:a,targetY:l,targetPosition:c=ze.Top,curvature:d=.25}){const[f,m]=gb({pos:r,x1:e,y1:n,x2:a,y2:l,c:d}),[h,g]=gb({pos:c,x1:a,y1:l,x2:e,y2:n,c:d}),[y,x,b,S]=gS({sourceX:e,sourceY:n,targetX:a,targetY:l,sourceControlX:f,sourceControlY:m,targetControlX:h,targetControlY:g});return[`M${e},${n} C${f},${m} ${h},${g} ${a},${l}`,y,x,b,S]}function yS({sourceX:e,sourceY:n,targetX:r,targetY:a}){const l=Math.abs(r-e)/2,c=r0}const X8=({source:e,sourceHandle:n,target:r,targetHandle:a})=>`xy-edge__${e}${n||""}-${r}${a||""}`,Z8=(e,n)=>n.some(r=>r.source===e.source&&r.target===e.target&&(r.sourceHandle===e.sourceHandle||!r.sourceHandle&&!e.sourceHandle)&&(r.targetHandle===e.targetHandle||!r.targetHandle&&!e.targetHandle)),W8=(e,n)=>{if(!e.source||!e.target)return n;let r;return aS(e)?r={...e}:r={...e,id:X8(e)},Z8(r,n)?n:(r.sourceHandle===null&&delete r.sourceHandle,r.targetHandle===null&&delete r.targetHandle,n.concat(r))};function vS({sourceX:e,sourceY:n,targetX:r,targetY:a}){const[l,c,d,f]=yS({sourceX:e,sourceY:n,targetX:r,targetY:a});return[`M ${e},${n}L ${r},${a}`,l,c,d,f]}const xb={[ze.Left]:{x:-1,y:0},[ze.Right]:{x:1,y:0},[ze.Top]:{x:0,y:-1},[ze.Bottom]:{x:0,y:1}},K8=({source:e,sourcePosition:n=ze.Bottom,target:r})=>n===ze.Left||n===ze.Right?e.xMath.sqrt(Math.pow(n.x-e.x,2)+Math.pow(n.y-e.y,2));function Q8({source:e,sourcePosition:n=ze.Bottom,target:r,targetPosition:a=ze.Top,center:l,offset:c,stepPosition:d}){const f=xb[n],m=xb[a],h={x:e.x+f.x*c,y:e.y+f.y*c},g={x:r.x+m.x*c,y:r.y+m.y*c},y=K8({source:h,sourcePosition:n,target:g}),x=y.x!==0?"x":"y",b=y[x];let S=[],N,j;const _={x:0,y:0},M={x:0,y:0},[,,E,T]=yS({sourceX:e.x,sourceY:e.y,targetX:r.x,targetY:r.y});if(f[x]*m[x]===-1){x==="x"?(N=l.x??h.x+(g.x-h.x)*d,j=l.y??(h.y+g.y)/2):(N=l.x??(h.x+g.x)/2,j=l.y??h.y+(g.y-h.y)*d);const D=[{x:N,y:h.y},{x:N,y:g.y}],O=[{x:h.x,y:j},{x:g.x,y:j}];f[x]===b?S=x==="x"?D:O:S=x==="x"?O:D}else{const D=[{x:h.x,y:g.y}],O=[{x:g.x,y:h.y}];if(x==="x"?S=f.x===b?O:D:S=f.y===b?D:O,n===a){const G=Math.abs(e[x]-r[x]);if(G<=c){const W=Math.min(c-1,c-G);f[x]===b?_[x]=(h[x]>e[x]?-1:1)*W:M[x]=(g[x]>r[x]?-1:1)*W}}if(n!==a){const G=x==="x"?"y":"x",W=f[x]===m[G],B=h[G]>g[G],U=h[G]=ee?(N=(P.x+q.x)/2,j=S[0].y):(N=S[0].x,j=(P.y+q.y)/2)}return[[e,{x:h.x+_.x,y:h.y+_.y},...S,{x:g.x+M.x,y:g.y+M.y},r],N,j,E,T]}function J8(e,n,r,a){const l=Math.min(yb(e,n)/2,yb(n,r)/2,a),{x:c,y:d}=n;if(e.x===c&&c===r.x||e.y===d&&d===r.y)return`L${c} ${d}`;if(e.y===d){const h=e.x{let T="";return E>0&&Er.id===n):e[0])||null}function hp(e,n){return e?typeof e=="string"?e:`${n?`${n}__`:""}${Object.keys(e).sort().map(a=>`${a}=${e[a]}`).join("&")}`:""}function t9(e,{id:n,defaultColor:r,defaultMarkerStart:a,defaultMarkerEnd:l}){const c=new Set;return e.reduce((d,f)=>([f.markerStart||a,f.markerEnd||l].forEach(m=>{if(m&&typeof m=="object"){const h=hp(m,n);c.has(h)||(d.push({id:h,color:m.color||r,...m}),c.add(h))}}),d),[]).sort((d,f)=>d.id.localeCompare(f.id))}const xg={nodeOrigin:[0,0],nodeExtent:cl,elevateNodesOnSelect:!0,defaults:{}},n9={...xg,checkEquality:!0};function yg(e,n){const r={...e};for(const a in n)n[a]!==void 0&&(r[a]=n[a]);return r}function s9(e,n,r){const a=yg(xg,r);for(const l of e.values())if(l.parentId)vg(l,e,n,a);else{const c=_l(l,a.nodeOrigin),d=ka(l.extent)?l.extent:a.nodeExtent,f=go(c,d,Vs(l));l.internals.positionAbsolute=f}}function pp(e,n,r,a){const l=yg(n9,a);let c=e.length>0;const d=new Map(n),f=l?.elevateNodesOnSelect?1e3:0;n.clear(),r.clear();for(const m of e){let h=d.get(m.id);if(l.checkEquality&&m===h?.internals.userNode)n.set(m.id,h);else{const g=_l(m,l.nodeOrigin),y=ka(m.extent)?m.extent:l.nodeExtent,x=go(g,y,Vs(m));h={...l.defaults,...m,measured:{width:m.measured?.width,height:m.measured?.height},internals:{positionAbsolute:x,handleBounds:m.measured?h?.internals.handleBounds:void 0,z:bS(m,f),userNode:m}},n.set(m.id,h)}(h.measured===void 0||h.measured.width===void 0||h.measured.height===void 0)&&!h.hidden&&(c=!1),m.parentId&&vg(h,n,r,a)}return c}function r9(e,n){if(!e.parentId)return;const r=n.get(e.parentId);r?r.set(e.id,e):n.set(e.parentId,new Map([[e.id,e]]))}function vg(e,n,r,a){const{elevateNodesOnSelect:l,nodeOrigin:c,nodeExtent:d}=yg(xg,a),f=e.parentId,m=n.get(f);if(!m){console.warn(`Parent node ${f} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);return}r9(e,r);const h=l?1e3:0,{x:g,y,z:x}=o9(e,m,c,d,h),{positionAbsolute:b}=e.internals,S=g!==b.x||y!==b.y;(S||x!==e.internals.z)&&n.set(e.id,{...e,internals:{...e.internals,positionAbsolute:S?{x:g,y}:b,z:x}})}function bS(e,n){return(Bn(e.zIndex)?e.zIndex:0)+(e.selected?n:0)}function o9(e,n,r,a,l){const{x:c,y:d}=n.internals.positionAbsolute,f=Vs(e),m=_l(e,r),h=ka(e.extent)?go(m,e.extent,f):m;let g=go({x:c+h.x,y:d+h.y},a,f);e.extent==="parent"&&(g=lS(g,f,n));const y=bS(e,l),x=n.internals.z??0;return{x:g.x,y:g.y,z:x>=y?x+1:y}}function bg(e,n,r,a=[0,0]){const l=[],c=new Map;for(const d of e){const f=n.get(d.parentId);if(!f)continue;const m=c.get(d.parentId)?.expandedRect??Ca(f),h=uS(m,d.rect);c.set(d.parentId,{expandedRect:h,parent:f})}return c.size>0&&c.forEach(({expandedRect:d,parent:f},m)=>{const h=f.internals.positionAbsolute,g=Vs(f),y=f.origin??a,x=d.x0||b>0||j||_)&&(l.push({id:m,type:"position",position:{x:f.position.x-x+j,y:f.position.y-b+_}}),r.get(m)?.forEach(M=>{e.some(E=>E.id===M.id)||l.push({id:M.id,type:"position",position:{x:M.position.x+x,y:M.position.y+b}})})),(g.width0){const x=bg(y,n,r,l);m.push(...x)}return{changes:m,updatedInternals:f}}async function i9({delta:e,panZoom:n,transform:r,translateExtent:a,width:l,height:c}){if(!n||!e.x&&!e.y)return Promise.resolve(!1);const d=await n.setViewportConstrained({x:r[0]+e.x,y:r[1]+e.y,zoom:r[2]},[[0,0],[l,c]],a),f=!!d&&(d.x!==r[0]||d.y!==r[1]||d.k!==r[2]);return Promise.resolve(f)}function Nb(e,n,r,a,l,c){let d=l;const f=a.get(d)||new Map;a.set(d,f.set(r,n)),d=`${l}-${e}`;const m=a.get(d)||new Map;if(a.set(d,m.set(r,n)),c){d=`${l}-${e}-${c}`;const h=a.get(d)||new Map;a.set(d,h.set(r,n))}}function wS(e,n,r){e.clear(),n.clear();for(const a of r){const{source:l,target:c,sourceHandle:d=null,targetHandle:f=null}=a,m={edgeId:a.id,source:l,target:c,sourceHandle:d,targetHandle:f},h=`${l}-${d}--${c}-${f}`,g=`${c}-${f}--${l}-${d}`;Nb("source",m,g,e,l,d),Nb("target",m,h,e,c,f),n.set(a.id,a)}}function NS(e,n){if(!e.parentId)return!1;const r=n.get(e.parentId);return r?r.selected?!0:NS(r,n):!1}function Sb(e,n,r){let a=e;do{if(a?.matches?.(n))return!0;if(a===r)return!1;a=a?.parentElement}while(a);return!1}function l9(e,n,r,a){const l=new Map;for(const[c,d]of e)if((d.selected||d.id===a)&&(!d.parentId||!NS(d,e))&&(d.draggable||n&&typeof d.draggable>"u")){const f=e.get(c);f&&l.set(c,{id:c,position:f.position||{x:0,y:0},distance:{x:r.x-f.internals.positionAbsolute.x,y:r.y-f.internals.positionAbsolute.y},extent:f.extent,parentId:f.parentId,origin:f.origin,expandParent:f.expandParent,internals:{positionAbsolute:f.internals.positionAbsolute||{x:0,y:0}},measured:{width:f.measured.width??0,height:f.measured.height??0}})}return l}function Sh({nodeId:e,dragItems:n,nodeLookup:r,dragging:a=!0}){const l=[];for(const[d,f]of n){const m=r.get(d)?.internals.userNode;m&&l.push({...m,position:f.position,dragging:a})}if(!e)return[l[0],l];const c=r.get(e)?.internals.userNode;return[c?{...c,position:n.get(e)?.position||c.position,dragging:a}:l[0],l]}function c9({dragItems:e,snapGrid:n,x:r,y:a}){const l=e.values().next().value;if(!l)return null;const c={x:r-l.distance.x,y:a-l.distance.y},d=Cl(c,n);return{x:d.x-c.x,y:d.y-c.y}}function u9({onNodeMouseDown:e,getStoreItems:n,onDragStart:r,onDrag:a,onDragStop:l}){let c={x:null,y:null},d=0,f=new Map,m=!1,h={x:0,y:0},g=null,y=!1,x=null,b=!1,S=!1,N=null;function j({noDragClassName:M,handleSelector:E,domNode:T,isSelectable:R,nodeId:D,nodeClickDistance:O=0}){x=vn(T);function P({x:G,y:W}){const{nodeLookup:B,nodeExtent:U,snapGrid:k,snapToGrid:L,nodeOrigin:I,onNodeDrag:H,onSelectionDrag:C,onError:$,updateNodePositions:Y}=n();c={x:G,y:W};let V=!1;const K=f.size>1,fe=K&&U?fp(El(f)):null,ue=K&&L?c9({dragItems:f,snapGrid:k,x:G,y:W}):null;for(const[te,ie]of f){if(!B.has(te))continue;let xe={x:G-ie.distance.x,y:W-ie.distance.y};L&&(xe=ue?{x:Math.round(xe.x+ue.x),y:Math.round(xe.y+ue.y)}:Cl(xe,k));let ve=null;if(K&&U&&!ie.extent&&fe){const{positionAbsolute:he}=ie.internals,X=he.x-fe.x+U[0][0],pe=he.x+ie.measured.width-fe.x2+U[1][0],Ne=he.y-fe.y+U[0][1],ye=he.y+ie.measured.height-fe.y2+U[1][1];ve=[[X,Ne],[pe,ye]]}const{position:be,positionAbsolute:ne}=iS({nodeId:te,nextPosition:xe,nodeLookup:B,nodeExtent:ve||U,nodeOrigin:I,onError:$});V=V||ie.position.x!==be.x||ie.position.y!==be.y,ie.position=be,ie.internals.positionAbsolute=ne}if(S=S||V,!!V&&(Y(f,!0),N&&(a||H||!D&&C))){const[te,ie]=Sh({nodeId:D,dragItems:f,nodeLookup:B});a?.(N,f,te,ie),H?.(N,te,ie),D||C?.(N,ie)}}async function q(){if(!g)return;const{transform:G,panBy:W,autoPanSpeed:B,autoPanOnNodeDrag:U}=n();if(!U){m=!1,cancelAnimationFrame(d);return}const[k,L]=cS(h,g,B);(k!==0||L!==0)&&(c.x=(c.x??0)-k/G[2],c.y=(c.y??0)-L/G[2],await W({x:k,y:L})&&P(c)),d=requestAnimationFrame(q)}function Q(G){const{nodeLookup:W,multiSelectionActive:B,nodesDraggable:U,transform:k,snapGrid:L,snapToGrid:I,selectNodesOnDrag:H,onNodeDragStart:C,onSelectionDragStart:$,unselectNodesAndEdges:Y}=n();y=!0,(!H||!R)&&!B&&D&&(W.get(D)?.selected||Y()),R&&H&&D&&e?.(D);const V=Qi(G.sourceEvent,{transform:k,snapGrid:L,snapToGrid:I,containerBounds:g});if(c=V,f=l9(W,U,V,D),f.size>0&&(r||C||!D&&$)){const[K,fe]=Sh({nodeId:D,dragItems:f,nodeLookup:W});r?.(G.sourceEvent,f,K,fe),C?.(G.sourceEvent,K,fe),D||$?.(G.sourceEvent,fe)}}const ee=P2().clickDistance(O).on("start",G=>{const{domNode:W,nodeDragThreshold:B,transform:U,snapGrid:k,snapToGrid:L}=n();g=W?.getBoundingClientRect()||null,b=!1,S=!1,N=G.sourceEvent,B===0&&Q(G),c=Qi(G.sourceEvent,{transform:U,snapGrid:k,snapToGrid:L,containerBounds:g}),h=ss(G.sourceEvent,g)}).on("drag",G=>{const{autoPanOnNodeDrag:W,transform:B,snapGrid:U,snapToGrid:k,nodeDragThreshold:L,nodeLookup:I}=n(),H=Qi(G.sourceEvent,{transform:B,snapGrid:U,snapToGrid:k,containerBounds:g});if(N=G.sourceEvent,(G.sourceEvent.type==="touchmove"&&G.sourceEvent.touches.length>1||D&&!I.has(D))&&(b=!0),!b){if(!m&&W&&y&&(m=!0,q()),!y){const C=H.xSnapped-(c.x??0),$=H.ySnapped-(c.y??0);Math.sqrt(C*C+$*$)>L&&Q(G)}(c.x!==H.xSnapped||c.y!==H.ySnapped)&&f&&y&&(h=ss(G.sourceEvent,g),P(H))}}).on("end",G=>{if(!(!y||b)&&(m=!1,y=!1,cancelAnimationFrame(d),f.size>0)){const{nodeLookup:W,updateNodePositions:B,onNodeDragStop:U,onSelectionDragStop:k}=n();if(S&&(B(f,!1),S=!1),l||U||!D&&k){const[L,I]=Sh({nodeId:D,dragItems:f,nodeLookup:W,dragging:!1});l?.(G.sourceEvent,f,L,I),U?.(G.sourceEvent,L,I),D||k?.(G.sourceEvent,I)}}}).filter(G=>{const W=G.target;return!G.button&&(!M||!Sb(W,`.${M}`,T))&&(!E||Sb(W,E,T))});x.call(ee)}function _(){x?.on(".drag",null)}return{update:j,destroy:_}}function d9(e,n,r){const a=[],l={x:e.x-r,y:e.y-r,width:r*2,height:r*2};for(const c of n.values())dl(l,Ca(c))>0&&a.push(c);return a}const f9=250;function m9(e,n,r,a){let l=[],c=1/0;const d=d9(e,r,n+f9);for(const f of d){const m=[...f.internals.handleBounds?.source??[],...f.internals.handleBounds?.target??[]];for(const h of m){if(a.nodeId===h.nodeId&&a.type===h.type&&a.id===h.id)continue;const{x:g,y}=ml(f,h,h.position,!0),x=Math.sqrt(Math.pow(g-e.x,2)+Math.pow(y-e.y,2));x>n||(x1){const f=a.type==="source"?"target":"source";return l.find(m=>m.type===f)??l[0]}return l[0]}function SS(e,n,r,a,l,c=!1){const d=a.get(e);if(!d)return null;const f=l==="strict"?d.internals.handleBounds?.[n]:[...d.internals.handleBounds?.source??[],...d.internals.handleBounds?.target??[]],m=(r?f?.find(h=>h.id===r):f?.[0])??null;return m&&c?{...m,...ml(d,m,m.position,!0)}:m}function jS(e,n){return e||(n?.classList.contains("target")?"target":n?.classList.contains("source")?"source":null)}function h9(e,n){let r=null;return n?r=!0:e&&!n&&(r=!1),r}const _S=()=>!0;function p9(e,{connectionMode:n,connectionRadius:r,handleId:a,nodeId:l,edgeUpdaterType:c,isTarget:d,domNode:f,nodeLookup:m,lib:h,autoPanOnConnect:g,flowId:y,panBy:x,cancelConnection:b,onConnectStart:S,onConnect:N,onConnectEnd:j,isValidConnection:_=_S,onReconnectEnd:M,updateConnection:E,getTransform:T,getFromHandle:R,autoPanSpeed:D,dragThreshold:O=1,handleDomNode:P}){const q=mS(e.target);let Q=0,ee;const{x:G,y:W}=ss(e),B=jS(c,P),U=f?.getBoundingClientRect();let k=!1;if(!U||!B)return;const L=SS(l,B,a,m,n);if(!L)return;let I=ss(e,U),H=!1,C=null,$=!1,Y=null;function V(){if(!g||!U)return;const[be,ne]=cS(I,U,D);x({x:be,y:ne}),Q=requestAnimationFrame(V)}const K={...L,nodeId:l,type:B,position:L.position},fe=m.get(l);let te={inProgress:!0,isValid:null,from:ml(fe,K,ze.Left,!0),fromHandle:K,fromPosition:K.position,fromNode:fe,to:I,toHandle:null,toPosition:db[K.position],toNode:null};function ie(){k=!0,E(te),S?.(e,{nodeId:l,handleId:a,handleType:B})}O===0&&ie();function xe(be){if(!k){const{x:pe,y:Ne}=ss(be),ye=pe-G,Oe=Ne-W;if(!(ye*ye+Oe*Oe>O*O))return;ie()}if(!R()||!K){ve(be);return}const ne=T();I=ss(be,U),ee=m9(kl(I,ne,!1,[1,1]),r,m,K),H||(V(),H=!0);const he=ES(be,{handle:ee,connectionMode:n,fromNodeId:l,fromHandleId:a,fromType:d?"target":"source",isValidConnection:_,doc:q,lib:h,flowId:y,nodeLookup:m});Y=he.handleDomNode,C=he.connection,$=h9(!!ee,he.isValid);const X={...te,isValid:$,to:he.toHandle&&$?Zu({x:he.toHandle.x,y:he.toHandle.y},ne):I,toHandle:he.toHandle,toPosition:$&&he.toHandle?he.toHandle.position:db[K.position],toNode:he.toHandle?m.get(he.toHandle.nodeId):null};$&&ee&&te.toHandle&&X.toHandle&&te.toHandle.type===X.toHandle.type&&te.toHandle.nodeId===X.toHandle.nodeId&&te.toHandle.id===X.toHandle.id&&te.to.x===X.to.x&&te.to.y===X.to.y||(E(X),te=X)}function ve(be){if(k){(ee||Y)&&C&&$&&N?.(C);const{inProgress:ne,...he}=te,X={...he,toPosition:te.toHandle?te.toPosition:null};j?.(be,X),c&&M?.(be,X)}b(),cancelAnimationFrame(Q),H=!1,$=!1,C=null,Y=null,q.removeEventListener("mousemove",xe),q.removeEventListener("mouseup",ve),q.removeEventListener("touchmove",xe),q.removeEventListener("touchend",ve)}q.addEventListener("mousemove",xe),q.addEventListener("mouseup",ve),q.addEventListener("touchmove",xe),q.addEventListener("touchend",ve)}function ES(e,{handle:n,connectionMode:r,fromNodeId:a,fromHandleId:l,fromType:c,doc:d,lib:f,flowId:m,isValidConnection:h=_S,nodeLookup:g}){const y=c==="target",x=n?d.querySelector(`.${f}-flow__handle[data-id="${m}-${n?.nodeId}-${n?.id}-${n?.type}"]`):null,{x:b,y:S}=ss(e),N=d.elementFromPoint(b,S),j=N?.classList.contains(`${f}-flow__handle`)?N:x,_={handleDomNode:j,isValid:!1,connection:null,toHandle:null};if(j){const M=jS(void 0,j),E=j.getAttribute("data-nodeid"),T=j.getAttribute("data-handleid"),R=j.classList.contains("connectable"),D=j.classList.contains("connectableend");if(!E||!M)return _;const O={source:y?E:a,sourceHandle:y?T:l,target:y?a:E,targetHandle:y?l:T};_.connection=O;const q=R&&D&&(r===_a.Strict?y&&M==="source"||!y&&M==="target":E!==a||T!==l);_.isValid=q&&h(O),_.toHandle=SS(E,M,T,g,r,!0)}return _}const gp={onPointerDown:p9,isValid:ES};function g9({domNode:e,panZoom:n,getTransform:r,getViewScale:a}){const l=vn(e);function c({translateExtent:f,width:m,height:h,zoomStep:g=1,pannable:y=!0,zoomable:x=!0,inversePan:b=!1}){const S=E=>{if(E.sourceEvent.type!=="wheel"||!n)return;const T=r(),R=E.sourceEvent.ctrlKey&&fl()?10:1,D=-E.sourceEvent.deltaY*(E.sourceEvent.deltaMode===1?.05:E.sourceEvent.deltaMode?1:.002)*g,O=T[2]*Math.pow(2,D*R);n.scaleTo(O)};let N=[0,0];const j=E=>{(E.sourceEvent.type==="mousedown"||E.sourceEvent.type==="touchstart")&&(N=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY])},_=E=>{const T=r();if(E.sourceEvent.type!=="mousemove"&&E.sourceEvent.type!=="touchmove"||!n)return;const R=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY],D=[R[0]-N[0],R[1]-N[1]];N=R;const O=a()*Math.max(T[2],Math.log(T[2]))*(b?-1:1),P={x:T[0]-D[0]*O,y:T[1]-D[1]*O},q=[[0,0],[m,h]];n.setViewportConstrained({x:P.x,y:P.y,zoom:T[2]},q,f)},M=tS().on("start",j).on("zoom",y?_:null).on("zoom.wheel",x?S:null);l.call(M,{})}function d(){l.on("zoom",null)}return{update:c,destroy:d,pointer:$n}}const x9=(e,n)=>e.x!==n.x||e.y!==n.y||e.zoom!==n.k,Dd=e=>({x:e.x,y:e.y,zoom:e.k}),jh=({x:e,y:n,zoom:r})=>Md.translate(e,n).scale(r),ca=(e,n)=>e.target.closest(`.${n}`),CS=(e,n)=>n===2&&Array.isArray(e)&&e.includes(2),y9=e=>((e*=2)<=1?e*e*e:(e-=2)*e*e+2)/2,_h=(e,n=0,r=y9,a=()=>{})=>{const l=typeof n=="number"&&n>0;return l||a(),l?e.transition().duration(n).ease(r).on("end",a):e},kS=e=>{const n=e.ctrlKey&&fl()?10:1;return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*n};function v9({zoomPanValues:e,noWheelClassName:n,d3Selection:r,d3Zoom:a,panOnScrollMode:l,panOnScrollSpeed:c,zoomOnPinch:d,onPanZoomStart:f,onPanZoom:m,onPanZoomEnd:h}){return g=>{if(ca(g,n))return!1;g.preventDefault(),g.stopImmediatePropagation();const y=r.property("__zoom").k||1;if(g.ctrlKey&&d){const j=$n(g),_=kS(g),M=y*Math.pow(2,_);a.scaleTo(r,M,j,g);return}const x=g.deltaMode===1?20:1;let b=l===ao.Vertical?0:g.deltaX*x,S=l===ao.Horizontal?0:g.deltaY*x;!fl()&&g.shiftKey&&l!==ao.Vertical&&(b=g.deltaY*x,S=0),a.translateBy(r,-(b/y)*c,-(S/y)*c,{internal:!0});const N=Dd(r.property("__zoom"));clearTimeout(e.panScrollTimeout),e.isPanScrolling||(e.isPanScrolling=!0,f?.(g,N)),e.isPanScrolling&&(m?.(g,N),e.panScrollTimeout=setTimeout(()=>{h?.(g,N),e.isPanScrolling=!1},150))}}function b9({noWheelClassName:e,preventScrolling:n,d3ZoomHandler:r}){return function(a,l){const c=a.type==="wheel",d=!n&&c&&!a.ctrlKey,f=ca(a,e);if(a.ctrlKey&&c&&f&&a.preventDefault(),d||f)return null;a.preventDefault(),r.call(this,a,l)}}function w9({zoomPanValues:e,onDraggingChange:n,onPanZoomStart:r}){return a=>{if(a.sourceEvent?.internal)return;const l=Dd(a.transform);e.mouseButton=a.sourceEvent?.button||0,e.isZoomingOrPanning=!0,e.prevViewport=l,a.sourceEvent?.type==="mousedown"&&n(!0),r&&r?.(a.sourceEvent,l)}}function N9({zoomPanValues:e,panOnDrag:n,onPaneContextMenu:r,onTransformChange:a,onPanZoom:l}){return c=>{e.usedRightMouseButton=!!(r&&CS(n,e.mouseButton??0)),c.sourceEvent?.sync||a([c.transform.x,c.transform.y,c.transform.k]),l&&!c.sourceEvent?.internal&&l?.(c.sourceEvent,Dd(c.transform))}}function S9({zoomPanValues:e,panOnDrag:n,panOnScroll:r,onDraggingChange:a,onPanZoomEnd:l,onPaneContextMenu:c}){return d=>{if(!d.sourceEvent?.internal&&(e.isZoomingOrPanning=!1,c&&CS(n,e.mouseButton??0)&&!e.usedRightMouseButton&&d.sourceEvent&&c(d.sourceEvent),e.usedRightMouseButton=!1,a(!1),l&&x9(e.prevViewport,d.transform))){const f=Dd(d.transform);e.prevViewport=f,clearTimeout(e.timerId),e.timerId=setTimeout(()=>{l?.(d.sourceEvent,f)},r?150:0)}}}function j9({zoomActivationKeyPressed:e,zoomOnScroll:n,zoomOnPinch:r,panOnDrag:a,panOnScroll:l,zoomOnDoubleClick:c,userSelectionActive:d,noWheelClassName:f,noPanClassName:m,lib:h}){return g=>{const y=e||n,x=r&&g.ctrlKey;if(g.button===1&&g.type==="mousedown"&&(ca(g,`${h}-flow__node`)||ca(g,`${h}-flow__edge`)))return!0;if(!a&&!y&&!l&&!c&&!r||d||ca(g,f)&&g.type==="wheel"||ca(g,m)&&(g.type!=="wheel"||l&&g.type==="wheel"&&!e)||!r&&g.ctrlKey&&g.type==="wheel")return!1;if(!r&&g.type==="touchstart"&&g.touches?.length>1)return g.preventDefault(),!1;if(!y&&!l&&!x&&g.type==="wheel"||!a&&(g.type==="mousedown"||g.type==="touchstart")||Array.isArray(a)&&!a.includes(g.button)&&g.type==="mousedown")return!1;const b=Array.isArray(a)&&a.includes(g.button)||!g.button||g.button<=1;return(!g.ctrlKey||g.type==="wheel")&&b}}function _9({domNode:e,minZoom:n,maxZoom:r,paneClickDistance:a,translateExtent:l,viewport:c,onPanZoom:d,onPanZoomStart:f,onPanZoomEnd:m,onDraggingChange:h}){const g={isZoomingOrPanning:!1,usedRightMouseButton:!1,prevViewport:{x:0,y:0,zoom:0},mouseButton:0,timerId:void 0,panScrollTimeout:void 0,isPanScrolling:!1},y=e.getBoundingClientRect(),x=tS().clickDistance(!Bn(a)||a<0?0:a).scaleExtent([n,r]).translateExtent(l),b=vn(e).call(x);E({x:c.x,y:c.y,zoom:Ea(c.zoom,n,r)},[[0,0],[y.width,y.height]],l);const S=b.on("wheel.zoom"),N=b.on("dblclick.zoom");x.wheelDelta(kS);function j(G,W){return b?new Promise(B=>{x?.interpolate(W?.interpolate==="linear"?Ki:bu).transform(_h(b,W?.duration,W?.ease,()=>B(!0)),G)}):Promise.resolve(!1)}function _({noWheelClassName:G,noPanClassName:W,onPaneContextMenu:B,userSelectionActive:U,panOnScroll:k,panOnDrag:L,panOnScrollMode:I,panOnScrollSpeed:H,preventScrolling:C,zoomOnPinch:$,zoomOnScroll:Y,zoomOnDoubleClick:V,zoomActivationKeyPressed:K,lib:fe,onTransformChange:ue}){U&&!g.isZoomingOrPanning&&M();const ie=k&&!K&&!U?v9({zoomPanValues:g,noWheelClassName:G,d3Selection:b,d3Zoom:x,panOnScrollMode:I,panOnScrollSpeed:H,zoomOnPinch:$,onPanZoomStart:f,onPanZoom:d,onPanZoomEnd:m}):b9({noWheelClassName:G,preventScrolling:C,d3ZoomHandler:S});if(b.on("wheel.zoom",ie,{passive:!1}),!U){const ve=w9({zoomPanValues:g,onDraggingChange:h,onPanZoomStart:f});x.on("start",ve);const be=N9({zoomPanValues:g,panOnDrag:L,onPaneContextMenu:!!B,onPanZoom:d,onTransformChange:ue});x.on("zoom",be);const ne=S9({zoomPanValues:g,panOnDrag:L,panOnScroll:k,onPaneContextMenu:B,onPanZoomEnd:m,onDraggingChange:h});x.on("end",ne)}const xe=j9({zoomActivationKeyPressed:K,panOnDrag:L,zoomOnScroll:Y,panOnScroll:k,zoomOnDoubleClick:V,zoomOnPinch:$,userSelectionActive:U,noPanClassName:W,noWheelClassName:G,lib:fe});x.filter(xe),V?b.on("dblclick.zoom",N):b.on("dblclick.zoom",null)}function M(){x.on("zoom",null)}async function E(G,W,B){const U=jh(G),k=x?.constrain()(U,W,B);return k&&await j(k),new Promise(L=>L(k))}async function T(G,W){const B=jh(G);return await j(B,W),new Promise(U=>U(B))}function R(G){if(b){const W=jh(G),B=b.property("__zoom");(B.k!==G.zoom||B.x!==G.x||B.y!==G.y)&&x?.transform(b,W,null,{sync:!0})}}function D(){const G=b?eS(b.node()):{x:0,y:0,k:1};return{x:G.x,y:G.y,zoom:G.k}}function O(G,W){return b?new Promise(B=>{x?.interpolate(W?.interpolate==="linear"?Ki:bu).scaleTo(_h(b,W?.duration,W?.ease,()=>B(!0)),G)}):Promise.resolve(!1)}function P(G,W){return b?new Promise(B=>{x?.interpolate(W?.interpolate==="linear"?Ki:bu).scaleBy(_h(b,W?.duration,W?.ease,()=>B(!0)),G)}):Promise.resolve(!1)}function q(G){x?.scaleExtent(G)}function Q(G){x?.translateExtent(G)}function ee(G){const W=!Bn(G)||G<0?0:G;x?.clickDistance(W)}return{update:_,destroy:M,setViewport:T,setViewportConstrained:E,getViewport:D,scaleTo:O,scaleBy:P,setScaleExtent:q,setTranslateExtent:Q,syncViewport:R,setClickDistance:ee}}var Aa;(function(e){e.Line="line",e.Handle="handle"})(Aa||(Aa={}));function E9({width:e,prevWidth:n,height:r,prevHeight:a,affectsX:l,affectsY:c}){const d=e-n,f=r-a,m=[d>0?1:d<0?-1:0,f>0?1:f<0?-1:0];return d&&l&&(m[0]=m[0]*-1),f&&c&&(m[1]=m[1]*-1),m}function C9(e){const n=e.includes("right")||e.includes("left"),r=e.includes("bottom")||e.includes("top"),a=e.includes("left"),l=e.includes("top");return{isHorizontal:n,isVertical:r,affectsX:a,affectsY:l}}function yr(e,n){return Math.max(0,n-e)}function vr(e,n){return Math.max(0,e-n)}function iu(e,n,r){return Math.max(0,n-e,e-r)}function jb(e,n){return e?!n:n}function k9(e,n,r,a,l,c,d,f){let{affectsX:m,affectsY:h}=n;const{isHorizontal:g,isVertical:y}=n,x=g&&y,{xSnapped:b,ySnapped:S}=r,{minWidth:N,maxWidth:j,minHeight:_,maxHeight:M}=a,{x:E,y:T,width:R,height:D,aspectRatio:O}=e;let P=Math.floor(g?b-e.pointerX:0),q=Math.floor(y?S-e.pointerY:0);const Q=R+(m?-P:P),ee=D+(h?-q:q),G=-c[0]*R,W=-c[1]*D;let B=iu(Q,N,j),U=iu(ee,_,M);if(d){let I=0,H=0;m&&P<0?I=yr(E+P+G,d[0][0]):!m&&P>0&&(I=vr(E+Q+G,d[1][0])),h&&q<0?H=yr(T+q+W,d[0][1]):!h&&q>0&&(H=vr(T+ee+W,d[1][1])),B=Math.max(B,I),U=Math.max(U,H)}if(f){let I=0,H=0;m&&P>0?I=vr(E+P,f[0][0]):!m&&P<0&&(I=yr(E+Q,f[1][0])),h&&q>0?H=vr(T+q,f[0][1]):!h&&q<0&&(H=yr(T+ee,f[1][1])),B=Math.max(B,I),U=Math.max(U,H)}if(l){if(g){const I=iu(Q/O,_,M)*O;if(B=Math.max(B,I),d){let H=0;!m&&!h||m&&!h&&x?H=vr(T+W+Q/O,d[1][1])*O:H=yr(T+W+(m?P:-P)/O,d[0][1])*O,B=Math.max(B,H)}if(f){let H=0;!m&&!h||m&&!h&&x?H=yr(T+Q/O,f[1][1])*O:H=vr(T+(m?P:-P)/O,f[0][1])*O,B=Math.max(B,H)}}if(y){const I=iu(ee*O,N,j)/O;if(U=Math.max(U,I),d){let H=0;!m&&!h||h&&!m&&x?H=vr(E+ee*O+G,d[1][0])/O:H=yr(E+(h?q:-q)*O+G,d[0][0])/O,U=Math.max(U,H)}if(f){let H=0;!m&&!h||h&&!m&&x?H=yr(E+ee*O,f[1][0])/O:H=vr(E+(h?q:-q)*O,f[0][0])/O,U=Math.max(U,H)}}}q=q+(q<0?U:-U),P=P+(P<0?B:-B),l&&(x?Q>ee*O?q=(jb(m,h)?-P:P)/O:P=(jb(m,h)?-q:q)*O:g?(q=P/O,h=m):(P=q*O,m=h));const k=m?E+P:E,L=h?T+q:T;return{width:R+(m?-P:P),height:D+(h?-q:q),x:c[0]*P*(m?-1:1)+k,y:c[1]*q*(h?-1:1)+L}}const AS={width:0,height:0,x:0,y:0},A9={...AS,pointerX:0,pointerY:0,aspectRatio:1};function M9(e){return[[0,0],[e.measured.width,e.measured.height]]}function T9(e,n,r){const a=n.position.x+e.position.x,l=n.position.y+e.position.y,c=e.measured.width??0,d=e.measured.height??0,f=r[0]*c,m=r[1]*d;return[[a-f,l-m],[a+c-f,l+d-m]]}function R9({domNode:e,nodeId:n,getStoreItems:r,onChange:a,onEnd:l}){const c=vn(e);function d({controlPosition:m,boundaries:h,keepAspectRatio:g,resizeDirection:y,onResizeStart:x,onResize:b,onResizeEnd:S,shouldResize:N}){let j={...AS},_={...A9};const M=C9(m);let E,T=null,R=[],D,O,P;const q=P2().on("start",Q=>{const{nodeLookup:ee,transform:G,snapGrid:W,snapToGrid:B,nodeOrigin:U,paneDomNode:k}=r();if(E=ee.get(n),!E)return;T=k?.getBoundingClientRect()??null;const{xSnapped:L,ySnapped:I}=Qi(Q.sourceEvent,{transform:G,snapGrid:W,snapToGrid:B,containerBounds:T});j={width:E.measured.width??0,height:E.measured.height??0,x:E.position.x??0,y:E.position.y??0},_={...j,pointerX:L,pointerY:I,aspectRatio:j.width/j.height},D=void 0,E.parentId&&(E.extent==="parent"||E.expandParent)&&(D=ee.get(E.parentId),O=D&&E.extent==="parent"?M9(D):void 0),R=[],P=void 0;for(const[H,C]of ee)if(C.parentId===n&&(R.push({id:H,position:{...C.position},extent:C.extent}),C.extent==="parent"||C.expandParent)){const $=T9(C,E,C.origin??U);P?P=[[Math.min($[0][0],P[0][0]),Math.min($[0][1],P[0][1])],[Math.max($[1][0],P[1][0]),Math.max($[1][1],P[1][1])]]:P=$}x?.(Q,{...j})}).on("drag",Q=>{const{transform:ee,snapGrid:G,snapToGrid:W,nodeOrigin:B}=r(),U=Qi(Q.sourceEvent,{transform:ee,snapGrid:G,snapToGrid:W,containerBounds:T}),k=[];if(!E)return;const{x:L,y:I,width:H,height:C}=j,$={},Y=E.origin??B,{width:V,height:K,x:fe,y:ue}=k9(_,M,U,h,g,Y,O,P),te=V!==H,ie=K!==C,xe=fe!==L&&te,ve=ue!==I&&ie;if(!xe&&!ve&&!te&&!ie)return;if((xe||ve||Y[0]===1||Y[1]===1)&&($.x=xe?fe:j.x,$.y=ve?ue:j.y,j.x=$.x,j.y=$.y,R.length>0)){const X=fe-L,pe=ue-I;for(const Ne of R)Ne.position={x:Ne.position.x-X+Y[0]*(V-H),y:Ne.position.y-pe+Y[1]*(K-C)},k.push(Ne)}if((te||ie)&&($.width=te&&(!y||y==="horizontal")?V:j.width,$.height=ie&&(!y||y==="vertical")?K:j.height,j.width=$.width,j.height=$.height),D&&E.expandParent){const X=Y[0]*($.width??0);$.x&&$.x{S?.(Q,{...j}),l?.({...j})});c.call(q)}function f(){c.on(".drag",null)}return{update:d,destroy:f}}var Eh={exports:{}},Ch={},kh={exports:{}},Ah={};/** + opacity-0 group-hover:opacity-100`,title:c?"Copied!":"Copy message",children:c?o.jsx(IA,{className:"h-3.5 w-3.5 text-green-600 dark:text-green-400"}):o.jsx(no,{className:"h-3.5 w-3.5"})})]}),o.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground font-mono",children:[o.jsx("span",{children:e.created_at?new Date(e.created_at*1e3).toLocaleTimeString():new Date().toLocaleTimeString()}),!x&&e.usage&&o.jsxs(o.Fragment,{children:[o.jsx("span",{children:"•"}),o.jsxs("span",{className:"flex items-center gap-1",children:[o.jsxs("span",{className:"text-blue-600 dark:text-blue-400",children:["↑",e.usage.input_tokens]}),o.jsxs("span",{className:"text-green-600 dark:text-green-400",children:["↓",e.usage.output_tokens]}),o.jsxs("span",{children:["(",e.usage.total_tokens," tokens)"]})]})]}),!x&&h&&n.length>0&&o.jsxs(o.Fragment,{children:[o.jsx("span",{children:"•"}),o.jsxs("button",{onClick:()=>m(!f),className:"flex items-center gap-1 hover:text-foreground transition-colors",title:`${n.length} tool call${n.length>1?"s":""} - click to ${f?"hide":"show"} details`,children:[o.jsx(ba,{className:"h-3 w-3"}),o.jsx("span",{children:n.length})]})]})]}),!x&&f&&n.length>0&&o.jsx("div",{className:"mt-2 ml-0 p-3 bg-muted/30 rounded-md border border-muted",children:o.jsx("div",{className:"space-y-2",children:n.map(j=>{const _=r.find(M=>M.call_id===j.call_id);return o.jsx("div",{className:"text-xs",children:o.jsxs("div",{className:"flex items-start gap-2",children:[o.jsx(ba,{className:"h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0"}),o.jsxs("div",{className:"flex-1 min-w-0",children:[o.jsxs("div",{className:"font-mono text-muted-foreground",children:[o.jsx("span",{className:"text-blue-600 dark:text-blue-400",children:j.name}),o.jsx("span",{className:"text-muted-foreground/60 ml-1",children:j.arguments&&o.jsxs("span",{className:"break-all",children:["(",j.arguments,")"]})})]}),_&&_.output&&o.jsx("div",{className:"mt-1 pl-5 border-l-2 border-green-600/20",children:o.jsxs("div",{className:"flex items-start gap-1",children:[o.jsx(vo,{className:"h-3 w-3 text-green-600 dark:text-green-400 mt-0.5 flex-shrink-0"}),o.jsx("pre",{className:"font-mono text-muted-foreground whitespace-pre-wrap break-all",children:_.output.substring(0,200)+(_.output.length>200?"...":"")})]})}),j.status==="incomplete"&&o.jsx("div",{className:"mt-1 pl-5 border-l-2 border-orange-600/20",children:o.jsxs("div",{className:"flex items-start gap-1",children:[o.jsx(wa,{className:"h-3 w-3 text-orange-600 dark:text-orange-400 mt-0.5 flex-shrink-0"}),o.jsx("span",{className:"font-mono text-orange-600 dark:text-orange-400",children:"Failed"})]})})]})]})},j.id)})})})]})]})}return e.type==="function_call"||e.type==="function_call_output",null}function BR({selectedAgent:e,onDebugEvent:n}){const r=de(ne=>ne.currentConversation),a=de(ne=>ne.availableConversations),l=de(ne=>ne.chatItems),c=de(ne=>ne.isStreaming),d=de(ne=>ne.isSubmitting),f=de(ne=>ne.loadingConversations),m=de(ne=>ne.uiMode),h=de(ne=>ne.conversationUsage),g=de(ne=>ne.pendingApprovals),y=de(ne=>ne.oaiMode),x=de(ne=>ne.setCurrentConversation),b=de(ne=>ne.setAvailableConversations),S=de(ne=>ne.setChatItems),N=de(ne=>ne.setIsStreaming),j=de(ne=>ne.setIsSubmitting),_=de(ne=>ne.setLoadingConversations),M=de(ne=>ne.updateConversationUsage),E=de(ne=>ne.setPendingApprovals),[T,R]=w.useState(!1),[D,O]=w.useState(null),[P,q]=w.useState(!1),[Q,ee]=w.useState(!1),{isCancelling:G,createAbortSignal:W,handleCancel:B,resetCancelling:U}=ON(),{isDragOver:k,droppedFiles:L,clearDroppedFiles:I,dragHandlers:H}=S5({disabled:d||c}),C=w.useRef(null),$=w.useRef(null),Y=w.useRef(null),V=w.useRef(!1),K=w.useRef("");w.useEffect(()=>{if(!$.current)return;const ne=C.current?.querySelector("[data-radix-scroll-area-viewport]");let he=!1;if(ne){const{scrollTop:X,scrollHeight:pe,clientHeight:Ne}=ne,ye=pe-X-Ne<100;he=V.current||ye}else he=!0;he&&$.current.scrollIntoView({behavior:c?"instant":"smooth"}),V.current&&!c&&(V.current=!1)},[l,c]),w.useEffect(()=>{},[c,d]),w.useEffect(()=>{const ne=async(X,pe,Ne)=>{const ye=ha(pe.id);if(!ye||!ye.responseId){N(!1);return}try{const Oe={model:Ne.id,input:[],stream:!0,conversation:pe.id},Se=Je.streamAgentExecutionOpenAIDirect(Ne.id,Oe,pe.id,void 0,ye.responseId);for await(const He of Se){if(n(He),He.type==="response.completed"){const Ve=He.response?.usage;Ve&&(Y.current={input_tokens:Ve.input_tokens,output_tokens:Ve.output_tokens,total_tokens:Ve.total_tokens});continue}if(He.type==="response.failed"){const Ve=He.response?.error,_e=Ve?typeof Ve=="object"&&"message"in Ve?Ve.message:JSON.stringify(Ve):"Request failed",$e=de.getState().chatItems;S($e.map(Fe=>Fe.id===X.id&&Fe.type==="message"?{...Fe,content:[{type:"text",text:K.current||_e}],status:"incomplete"}:Fe)),N(!1);return}if(He.type==="response.function_approval.requested"){const Re=He;E([...de.getState().pendingApprovals,{request_id:Re.request_id,function_call:Re.function_call}]);continue}if(He.type==="response.function_approval.responded"){const Re=He;E(de.getState().pendingApprovals.filter(Ve=>Ve.request_id!==Re.request_id));continue}if(He.type==="error"){const Ve=He.message||"An error occurred",_e=de.getState().chatItems;S(_e.map($e=>$e.id===X.id&&$e.type==="message"?{...$e,content:[{type:"text",text:K.current||Ve}],status:"incomplete"}:$e)),N(!1);return}if(He.type==="response.output_text.delta"&&"delta"in He&&He.delta){K.current+=He.delta;const Re=de.getState().chatItems;S(Re.map(Ve=>Ve.id===X.id&&Ve.type==="message"?{...Ve,content:[{type:"text",text:K.current}],status:"in_progress"}:Ve))}}const Ie=Y.current,Xe=de.getState().chatItems;S(Xe.map(He=>He.id===X.id&&He.type==="message"?{...He,status:"completed",usage:Ie||void 0}:He)),N(!1),Ie&&M(Ie.total_tokens),Y.current=null}catch(Oe){const Se=de.getState().chatItems;S(Se.map(Ie=>Ie.id===X.id&&Ie.type==="message"?{...Ie,content:[{type:"text",text:`Error resuming stream: ${Oe instanceof Error?Oe.message:"Unknown error"}`}],status:"incomplete"}:Ie)),N(!1)}},he=async()=>{if(e){_(!0);try{try{const{data:ye}=await Je.listConversations(e.id);if(b(ye),ye.length>0){const Oe=ye[0];x(Oe);try{let Se=[],Ie=!0,Xe;for(;Ie;){const Re=await Je.listConversationItems(Oe.id,{order:"asc",after:Xe});Se=Se.concat(Re.data),Ie=Re.has_more,Ie&&Re.data.length>0&&(Xe=Re.data[Re.data.length-1].id)}S(Se),N(!1);const He=ha(Oe.id);if(He&&!He.completed){K.current=He.accumulatedText||"";const Re={id:He.lastMessageId||`assistant-${Date.now()}`,type:"message",role:"assistant",content:He.accumulatedText?[{type:"text",text:He.accumulatedText}]:[],status:"in_progress"};S([...Se,Re]),N(!0),setTimeout(()=>{ne(Re,Oe,e)},100)}setTimeout(()=>{$.current?.scrollIntoView({behavior:"smooth"})},100)}catch{console.debug(`No items found for conversation ${Oe.id}, starting fresh`),S([]),N(!1)}return}}catch{}const X=`devui_convs_${e.id}`,pe=localStorage.getItem(X);if(pe)try{const ye=JSON.parse(pe);if(ye.length>0)try{await Je.listConversationItems(ye[0].id),b(ye),x(ye[0]),S([]),N(!1);return}catch{console.debug(`Cached conversation ${ye[0].id} no longer exists, clearing cache`),localStorage.removeItem(X)}}catch{localStorage.removeItem(X)}const Ne=await Je.createConversation({agent_id:e.id});x(Ne),b([Ne]),S([]),N(!1),O(null),localStorage.setItem(X,JSON.stringify([Ne]))}catch(X){b([]),S([]),N(!1);const pe=X instanceof Error?X.message:"Failed to create conversation";O({message:pe,type:"conversation_creation_error"})}finally{_(!1)}}};S([]),N(!1),x(void 0),K.current="",he()},[e,n,S,N,_,b,x,E,M]);const fe=w.useCallback(async()=>{if(e)try{const ne=await Je.createConversation({agent_id:e.id});x(ne),b([ne,...de.getState().availableConversations]),S([]),N(!1),O(null),de.setState({conversationUsage:{total_tokens:0,message_count:0}}),K.current="";const he=`devui_convs_${e.id}`,X=[ne,...a];localStorage.setItem(he,JSON.stringify(X))}catch(ne){const he=ne instanceof Error?ne.message:"Failed to create conversation";O({message:he,type:"conversation_creation_error"})}},[e,x,b,S,N]),ue=w.useCallback(async(ne,he)=>{if(he&&(he.preventDefault(),he.stopPropagation()),!!confirm("Delete this conversation? This cannot be undone."))try{if(await Je.deleteConversation(ne)){const pe=a.filter(Ne=>Ne.id!==ne);if(b(pe),r?.id===ne)if(pe.length>0){const Ne=pe[0];x(Ne),S([]),N(!1)}else x(void 0),S([]),N(!1),de.setState({conversationUsage:{total_tokens:0,message_count:0}}),K.current="";n("clear")}}catch{alert("Failed to delete conversation. Please try again.")}},[a,r,n,b,x,S,N]),te=w.useCallback(async()=>{if(P||!e)return;q(!0);const ne=de.getState().addToast,he=de.getState().updateAgent;try{await Je.reloadEntity(e.id);const X=await Je.getAgentInfo(e.id);he(X),ne({message:`${e.name} has been reloaded successfully`,type:"success"})}catch(X){const pe=X instanceof Error?X.message:"Failed to reload entity";ne({message:`Failed to reload: ${pe}`,type:"error",duration:6e3})}finally{q(!1)}},[P,e]),ie=w.useCallback(async ne=>{const he=a.find(X=>X.id===ne);if(he){x(he),n("clear");try{let X=[],pe=!0,Ne;for(;pe;){const Se=await Je.listConversationItems(ne,{order:"asc",after:Ne});X=X.concat(Se.data),pe=Se.has_more,pe&&Se.data.length>0&&(Ne=Se.data[Se.data.length-1].id)}const ye=X;S(ye),N(!1),de.setState({conversationUsage:{total_tokens:0,message_count:ye.length}});const Oe=ha(ne);if(Oe?.accumulatedText){K.current=Oe.accumulatedText;const Se={id:`assistant-${Date.now()}`,type:"message",role:"assistant",content:[{type:"output_text",text:Oe.accumulatedText}],status:"in_progress"};S([...ye,Se]),N(!0)}setTimeout(()=>{$.current?.scrollIntoView({behavior:"smooth"})},100)}catch{console.debug(`No items found for conversation ${ne}, starting with empty chat`),S([]),N(!1),de.setState({conversationUsage:{total_tokens:0,message_count:0}})}K.current=""}},[a,n,x,S,N]),xe=async(ne,he)=>{const X=g.find(Ie=>Ie.request_id===ne);if(!X)return;const pe=Math.floor(Date.now()/1e3),Ne={id:`user-approval-${Date.now()}`,type:"message",role:"user",content:[{type:"function_approval_request",request_id:ne,status:he?"approved":"rejected",function_call:X.function_call}],status:"completed",created_at:pe},ye=de.getState().chatItems;S([...ye,Ne]);const Se={input:[{type:"message",role:"user",content:[{type:"function_approval_response",request_id:ne,approved:he,function_call:X.function_call}]}],conversation_id:r?.id};return E(de.getState().pendingApprovals.filter(Ie=>Ie.request_id!==ne)),Se},ve=w.useCallback(async ne=>{if(!e)return;const he=ne.input.some(ye=>ye.type==="message"&&Array.isArray(ye.content)&&ye.content.some(Oe=>Oe.type==="function_approval_response")),X=[];for(const ye of ne.input)if(ye.type==="message"&&Array.isArray(ye.content)){for(const Oe of ye.content)if(Oe.type==="input_text")X.push({type:"text",text:Oe.text});else if(Oe.type==="input_image")X.push({type:"input_image",image_url:Oe.image_url||"",detail:"auto"});else if(Oe.type==="input_file"){const Se=Oe;X.push({type:"input_file",file_data:Se.file_data,filename:Se.filename})}}const pe=Math.floor(Date.now()/1e3);if(!he&&X.length>0){const ye={id:`user-${Date.now()}`,type:"message",role:"user",content:X,status:"completed",created_at:pe};S([...de.getState().chatItems,ye])}N(!0);const Ne={id:`assistant-${Date.now()}`,type:"message",role:"assistant",content:[],status:"in_progress",created_at:pe};S([...de.getState().chatItems,Ne]);try{let ye=r;if(!ye)try{ye=await Je.createConversation({agent_id:e.id}),x(ye),b([ye,...de.getState().availableConversations]),O(null)}catch(Re){const Ve=Re instanceof Error?Re.message:"Failed to create conversation";O({message:Ve,type:"conversation_creation_error"}),j(!1),N(!1);return}ye?.id&&Je.clearStreamingState(ye.id);const Oe={input:ne.input,conversation_id:ye?.id};K.current="";const Se=W(),Ie=Je.streamAgentExecutionOpenAI(e.id,Oe,Se);for await(const Re of Ie){if(n(Re),Re.type==="response.completed"){const _e=Re.response?.usage;_e&&(Y.current={input_tokens:_e.input_tokens,output_tokens:_e.output_tokens,total_tokens:_e.total_tokens});continue}if(Re.type==="response.failed"){const _e=Re.response?.error;let $e="Request failed";_e&&(typeof _e=="object"&&"message"in _e?($e=_e.message,"code"in _e&&_e.code&&($e+=` (Code: ${_e.code})`)):typeof _e=="string"&&($e=_e));const Fe=de.getState().chatItems;S(Fe.map(Nt=>Nt.id===Ne.id&&Nt.type==="message"?{...Nt,content:[{type:"text",text:K.current||$e}],status:"incomplete"}:Nt)),N(!1);return}if(Re.type==="response.function_approval.requested"){const Ve=Re;E([...de.getState().pendingApprovals,{request_id:Ve.request_id,function_call:Ve.function_call}]);const _e=de.getState().chatItems;S(_e.map($e=>$e.id===Ne.id&&$e.type==="message"?{...$e,content:[...$e.content,{type:"function_approval_request",request_id:Ve.request_id,status:"pending",function_call:Ve.function_call}],status:"in_progress"}:$e));continue}if(Re.type==="response.function_call_arguments.delta"){const Ve=Re,_e=de.getState().chatItems;S(_e.map($e=>$e.type==="function_call"&&$e.call_id===Ve.item_id?{...$e,arguments:($e.arguments||"")+(Ve.delta||"")}:$e));continue}if(Re.type==="response.function_result.complete"){const Ve=Re,_e={id:`result-${Date.now()}`,type:"function_call_output",call_id:Ve.call_id,output:Ve.output,status:Ve.status==="completed"?"completed":"incomplete",created_at:Math.floor(Date.now()/1e3)},$e=de.getState().chatItems;S([...$e,_e]);continue}if(Re.type==="error"){const _e=Re.message||"An error occurred",$e=de.getState().chatItems;S($e.map(Fe=>Fe.id===Ne.id&&Fe.type==="message"?{...Fe,content:[{type:"text",text:_e}],status:"incomplete"}:Fe)),N(!1);return}if(Re.type==="response.output_item.added"){const _e=Re.item;if(_e.type==="function_call"){const Fe={id:_e.id||`call-${Date.now()}`,type:"function_call",name:_e.name,arguments:_e.arguments||"",call_id:_e.call_id,status:(_e.status==="failed"||_e.status==="cancelled"?"incomplete":_e.status)||"in_progress",created_at:Math.floor(Date.now()/1e3)},Nt=de.getState().chatItems;S([...Nt,Fe]);continue}const $e=de.getState().chatItems;S($e.map(Fe=>{if(Fe.id===Ne.id&&Fe.type==="message"){const Nt=Fe.content;let yt=null;if(_e.type==="output_image"?yt={type:"output_image",image_url:_e.image_url,alt_text:_e.alt_text,mime_type:_e.mime_type}:_e.type==="output_file"?yt={type:"output_file",filename:_e.filename,file_url:_e.file_url,file_data:_e.file_data,mime_type:_e.mime_type}:_e.type==="output_data"&&(yt={type:"output_data",data:_e.data,mime_type:_e.mime_type,description:_e.description}),yt)return{...Fe,content:[...Nt,yt],status:"in_progress"}}return Fe}));continue}if(Re.type==="response.output_text.delta"&&"delta"in Re&&Re.delta){K.current+=Re.delta;const Ve=de.getState().chatItems;S(Ve.map(_e=>{if(_e.id===Ne.id&&_e.type==="message"){const $e=_e.content.filter(Fe=>Fe.type!=="text");return{..._e,content:[...$e,{type:"text",text:K.current}],status:"in_progress"}}return _e}))}}const Xe=Y.current,He=de.getState().chatItems;S(He.map(Re=>Re.id===Ne.id&&Re.type==="message"?{...Re,status:"completed",usage:Xe||void 0}:Re)),N(!1),Xe&&M(Xe.total_tokens),Y.current=null}catch(ye){if(Hu(ye)){ee(!0);const Oe=de.getState().chatItems;S(Oe.map(Se=>Se.id===Ne.id&&Se.type==="message"?{...Se,status:K.current?"completed":"incomplete",content:Se.content}:Se))}else{const Oe=de.getState().chatItems;S(Oe.map(Se=>Se.id===Ne.id&&Se.type==="message"?{...Se,content:[{type:"text",text:`Error: ${ye instanceof Error?ye.message:"Failed to get response"}`}],status:"incomplete"}:Se))}N(!1),U()}},[e,r,n,S,N,x,b,E,M,W,U]),be=async ne=>{if(!(!e||ne.length===0)){V.current=!0,ee(!1),j(!0);try{await ve({input:[{type:"message",role:"user",content:ne}],conversation_id:r?.id})}finally{j(!1)}}};return o.jsxs("div",{className:"flex h-[calc(100vh-3.5rem)] flex-col relative",...H,children:[k&&o.jsx("div",{className:"absolute inset-0 z-50 bg-blue-50/95 dark:bg-blue-950/95 backdrop-blur-sm flex items-center justify-center border-2 border-dashed border-blue-400 dark:border-blue-500 rounded-lg m-2",children:o.jsxs("div",{className:"text-center p-8",children:[o.jsx("div",{className:"text-blue-600 dark:text-blue-400 text-lg font-medium mb-2",children:"Drop files here"}),o.jsx("div",{className:"text-blue-500/80 dark:text-blue-400/70 text-sm",children:"Images, PDFs, audio, and other files"})]})}),o.jsxs("div",{className:"border-b pb-2 p-4 flex-shrink-0",children:[o.jsxs("div",{className:"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3 mb-3",children:[o.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[o.jsx("h2",{className:"font-semibold text-sm truncate",children:o.jsxs("div",{className:"flex items-center gap-2",children:[o.jsx(Hs,{className:"h-4 w-4 flex-shrink-0"}),o.jsx("span",{className:"truncate",children:y.enabled?`Chat with ${y.model}`:`Chat with ${e.name||e.id}`})]})}),!y.enabled&&m==="developer"&&o.jsxs(o.Fragment,{children:[o.jsx(Te,{variant:"ghost",size:"sm",onClick:()=>R(!0),className:"h-6 w-6 p-0 flex-shrink-0",title:"View agent details",children:o.jsx(va,{className:"h-4 w-4"})}),e.source!=="in_memory"&&o.jsx(Te,{variant:"ghost",size:"sm",onClick:te,disabled:P,className:"h-6 w-6 p-0 flex-shrink-0",title:P?"Reloading...":"Reload entity code (hot reload)",children:o.jsx(Fp,{className:`h-4 w-4 ${P?"animate-spin":""}`})})]})]}),o.jsxs("div",{className:"flex flex-col sm:flex-row items-stretch sm:items-center gap-2 flex-shrink-0",children:[o.jsxs(bd,{value:r?.id||"",onValueChange:ie,disabled:f||d,children:[o.jsx(Nd,{className:"w-full sm:w-64",children:o.jsx(wd,{placeholder:f?"Loading...":a.length===0?"No conversations":r?`Conversation ${r.id.slice(-8)}`:"Select conversation",children:r&&o.jsxs("div",{className:"flex items-center gap-2 text-xs",children:[o.jsxs("span",{children:["Conversation ",r.id.slice(-8)]}),h.total_tokens>0&&o.jsxs(o.Fragment,{children:[o.jsx("span",{className:"text-muted-foreground",children:"•"}),o.jsxs("span",{className:"text-muted-foreground",children:[h.total_tokens>=1e3?`${(h.total_tokens/1e3).toFixed(1)}k`:h.total_tokens," ","tokens"]})]})]})})}),o.jsx(Sd,{children:a.map(ne=>o.jsx(jd,{value:ne.id,children:o.jsxs("div",{className:"flex items-center justify-between w-full",children:[o.jsxs("span",{children:["Conversation ",ne.id.slice(-8)]}),ne.created_at&&o.jsx("span",{className:"text-xs text-muted-foreground ml-3",children:new Date(ne.created_at*1e3).toLocaleDateString()})]})},ne.id))})]}),o.jsx(Te,{variant:"outline",size:"icon",onClick:()=>r&&ue(r.id),disabled:!r||d,title:r?`Delete Conversation ${r.id.slice(-8)}`:"No conversation selected",children:o.jsx(Gp,{className:"h-4 w-4"})}),o.jsxs(Te,{variant:"outline",size:"lg",onClick:fe,disabled:!e||d,className:"whitespace-nowrap ",children:[o.jsx(qp,{className:"h-4 w-4 mr-2"}),o.jsx("span",{className:"hidden md:inline",children:" New Conversation"})]})]})]}),y.enabled?o.jsx("p",{className:"text-sm text-muted-foreground",children:"Using OpenAI model directly. Local agent tools and instructions are not applied."}):e.description&&o.jsx("p",{className:"text-sm text-muted-foreground",children:e.description})]}),D&&o.jsxs("div",{className:"mx-4 mt-2 p-3 bg-destructive/10 border border-destructive/30 rounded-md flex items-start gap-2",children:[o.jsx(is,{className:"h-4 w-4 text-destructive mt-0.5 flex-shrink-0"}),o.jsxs("div",{className:"flex-1 min-w-0",children:[o.jsx("div",{className:"text-sm font-medium text-destructive",children:"Failed to Create Conversation"}),o.jsx("div",{className:"text-xs text-destructive/90 mt-1 break-words",children:D.message}),D.code&&o.jsxs("div",{className:"text-xs text-destructive/70 mt-1",children:["Error Code: ",D.code]})]}),o.jsx("button",{onClick:()=>O(null),className:"text-destructive hover:text-destructive/80 flex-shrink-0",title:"Dismiss error",children:o.jsx(wa,{className:"h-4 w-4"})})]}),o.jsx(ls,{className:"flex-1 p-4 h-0",ref:C,children:o.jsxs("div",{className:"space-y-4",children:[l.length===0?o.jsxs("div",{className:"flex flex-col items-center justify-center h-32 text-center",children:[o.jsxs("div",{className:"text-muted-foreground text-sm",children:["Start a conversation with"," ",e.name||e.id]}),o.jsx("div",{className:"text-xs text-muted-foreground mt-1",children:"Type a message below to begin"})]}):(()=>{const ne=[],he=new Map,X=new Map;let pe=null;const Ne=[],ye=[];for(let Oe=0;Oe0){const Ie=he.get(Se.id)||[];Ie.push(...Ne),he.set(Se.id,Ie),Ne.length=0}if(ye.length>0){const Ie=X.get(Se.id)||[];Ie.push(...ye),X.set(Se.id,Ie),ye.length=0}pe=Se.id}else if(Se.type==="function_call")if(pe){const Ie=he.get(pe)||[];Ie.push(Se),he.set(pe,Ie)}else Ne.push(Se);else if(Se.type==="function_call_output")if(pe){const Ie=X.get(pe)||[];Ie.push(Se),X.set(pe,Ie)}else ye.push(Se);else Se.type==="message"&&Se.role==="user"&&(pe=null)}for(const Oe of l)if(Oe.type==="message"){const Se=he.get(Oe.id)||[],Ie=X.get(Oe.id)||[];ne.push(o.jsx(PR,{item:Oe,toolCalls:Se,toolResults:Ie},Oe.id))}return ne})(),Q&&!c&&o.jsx("div",{className:"px-4 py-2",children:o.jsx("div",{className:"border rounded-lg border-orange-500/40 bg-orange-500/5 dark:bg-orange-500/10",children:o.jsxs("div",{className:"px-4 py-3 flex items-center gap-2",children:[o.jsx(cd,{className:"w-4 h-4 text-orange-500 dark:text-orange-400 fill-current"}),o.jsx("span",{className:"font-medium text-sm text-orange-700 dark:text-orange-300",children:"Response stopped by user"})]})})}),o.jsx("div",{ref:$})]})}),g.length>0&&o.jsx("div",{className:"border-t bg-amber-50 dark:bg-amber-950/20 p-4 flex-shrink-0",children:o.jsxs("div",{className:"flex items-start gap-3",children:[o.jsx(is,{className:"h-5 w-5 text-amber-600 dark:text-amber-500 mt-0.5 flex-shrink-0"}),o.jsxs("div",{className:"flex-1 min-w-0",children:[o.jsx("h4",{className:"font-medium text-sm mb-2",children:"Approval Required"}),o.jsx("div",{className:"space-y-2",children:g.map(ne=>o.jsxs("div",{className:"bg-white dark:bg-gray-900 rounded-lg p-3 border border-amber-200 dark:border-amber-900",children:[o.jsxs("div",{className:"font-mono text-xs mb-3 break-all",children:[o.jsx("span",{className:"text-blue-600 dark:text-blue-400 font-semibold",children:ne.function_call.name}),o.jsx("span",{className:"text-gray-500",children:"("}),o.jsx("span",{className:"text-gray-700 dark:text-gray-300",children:JSON.stringify(ne.function_call.arguments)}),o.jsx("span",{className:"text-gray-500",children:")"})]}),o.jsxs("div",{className:"flex gap-2",children:[o.jsxs(Te,{size:"sm",onClick:async()=>{const he=await xe(ne.request_id,!0);he&&await ve(he)},variant:"default",className:"flex-1 sm:flex-none",children:[o.jsx(vo,{className:"h-4 w-4 mr-1"}),"Approve"]}),o.jsxs(Te,{size:"sm",onClick:async()=>{const he=await xe(ne.request_id,!1);he&&await ve(he)},variant:"outline",className:"flex-1 sm:flex-none",children:[o.jsx(wa,{className:"h-4 w-4 mr-1"}),"Reject"]})]})]},ne.request_id))})]})]})}),o.jsx("div",{className:"border-t flex-shrink-0",children:o.jsx("div",{className:"p-4",children:o.jsx(PN,{onSubmit:be,isSubmitting:d,isStreaming:c,onCancel:B,isCancelling:G,placeholder:`Message ${e.name||e.id}... (Shift+Enter for new line)`,showFileUpload:!0,entityName:e.name||e.id,disabled:!e,externalFiles:L,onExternalFilesProcessed:I})})}),o.jsx($R,{agent:e,open:T,onOpenChange:R})]})}function Gv({message:e="Loading...",description:n,size:r="md",className:a,fullPage:l=!1}){const c=o.jsxs("div",{className:Ze("flex flex-col items-center justify-center gap-3",l?"min-h-[50vh]":"py-8",a),children:[o.jsx(Ou,{size:r,className:"text-muted-foreground"}),o.jsxs("div",{className:"text-center space-y-1",children:[o.jsx("p",{className:Ze("font-medium text-muted-foreground",r==="sm"&&"text-sm",r==="lg"&&"text-lg"),children:e}),n&&o.jsx("p",{className:"text-sm text-muted-foreground/80",children:n})]})]});return l?o.jsx("div",{className:"flex items-center justify-center min-h-screen bg-background",children:c}):c}var _d="Checkbox",[UR,I7]=us(_d),[VR,og]=UR(_d);function qR(e){const{__scopeCheckbox:n,checked:r,children:a,defaultChecked:l,disabled:c,form:d,name:f,onCheckedChange:m,required:h,value:g="on",internal_do_not_use_render:y}=e,[x,b]=io({prop:r,defaultProp:l??!1,onChange:m,caller:_d}),[S,N]=w.useState(null),[j,_]=w.useState(null),M=w.useRef(!1),E=S?!!d||!!S.closest("form"):!0,T={checked:x,disabled:c,setChecked:b,control:S,setControl:N,name:f,form:d,value:g,hasConsumerStoppedPropagationRef:M,required:h,defaultChecked:Sr(l)?!1:l,isFormControl:E,bubbleInput:j,setBubbleInput:_};return o.jsx(VR,{scope:n,...T,children:FR(y)?y(T):a})}var h2="CheckboxTrigger",p2=w.forwardRef(({__scopeCheckbox:e,onKeyDown:n,onClick:r,...a},l)=>{const{control:c,value:d,disabled:f,checked:m,required:h,setControl:g,setChecked:y,hasConsumerStoppedPropagationRef:x,isFormControl:b,bubbleInput:S}=og(h2,e),N=rt(l,g),j=w.useRef(m);return w.useEffect(()=>{const _=c?.form;if(_){const M=()=>y(j.current);return _.addEventListener("reset",M),()=>_.removeEventListener("reset",M)}},[c,y]),o.jsx(Ye.button,{type:"button",role:"checkbox","aria-checked":Sr(m)?"mixed":m,"aria-required":h,"data-state":w2(m),"data-disabled":f?"":void 0,disabled:f,value:d,...a,ref:N,onKeyDown:Ae(n,_=>{_.key==="Enter"&&_.preventDefault()}),onClick:Ae(r,_=>{y(M=>Sr(M)?!0:!M),S&&b&&(x.current=_.isPropagationStopped(),x.current||_.stopPropagation())})})});p2.displayName=h2;var g2=w.forwardRef((e,n)=>{const{__scopeCheckbox:r,name:a,checked:l,defaultChecked:c,required:d,disabled:f,value:m,onCheckedChange:h,form:g,...y}=e;return o.jsx(qR,{__scopeCheckbox:r,checked:l,defaultChecked:c,disabled:f,required:d,onCheckedChange:h,name:a,form:g,value:m,internal_do_not_use_render:({isFormControl:x})=>o.jsxs(o.Fragment,{children:[o.jsx(p2,{...y,ref:n,__scopeCheckbox:r}),x&&o.jsx(b2,{__scopeCheckbox:r})]})})});g2.displayName=_d;var x2="CheckboxIndicator",y2=w.forwardRef((e,n)=>{const{__scopeCheckbox:r,forceMount:a,...l}=e,c=og(x2,r);return o.jsx(Fn,{present:a||Sr(c.checked)||c.checked===!0,children:o.jsx(Ye.span,{"data-state":w2(c.checked),"data-disabled":c.disabled?"":void 0,...l,ref:n,style:{pointerEvents:"none",...e.style}})})});y2.displayName=x2;var v2="CheckboxBubbleInput",b2=w.forwardRef(({__scopeCheckbox:e,...n},r)=>{const{control:a,hasConsumerStoppedPropagationRef:l,checked:c,defaultChecked:d,required:f,disabled:m,name:h,value:g,form:y,bubbleInput:x,setBubbleInput:b}=og(v2,e),S=rt(r,b),N=sg(c),j=Ap(a);w.useEffect(()=>{const M=x;if(!M)return;const E=window.HTMLInputElement.prototype,R=Object.getOwnPropertyDescriptor(E,"checked").set,D=!l.current;if(N!==c&&R){const O=new Event("click",{bubbles:D});M.indeterminate=Sr(c),R.call(M,Sr(c)?!1:c),M.dispatchEvent(O)}},[x,N,c,l]);const _=w.useRef(Sr(c)?!1:c);return o.jsx(Ye.input,{type:"checkbox","aria-hidden":!0,defaultChecked:d??_.current,required:f,disabled:m,name:h,value:g,form:y,...n,tabIndex:-1,ref:S,style:{...n.style,...j,position:"absolute",pointerEvents:"none",opacity:0,margin:0,transform:"translateX(-100%)"}})});b2.displayName=v2;function FR(e){return typeof e=="function"}function Sr(e){return e==="indeterminate"}function w2(e){return Sr(e)?"indeterminate":e?"checked":"unchecked"}function to({className:e,...n}){return o.jsx(g2,{"data-slot":"checkbox",className:Ze("peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",e),...n,children:o.jsx(y2,{"data-slot":"checkbox-indicator",className:"flex items-center justify-center text-current transition-none",children:o.jsx(vo,{className:"size-3.5"})})})}function YR(e){return["name","title","id","key","label","type","status","tag","category","code","username","password"].includes(e.toLowerCase())}function xr({name:e,schema:n,value:r,onChange:a,isRequired:l=!1}){const{type:c,description:d,enum:f,default:m}=n,h=n.format==="textarea"||d&&d.length>100||c==="string"&&!f&&!YR(e),g=h||d&&d.length>150,y=h||d&&d.length>80||c==="array",x=(()=>{switch(c){case"string":return f?o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsxs(bd,{value:typeof r=="string"&&r?r:typeof m=="string"?m:f[0],onValueChange:S=>a(S),children:[o.jsx(Nd,{children:o.jsx(wd,{placeholder:`Select ${e}`})}),o.jsx(Sd,{children:f.map(S=>o.jsx(jd,{value:S,children:S},S))})]}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]}):h||d&&d.length>100?o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(ro,{id:e,value:typeof r=="string"?r:"",onChange:S=>a(S.target.value),placeholder:typeof m=="string"?m:`Enter ${e}`,rows:h?4:2,className:"min-w-[300px] w-full"}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]}):o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(An,{id:e,type:"text",value:typeof r=="string"?r:"",onChange:S=>a(S.target.value),placeholder:typeof m=="string"?m:`Enter ${e}`}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"number":case"integer":return o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(An,{id:e,type:"number",value:typeof r=="number"?r:"",onChange:S=>{const N=c==="integer"?parseInt(S.target.value,10):parseFloat(S.target.value);a(isNaN(N)?"":N)},placeholder:typeof m=="number"?m.toString():`Enter ${e}`}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"boolean":return o.jsxs("div",{className:"space-y-2",children:[o.jsxs("div",{className:"flex items-center space-x-2",children:[o.jsx(to,{id:e,checked:!!r,onCheckedChange:S=>a(S)}),o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]})]}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"array":return o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(ro,{id:e,value:Array.isArray(r)?r.join(", "):typeof r=="string"?r:"",onChange:S=>{const N=S.target.value.split(",").map(j=>j.trim()).filter(j=>j.length>0);a(N)},placeholder:"Enter items separated by commas",rows:2}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]});case"object":default:return o.jsxs("div",{className:"space-y-2",children:[o.jsxs(xt,{htmlFor:e,children:[e,l&&o.jsx("span",{className:"text-destructive ml-1",children:"*"})]}),o.jsx(ro,{id:e,value:typeof r=="object"&&r!==null?JSON.stringify(r,null,2):typeof r=="string"?r:"",onChange:S=>{try{const N=JSON.parse(S.target.value);a(N)}catch{a(S.target.value)}},placeholder:'{"key": "value"}',rows:3}),d&&o.jsx("p",{className:"text-sm text-muted-foreground",children:d})]})}})(),b=()=>g?"md:col-span-2 lg:col-span-3 xl:col-span-4":y?"xl:col-span-2":"";return o.jsx("div",{className:b(),children:x})}function GR({inputSchema:e,inputTypeName:n,onSubmit:r,isSubmitting:a=!1,className:l}){const[c,d]=w.useState(!1),[f,m]=w.useState(!1),h=l?.includes("embedded"),[g,y]=w.useState({}),[x,b]=w.useState(!1),S=e.properties||{},N=Object.keys(S),j=e.required||[],_=e.type==="string"&&!e.enum,M=N.filter(k=>!j.includes(k)),E=j.includes("role")&&M.some(k=>["text","message","content"].includes(k))&&S.role?.type==="string",T=N.filter(k=>j.includes(k)&&!(E&&k==="role")),R=M,D=E?[...R].sort((k,L)=>{const I=H=>["text","message","content"].includes(H)?1:0;return I(L)-I(k)}):R,P=Math.max(0,(E?1:6)-T.length),q=D.slice(0,P),Q=D.slice(P),ee=Q.length>0,G=T.length>0,W=_?g.value!==void 0&&g.value!=="":j.length>0?j.every(k=>E&&k==="role"&&g.role==="user"?!0:g[k]!==void 0&&g[k]!==""):Object.keys(g).length>0;w.useEffect(()=>{if(e.type==="string")y({value:e.default||""});else if(e.type==="object"&&e.properties){const k={};Object.entries(e.properties).forEach(([L,I])=>{I.default!==void 0?k[L]=I.default:I.enum&&I.enum.length>0&&(k[L]=I.enum[0])}),E&&!k.role&&(k.role="user"),y(k)}},[e,E]);const B=k=>{if(k.preventDefault(),b(!0),e.type==="string")r({input:g.value||""});else if(e.type==="object"){const L=e.properties||{},I=Object.keys(L);if(I.length===1){const H=I[0];r({[H]:g[H]||""})}else{const H={};Object.keys(g).forEach(C=>{const $=g[C];(j.includes(C)||$!==void 0&&$!==""&&$!==null)&&(H[C]=$)}),r(H)}}else r(g);h||d(!1),b(!1)},U=(k,L)=>{y(I=>({...I,[k]:L}))};return h?o.jsxs("form",{onSubmit:B,className:l,children:[o.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-4",children:[_&&o.jsx(xr,{name:"Input",schema:e,value:g.value,onChange:k=>U("value",k),isRequired:!1}),!_&&o.jsxs(o.Fragment,{children:[T.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!0},k)),G&&R.length>0&&o.jsx("div",{className:"sm:col-span-2 border-t border-border my-2"}),q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k)),ee&&o.jsx("div",{className:"sm:col-span-2",children:o.jsx(Te,{type:"button",variant:"ghost",size:"sm",onClick:()=>m(!f),className:"w-full justify-center gap-2",children:f?o.jsxs(o.Fragment,{children:[o.jsx(Mu,{className:"h-4 w-4"}),"Hide ",Q.length," optional field",Q.length!==1?"s":""]}):o.jsxs(o.Fragment,{children:[o.jsx(Gt,{className:"h-4 w-4"}),"Show ",Q.length," optional field",Q.length!==1?"s":""]})})}),f&&Q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k))]})]}),o.jsx("div",{className:"flex gap-2 mt-4 justify-end",children:o.jsxs(Te,{type:"submit",disabled:x||!W,size:"default",children:[o.jsx(Zi,{className:"h-4 w-4"}),x?"Running...":"Run Workflow"]})})]}):o.jsxs(o.Fragment,{children:[o.jsxs("div",{className:Ze("flex flex-col",l),children:[o.jsxs("div",{className:"border-b border-border px-4 py-3 bg-muted",children:[o.jsx(zN,{className:"text-sm mb-3",children:"Run Workflow"}),o.jsxs(Te,{onClick:()=>d(!0),disabled:a,className:"w-full",size:"default",children:[o.jsx(Zi,{className:"h-4 w-4 mr-2"}),a?"Running...":"Run Workflow"]})]}),o.jsxs("div",{className:"px-4 py-3",children:[o.jsxs("div",{className:"text-sm text-muted-foreground",children:[o.jsx("strong",{children:"Input Type:"})," ",o.jsx("code",{className:"bg-muted px-1 py-0.5 rounded",children:n}),e.type==="object"&&e.properties&&o.jsxs("span",{className:"ml-2",children:["(",Object.keys(e.properties).length," field",Object.keys(e.properties).length!==1?"s":"",")"]})]}),o.jsx("p",{className:"text-xs text-muted-foreground mt-2",children:'Click "Run Workflow" to configure inputs and execute'})]})]}),o.jsx(kr,{open:c,onOpenChange:d,children:o.jsxs(Ar,{className:"w-full max-w-md sm:max-w-lg md:max-w-2xl lg:max-w-4xl xl:max-w-5xl max-h-[90vh] flex flex-col",children:[o.jsxs(Mr,{children:[o.jsx(Tr,{children:"Run Workflow"}),o.jsx(bo,{onClose:()=>d(!1)})]}),o.jsx("div",{className:"px-8 py-4 border-b flex-shrink-0",children:o.jsx("div",{className:"text-sm text-muted-foreground",children:o.jsxs("div",{className:"flex items-center gap-3",children:[o.jsx("span",{className:"font-medium",children:"Input Type:"}),o.jsx("code",{className:"bg-muted px-3 py-1 text-xs font-mono",children:n}),e.type==="object"&&o.jsxs("span",{className:"text-xs text-muted-foreground",children:[N.length," field",N.length!==1?"s":""]})]})})}),o.jsx("div",{className:"px-8 py-6 overflow-y-auto flex-1 min-h-0",children:o.jsx("form",{id:"workflow-modal-form",onSubmit:B,children:o.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 md:gap-8 max-w-none",children:[_&&o.jsxs("div",{className:"md:col-span-2 lg:col-span-3 xl:col-span-4",children:[o.jsx(xr,{name:"Input",schema:e,value:g.value,onChange:k=>U("value",k),isRequired:!1}),e.description&&o.jsx("p",{className:"text-sm text-muted-foreground mt-2",children:e.description})]}),!_&&o.jsxs(o.Fragment,{children:[T.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!0},k)),G&&R.length>0&&o.jsx("div",{className:"md:col-span-2 lg:col-span-3 xl:col-span-4",children:o.jsx("div",{className:"border-t border-border"})}),q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k)),ee&&o.jsx("div",{className:"md:col-span-2 lg:col-span-3 xl:col-span-4",children:o.jsx(Te,{type:"button",variant:"ghost",size:"sm",onClick:()=>m(!f),className:"w-full justify-center gap-2",children:f?o.jsxs(o.Fragment,{children:[o.jsx(Mu,{className:"h-4 w-4"}),"Hide ",Q.length," optional field",Q.length!==1?"s":""]}):o.jsxs(o.Fragment,{children:[o.jsx(Gt,{className:"h-4 w-4"}),"Show ",Q.length," optional field",Q.length!==1?"s":""]})})}),f&&Q.map(k=>o.jsx(xr,{name:k,schema:S[k],value:g[k],onChange:L=>U(k,L),isRequired:!1},k))]})]})})}),o.jsx("div",{className:"px-8 py-4 border-t flex-shrink-0",children:o.jsxs(u5,{children:[o.jsx(Te,{variant:"outline",onClick:()=>d(!1),disabled:x,children:"Cancel"}),o.jsxs(Te,{type:"submit",form:"workflow-modal-form",disabled:x||!W,children:[o.jsx(Zi,{className:"h-4 w-4 mr-2"}),x?"Running...":"Run Workflow"]})]})})]})})]})}function XR(e,n,r="LR"){if(e.length===0)return e;if(e.length===1)return e.map(S=>({...S,position:{x:0,y:0}}));const a=new Map,l=new Map;e.forEach(S=>{a.set(S.id,[]),l.set(S.id,[])}),n.forEach(S=>{a.get(S.source)?.push(S.target),l.get(S.target)?.push(S.source)});const c=e.filter(S=>(l.get(S.id)||[]).length===0);c.length===0&&c.push(e[0]);const d=220,f=120,m=r==="LR"?350:280,h=r==="TB"?250:180,g=new Map,y=new Map,x=[],b=new Set;for(c.forEach(S=>{x.push({nodeId:S.id,level:0})});x.length>0;){const{nodeId:S,level:N}=x.shift();if(b.has(S))continue;b.add(S),y.has(N)||y.set(N,[]),y.get(N).push(S),(a.get(S)||[]).forEach(_=>{b.has(_)||x.push({nodeId:_,level:N+1})})}return e.forEach(S=>{if(!b.has(S.id)){const j=Math.max(...Array.from(y.keys()),-1)+1;y.has(j)||y.set(j,[]),y.get(j).push(S.id)}}),y.forEach((S,N)=>{const j=S.length;S.forEach((_,M)=>{let E,T;r==="LR"?(E=N*m,T=-((j-1)*h)/2+M*h):(T=N*h,E=-((j-1)*m)/2+M*m),g.set(_,{x:E,y:T,level:N})})}),e.map(S=>{const N=g.get(S.id)||{x:0,y:0};return{...S,position:{x:N.x-d/2,y:N.y-f/2}}})}function ZR(e){return typeof e=="object"&&e!==null&&"id"in e&&"edge_groups"in e&&"executors"in e&&"start_executor_id"in e&&"max_iterations"in e&&typeof e.id=="string"&&Array.isArray(e.edge_groups)&&typeof e.executors=="object"&&typeof e.start_executor_id=="string"&&typeof e.max_iterations=="number"}function N2(e){return ZR(e)?e:null}function WR(e){if(!e||e.type!=="object"||!e.properties)return!1;const n=e.properties,r="text"in n&&n.text?.type==="string",a="role"in n&&n.role?.type==="string";return!!(r&&a||Object.keys(n).length===1&&r)}function yu(e,n=50,r="..."){return e.length<=n?e:e.substring(0,n)+r}function tp(e,n,r){if(!e)return console.warn("convertWorkflowDumpToNodes: workflowDump is undefined"),[];const a=N2(e);let l,c;return a?(l=Object.values(a.executors).map(f=>({id:f.id,type:f.type,name:f.name||f.id,description:f.description,config:f.config})),c=a.start_executor_id):(l=S2(e),c=e?.start_executor_id),!l||!Array.isArray(l)||l.length===0?(console.warn("No executors found in workflow dump. Available keys:",Object.keys(e)),[]):l.map(f=>({id:f.id,type:"executor",position:{x:0,y:0},data:{executorId:f.id,executorType:f.type,name:f.name||f.id,state:"pending",isStartNode:f.id===c,layoutDirection:r||"LR",onNodeClick:n}}))}function np(e){if(!e)return console.warn("convertWorkflowDumpToEdges: workflowDump is undefined"),[];const n=N2(e);let r;return n?(r=[],n.edge_groups.forEach(l=>{l.type!=="InternalEdgeGroup"&&l.edges.forEach(c=>{c.source_id.startsWith("internal:")||r.push({source:c.source_id,target:c.target_id,condition:c.condition_name})})})):r=j2(e),!r||!Array.isArray(r)||r.length===0?(console.warn("No connections found in workflow dump. Available keys:",Object.keys(e)),[]):r.map(l=>{const c=l.source===l.target;return{id:`${l.source}-${l.target}`,source:l.source,target:l.target,sourceHandle:"source",targetHandle:"target",type:c?"selfLoop":"default",animated:!1,style:{stroke:"#6b7280",strokeWidth:2}}})}function S2(e){if(e.executors&&typeof e.executors=="object"&&!Array.isArray(e.executors)){const a=e.executors;return Object.entries(a).map(([l,c])=>({id:l,type:c.type_||c.type||"executor",name:c.name||l,description:c.description,config:c.config}))}const n=["executors","agents","steps","nodes"];for(const a of n)if(e[a]&&Array.isArray(e[a]))return e[a];if(e.config&&typeof e.config=="object")return S2(e.config);const r=[];return Object.entries(e).forEach(([a,l])=>{if(typeof l=="object"&&l!==null&&("type"in l||"type_"in l)){const c=l;r.push({id:a,type:c.type_||c.type||"executor",name:c.name||a,description:c.description,config:c.config})}}),r}function j2(e){if(e.edge_groups&&Array.isArray(e.edge_groups)){const r=[];return e.edge_groups.forEach(a=>{if(typeof a=="object"&&a!==null&&"edges"in a){const l=a.edges;Array.isArray(l)&&l.forEach(c=>{if(typeof c=="object"&&c!==null&&"source_id"in c&&"target_id"in c){const d=c;r.push({source:d.source_id,target:d.target_id,condition:d.condition_name||void 0})}})}}),r}const n=["connections","edges","transitions","links"];for(const r of n)if(e[r]&&Array.isArray(e[r]))return e[r];return e.config&&typeof e.config=="object"?j2(e.config):[]}function sp(e,n,r="LR"){return XR(e,n,r)}function KR(e,n){const r={};let a=!1;const l={};return e.forEach(c=>{if(c.type==="response.output_item.added"||c.type==="response.output_item.done"){const d=c.item;if(d&&d.type==="executor_action"&&d.executor_id){const f=d.executor_id,m=d.id;if(c.type==="response.output_item.added"&&(l[f]=m),!(l[f]===m)&&c.type==="response.output_item.done")return;let g="pending",y;c.type==="response.output_item.added"?g="running":c.type==="response.output_item.done"&&(d.status==="completed"?g="completed":d.status==="failed"?(g="failed",y=d.error?typeof d.error=="string"?d.error:JSON.stringify(d.error):"Execution failed"):d.status==="cancelled"&&(g="cancelled")),r[f]={nodeId:f,state:g,data:d.result,error:y,timestamp:new Date().toISOString()}}}else if(c.type==="response.created"||c.type==="response.in_progress")a=!0;else if(c.type==="response.workflow_event.completed"&&"data"in c&&c.data){const f=c.data,m=f.executor_id,h=f.event_type,g=f.data;let y="pending",x;h==="ExecutorInvokedEvent"?y="running":h==="ExecutorCompletedEvent"?y="completed":h?.includes("Error")||h?.includes("Failed")?(y="failed",x=typeof g=="string"?g:"Execution failed"):h?.includes("Cancel")?y="cancelled":h==="WorkflowCompletedEvent"||h==="WorkflowOutputEvent"?y="completed":h==="WorkflowStartedEvent"&&(a=!0),m&&(r[m]={nodeId:m,state:y,data:g,error:x,timestamp:new Date().toISOString()})}}),a&&n&&!r[n]&&(e.some(d=>{if(d.type==="response.output_item.done"){const f=d.item;return f&&f.type==="executor_action"&&f.executor_id===n}if(d.type==="response.workflow_event.completed"&&"data"in d&&d.data){const f=d.data;return f.executor_id===n&&(f.event_type==="ExecutorCompletedEvent"||f.event_type==="ExecutorFailedEvent"||f.event_type?.includes("Error")||f.event_type?.includes("Failed"))}return!1})||(r[n]={nodeId:n,state:"running",data:void 0,error:void 0,timestamp:new Date().toISOString()})),r}function QR(e,n,r=!0){return e.map(a=>{const l=n[a.id];return l?{...a,data:{...a.data,state:l.state,outputData:l.data,error:l.error,isStreaming:r,layoutDirection:a.data.layoutDirection}}:a})}function JR(e){const n={};return e.forEach(a=>{if(a.type==="response.output_item.added"||a.type==="response.output_item.done"){const l=a.item;if(l&&l.type==="executor_action"&&l.executor_id){const c=l.executor_id;n[c]={lastEvent:a.type==="response.output_item.added"?"ExecutorInvokedEvent":"ExecutorCompletedEvent",timestamp:new Date().toISOString()}}}else if(a.type==="response.workflow_event.completed"&&"data"in a&&a.data){const c=a.data,d=c.executor_id,f=c.event_type;d&&(f==="ExecutorInvokedEvent"||f==="ExecutorCompletedEvent")&&(n[d]={lastEvent:f,timestamp:new Date().toISOString()})}}),Object.entries(n).filter(([,a])=>a.lastEvent==="ExecutorInvokedEvent").map(([a])=>a)}function eD(e,n){const r=JR(n),a={};return n.forEach(l=>{if(l.type==="response.workflow_event.completed"&&"data"in l&&l.data){const d=l.data,f=d.executor_id,m=d.event_type;f&&m&&(a[f]||(a[f]={completed:!1,invoked:!1}),m==="ExecutorInvokedEvent"?a[f].invoked=!0:m==="ExecutorCompletedEvent"&&(a[f].completed=!0))}}),e.map(l=>{const c=a[l.source],d=a[l.target],f=r.includes(l.target);let m={...l.style},h=!1;return c?.completed&&f?(m={stroke:"#643FB2",strokeWidth:3,strokeDasharray:"5,5"},h=!0):c?.completed&&d?.completed?m={stroke:"#10b981",strokeWidth:2}:c?.completed&&d?.invoked?m={stroke:"#f59e0b",strokeWidth:2}:m={stroke:"#6b7280",strokeWidth:2},{...l,style:m,animated:h}})}function yh(e){const n=new Map,r=new Set;return e.forEach(a=>{const l=`${a.source}-${a.target}`,c=`${a.target}-${a.source}`;if(a.source===a.target){n.set(l,a);return}if(n.has(c)){r.add(c),r.add(l);const d=n.get(c);n.set(c,{...d,markerStart:{type:"arrow",width:20,height:20},markerEnd:{type:"arrow",width:20,height:20},data:{...d.data,isBidirectional:!0}})}else r.has(l)||n.set(l,a)}),Array.from(n.values())}function _2({inputSchema:e,onRun:n,onCancel:r,isSubmitting:a,isCancelling:l=!1,workflowState:c,checkpoints:d=[],showCheckpoints:f=!0}){const[m,h]=w.useState(!1);w.useEffect(()=>{const R=D=>{D.key==="Escape"&&m&&h(!1)};if(m)return document.addEventListener("keydown",R),()=>document.removeEventListener("keydown",R)},[m]);const g=w.useMemo(()=>{const R=WR(e);if(!e)return{needsInput:!1,hasDefaults:!1,fieldCount:0,canRunDirectly:!0,isChatMessage:!1};if(e.type==="string")return{needsInput:!e.default,hasDefaults:!!e.default,fieldCount:1,canRunDirectly:!!e.default,isChatMessage:!1};if(e.type==="object"&&e.properties){const D=e.properties,O=Object.entries(D),P=O.filter(([,q])=>q.default!==void 0||q.enum&&q.enum.length>0);return{needsInput:O.length>0,hasDefaults:P.length>0,fieldCount:O.length,canRunDirectly:O.length===0||P.length===O.length,isChatMessage:R}}return{needsInput:!1,hasDefaults:!1,fieldCount:0,canRunDirectly:!0,isChatMessage:!1}},[e]),y=()=>{if(c==="running"&&r)r();else if(g.canRunDirectly){const R={};e?.type==="string"&&e.default?R.input=e.default:e?.type==="object"&&e.properties&&Object.entries(e.properties).forEach(([D,O])=>{O.default!==void 0?R[D]=O.default:O.enum&&O.enum.length>0&&(R[D]=O.enum[0])}),n(R)}else h(!0)},x=R=>{if(g.canRunDirectly){const D={};e?.type==="string"&&e.default?D.input=e.default:e?.type==="object"&&e.properties&&Object.entries(e.properties).forEach(([O,P])=>{P.default!==void 0?D[O]=P.default:P.enum&&P.enum.length>0&&(D[O]=P.enum[0])}),n(D,R)}else h(!0)},b=f&&d.length>0,S=R=>{if(!R)return"";const D=R/1024;return D<1?`${R} B`:D<1024?`${D.toFixed(1)} KB`:`${(D/1024).toFixed(1)} MB`},N=()=>{const R=l?o.jsx(Cr,{className:"w-4 h-4 animate-spin"}):c==="running"&&r?o.jsx(cd,{className:"w-4 h-4 fill-current"}):c==="running"?o.jsx(Cr,{className:"w-4 h-4 animate-spin"}):c==="error"?o.jsx(Yp,{className:"w-4 h-4"}):g.needsInput&&!g.canRunDirectly?o.jsx(Fh,{className:"w-4 h-4"}):o.jsx(Hv,{className:"w-4 h-4"}),D=l?"Stopping...":c==="running"&&r?"Stop":c==="running"?"Running...":c==="completed"?"Run Again":c==="error"?"Retry":g.fieldCount===0||g.canRunDirectly?"Run Workflow":"Configure & Run";return{icon:R,text:D}},{icon:j,text:_}=N(),M=c==="running"&&!r||l,E=c==="error"?"destructive":"default",T=()=>b||g.needsInput?o.jsxs(ud,{children:[o.jsxs("div",{className:"flex w-full",children:[o.jsxs(Te,{onClick:y,disabled:M,variant:E,className:"gap-2 rounded-r-none flex-1",title:c==="running"&&r?"Stop workflow execution":void 0,children:[j,_]}),o.jsx(dd,{asChild:!0,children:o.jsx(Te,{disabled:M,variant:E,className:"rounded-l-none border-l-0 px-2",title:"More options",children:o.jsx(Gt,{className:"w-4 h-4"})})})]}),o.jsxs(fd,{align:"end",className:"w-80 max-h-[400px] overflow-y-auto",children:[b&&o.jsxs(Lt,{onClick:y,children:[o.jsx(Hv,{className:"w-4 h-4 mr-2"}),"Run Fresh"]}),g.needsInput&&o.jsxs(Lt,{onClick:()=>h(!0),children:[o.jsx(Fh,{className:"w-4 h-4 mr-2"}),"Configure Inputs"]}),b&&o.jsxs(o.Fragment,{children:[o.jsx(ma,{}),o.jsx("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:"Resume from checkpoint"}),d.map((D,O)=>o.jsxs(Lt,{onClick:()=>x(D.checkpoint_id),className:"flex flex-col items-start py-2",children:[o.jsxs("div",{className:"flex items-center gap-2 w-full",children:[o.jsx(Fp,{className:"w-4 h-4 flex-shrink-0"}),o.jsx("span",{className:"font-medium",children:D.metadata.iteration_count===0?"Initial State":`Step ${D.metadata.iteration_count}`}),O===0&&o.jsx(ht,{variant:"secondary",className:"text-[10px] h-4 px-1 ml-auto",children:"Latest"})]}),o.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground ml-6 mt-0.5",children:[o.jsx(Up,{className:"w-3 h-3"}),o.jsx("span",{children:new Date(D.timestamp).toLocaleTimeString()}),D.metadata.size_bytes&&o.jsxs(o.Fragment,{children:[o.jsx("span",{children:"•"}),o.jsx("span",{children:S(D.metadata.size_bytes)})]})]})]},D.checkpoint_id))]})]})]}):o.jsxs(Te,{onClick:y,disabled:M,variant:E,className:"gap-2 w-full",title:c==="running"&&r?"Stop workflow execution":void 0,children:[j,_]});return o.jsxs(o.Fragment,{children:[T(),e&&o.jsx(kr,{open:m,onOpenChange:h,children:o.jsxs(Ar,{className:"w-full min-w-[400px] max-w-md sm:max-w-lg md:max-w-2xl lg:max-w-4xl xl:max-w-5xl max-h-[90vh] flex flex-col",children:[o.jsxs(Mr,{className:"px-8 pt-6",children:[o.jsx(Tr,{children:"Configure Workflow Inputs"}),o.jsx(bo,{onClose:()=>h(!1)})]}),o.jsx("div",{className:"px-8 py-4 border-b flex-shrink-0",children:o.jsx("div",{className:"text-sm text-muted-foreground",children:o.jsxs("div",{className:"flex items-center gap-3",children:[o.jsx("span",{className:"font-medium",children:"Input Type:"}),o.jsx(ht,{variant:"secondary",children:g.isChatMessage?"Chat Message":e.type==="string"?"Simple Text":"Structured Data"})]})})}),o.jsx("div",{className:"flex-1 overflow-y-auto py-4 px-8",children:g.isChatMessage?o.jsx(PN,{onSubmit:async R=>{n([{type:"message",role:"user",content:R}]),h(!1)},isSubmitting:a,placeholder:"Enter your message...",entityName:"workflow",showFileUpload:!0}):o.jsx(GR,{inputSchema:e,inputTypeName:"Input",onSubmit:R=>{n(R),h(!1)},isSubmitting:a,className:"embedded"})})]})})]})}function Dt(e){if(typeof e=="string"||typeof e=="number")return""+e;let n="";if(Array.isArray(e))for(let r=0,a;r{}};function Ed(){for(var e=0,n=arguments.length,r={},a;e=0&&(a=r.slice(l+1),r=r.slice(0,l)),r&&!n.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:a}})}vu.prototype=Ed.prototype={constructor:vu,on:function(e,n){var r=this._,a=nD(e+"",r),l,c=-1,d=a.length;if(arguments.length<2){for(;++c0)for(var r=new Array(l),a=0,l,c;a=0&&(n=e.slice(0,r))!=="xmlns"&&(e=e.slice(r+1)),Zv.hasOwnProperty(n)?{space:Zv[n],local:e}:e}function rD(e){return function(){var n=this.ownerDocument,r=this.namespaceURI;return r===rp&&n.documentElement.namespaceURI===rp?n.createElement(e):n.createElementNS(r,e)}}function oD(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function E2(e){var n=Cd(e);return(n.local?oD:rD)(n)}function aD(){}function ag(e){return e==null?aD:function(){return this.querySelector(e)}}function iD(e){typeof e!="function"&&(e=ag(e));for(var n=this._groups,r=n.length,a=new Array(r),l=0;l=E&&(E=M+1);!(R=j[E])&&++E=0;)(d=a[l])&&(c&&d.compareDocumentPosition(c)^4&&c.parentNode.insertBefore(d,c),c=d);return this}function RD(e){e||(e=DD);function n(y,x){return y&&x?e(y.__data__,x.__data__):!y-!x}for(var r=this._groups,a=r.length,l=new Array(a),c=0;cn?1:e>=n?0:NaN}function OD(){var e=arguments[0];return arguments[0]=this,e.apply(null,arguments),this}function zD(){return Array.from(this)}function ID(){for(var e=this._groups,n=0,r=e.length;n1?this.each((n==null?GD:typeof n=="function"?ZD:XD)(e,n,r??"")):Sa(this.node(),e)}function Sa(e,n){return e.style.getPropertyValue(n)||T2(e).getComputedStyle(e,null).getPropertyValue(n)}function KD(e){return function(){delete this[e]}}function QD(e,n){return function(){this[e]=n}}function JD(e,n){return function(){var r=n.apply(this,arguments);r==null?delete this[e]:this[e]=r}}function e6(e,n){return arguments.length>1?this.each((n==null?KD:typeof n=="function"?JD:QD)(e,n)):this.node()[e]}function R2(e){return e.trim().split(/^|\s+/)}function ig(e){return e.classList||new D2(e)}function D2(e){this._node=e,this._names=R2(e.getAttribute("class")||"")}D2.prototype={add:function(e){var n=this._names.indexOf(e);n<0&&(this._names.push(e),this._node.setAttribute("class",this._names.join(" ")))},remove:function(e){var n=this._names.indexOf(e);n>=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};function O2(e,n){for(var r=ig(e),a=-1,l=n.length;++a=0&&(r=n.slice(a+1),n=n.slice(0,a)),{type:n,name:r}})}function k6(e){return function(){var n=this.__on;if(n){for(var r=0,a=-1,l=n.length,c;r()=>e;function op(e,{sourceEvent:n,subject:r,target:a,identifier:l,active:c,x:d,y:f,dx:m,dy:h,dispatch:g}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},subject:{value:r,enumerable:!0,configurable:!0},target:{value:a,enumerable:!0,configurable:!0},identifier:{value:l,enumerable:!0,configurable:!0},active:{value:c,enumerable:!0,configurable:!0},x:{value:d,enumerable:!0,configurable:!0},y:{value:f,enumerable:!0,configurable:!0},dx:{value:m,enumerable:!0,configurable:!0},dy:{value:h,enumerable:!0,configurable:!0},_:{value:g}})}op.prototype.on=function(){var e=this._.on.apply(this._,arguments);return e===this._?this:e};function H6(e){return!e.ctrlKey&&!e.button}function $6(){return this.parentNode}function P6(e,n){return n??{x:e.x,y:e.y}}function B6(){return navigator.maxTouchPoints||"ontouchstart"in this}function P2(){var e=H6,n=$6,r=P6,a=B6,l={},c=Ed("start","drag","end"),d=0,f,m,h,g,y=0;function x(T){T.on("mousedown.drag",b).filter(a).on("touchstart.drag",j).on("touchmove.drag",_,L6).on("touchend.drag touchcancel.drag",M).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function b(T,R){if(!(g||!e.call(this,T,R))){var D=E(this,n.call(this,T,R),T,R,"mouse");D&&(vn(T.view).on("mousemove.drag",S,ol).on("mouseup.drag",N,ol),H2(T.view),vh(T),h=!1,f=T.clientX,m=T.clientY,D("start",T))}}function S(T){if(pa(T),!h){var R=T.clientX-f,D=T.clientY-m;h=R*R+D*D>y}l.mouse("drag",T)}function N(T){vn(T.view).on("mousemove.drag mouseup.drag",null),$2(T.view,h),pa(T),l.mouse("end",T)}function j(T,R){if(e.call(this,T,R)){var D=T.changedTouches,O=n.call(this,T,R),P=D.length,q,Q;for(q=0;q>8&15|n>>4&240,n>>4&15|n&240,(n&15)<<4|n&15,1):r===8?nu(n>>24&255,n>>16&255,n>>8&255,(n&255)/255):r===4?nu(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|n&240,((n&15)<<4|n&15)/255):null):(n=V6.exec(e))?new ln(n[1],n[2],n[3],1):(n=q6.exec(e))?new ln(n[1]*255/100,n[2]*255/100,n[3]*255/100,1):(n=F6.exec(e))?nu(n[1],n[2],n[3],n[4]):(n=Y6.exec(e))?nu(n[1]*255/100,n[2]*255/100,n[3]*255/100,n[4]):(n=G6.exec(e))?nb(n[1],n[2]/100,n[3]/100,1):(n=X6.exec(e))?nb(n[1],n[2]/100,n[3]/100,n[4]):Wv.hasOwnProperty(e)?Jv(Wv[e]):e==="transparent"?new ln(NaN,NaN,NaN,0):null}function Jv(e){return new ln(e>>16&255,e>>8&255,e&255,1)}function nu(e,n,r,a){return a<=0&&(e=n=r=NaN),new ln(e,n,r,a)}function K6(e){return e instanceof jl||(e=ho(e)),e?(e=e.rgb(),new ln(e.r,e.g,e.b,e.opacity)):new ln}function ap(e,n,r,a){return arguments.length===1?K6(e):new ln(e,n,r,a??1)}function ln(e,n,r,a){this.r=+e,this.g=+n,this.b=+r,this.opacity=+a}lg(ln,ap,B2(jl,{brighter(e){return e=e==null?Bu:Math.pow(Bu,e),new ln(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?al:Math.pow(al,e),new ln(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new ln(oo(this.r),oo(this.g),oo(this.b),Uu(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:eb,formatHex:eb,formatHex8:Q6,formatRgb:tb,toString:tb}));function eb(){return`#${so(this.r)}${so(this.g)}${so(this.b)}`}function Q6(){return`#${so(this.r)}${so(this.g)}${so(this.b)}${so((isNaN(this.opacity)?1:this.opacity)*255)}`}function tb(){const e=Uu(this.opacity);return`${e===1?"rgb(":"rgba("}${oo(this.r)}, ${oo(this.g)}, ${oo(this.b)}${e===1?")":`, ${e})`}`}function Uu(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function oo(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function so(e){return e=oo(e),(e<16?"0":"")+e.toString(16)}function nb(e,n,r,a){return a<=0?e=n=r=NaN:r<=0||r>=1?e=n=NaN:n<=0&&(e=NaN),new Pn(e,n,r,a)}function U2(e){if(e instanceof Pn)return new Pn(e.h,e.s,e.l,e.opacity);if(e instanceof jl||(e=ho(e)),!e)return new Pn;if(e instanceof Pn)return e;e=e.rgb();var n=e.r/255,r=e.g/255,a=e.b/255,l=Math.min(n,r,a),c=Math.max(n,r,a),d=NaN,f=c-l,m=(c+l)/2;return f?(n===c?d=(r-a)/f+(r0&&m<1?0:d,new Pn(d,f,m,e.opacity)}function J6(e,n,r,a){return arguments.length===1?U2(e):new Pn(e,n,r,a??1)}function Pn(e,n,r,a){this.h=+e,this.s=+n,this.l=+r,this.opacity=+a}lg(Pn,J6,B2(jl,{brighter(e){return e=e==null?Bu:Math.pow(Bu,e),new Pn(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?al:Math.pow(al,e),new Pn(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,n=isNaN(e)||isNaN(this.s)?0:this.s,r=this.l,a=r+(r<.5?r:1-r)*n,l=2*r-a;return new ln(bh(e>=240?e-240:e+120,l,a),bh(e,l,a),bh(e<120?e+240:e-120,l,a),this.opacity)},clamp(){return new Pn(sb(this.h),su(this.s),su(this.l),Uu(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=Uu(this.opacity);return`${e===1?"hsl(":"hsla("}${sb(this.h)}, ${su(this.s)*100}%, ${su(this.l)*100}%${e===1?")":`, ${e})`}`}}));function sb(e){return e=(e||0)%360,e<0?e+360:e}function su(e){return Math.max(0,Math.min(1,e||0))}function bh(e,n,r){return(e<60?n+(r-n)*e/60:e<180?r:e<240?n+(r-n)*(240-e)/60:n)*255}const cg=e=>()=>e;function eO(e,n){return function(r){return e+r*n}}function tO(e,n,r){return e=Math.pow(e,r),n=Math.pow(n,r)-e,r=1/r,function(a){return Math.pow(e+a*n,r)}}function nO(e){return(e=+e)==1?V2:function(n,r){return r-n?tO(n,r,e):cg(isNaN(n)?r:n)}}function V2(e,n){var r=n-e;return r?eO(e,r):cg(isNaN(e)?n:e)}const Vu=(function e(n){var r=nO(n);function a(l,c){var d=r((l=ap(l)).r,(c=ap(c)).r),f=r(l.g,c.g),m=r(l.b,c.b),h=V2(l.opacity,c.opacity);return function(g){return l.r=d(g),l.g=f(g),l.b=m(g),l.opacity=h(g),l+""}}return a.gamma=e,a})(1);function sO(e,n){n||(n=[]);var r=e?Math.min(n.length,e.length):0,a=n.slice(),l;return function(c){for(l=0;lr&&(c=n.slice(r,c),f[d]?f[d]+=c:f[++d]=c),(a=a[0])===(l=l[0])?f[d]?f[d]+=l:f[++d]=l:(f[++d]=null,m.push({i:d,x:es(a,l)})),r=wh.lastIndex;return r180?g+=360:g-h>180&&(h+=360),x.push({i:y.push(l(y)+"rotate(",null,a)-2,x:es(h,g)})):g&&y.push(l(y)+"rotate("+g+a)}function f(h,g,y,x){h!==g?x.push({i:y.push(l(y)+"skewX(",null,a)-2,x:es(h,g)}):g&&y.push(l(y)+"skewX("+g+a)}function m(h,g,y,x,b,S){if(h!==y||g!==x){var N=b.push(l(b)+"scale(",null,",",null,")");S.push({i:N-4,x:es(h,y)},{i:N-2,x:es(g,x)})}else(y!==1||x!==1)&&b.push(l(b)+"scale("+y+","+x+")")}return function(h,g){var y=[],x=[];return h=e(h),g=e(g),c(h.translateX,h.translateY,g.translateX,g.translateY,y,x),d(h.rotate,g.rotate,y,x),f(h.skewX,g.skewX,y,x),m(h.scaleX,h.scaleY,g.scaleX,g.scaleY,y,x),h=g=null,function(b){for(var S=-1,N=x.length,j;++S=0&&e._call.call(void 0,n),e=e._next;--ja}function ab(){po=(Fu=ll.now())+kd,ja=Gi=0;try{yO()}finally{ja=0,bO(),po=0}}function vO(){var e=ll.now(),n=e-Fu;n>G2&&(kd-=n,Fu=e)}function bO(){for(var e,n=qu,r,a=1/0;n;)n._call?(a>n._time&&(a=n._time),e=n,n=n._next):(r=n._next,n._next=null,n=e?e._next=r:qu=r);Xi=e,cp(a)}function cp(e){if(!ja){Gi&&(Gi=clearTimeout(Gi));var n=e-po;n>24?(e<1/0&&(Gi=setTimeout(ab,e-ll.now()-kd)),Vi&&(Vi=clearInterval(Vi))):(Vi||(Fu=ll.now(),Vi=setInterval(vO,G2)),ja=1,X2(ab))}}function ib(e,n,r){var a=new Yu;return n=n==null?0:+n,a.restart(l=>{a.stop(),e(l+n)},n,r),a}var wO=Ed("start","end","cancel","interrupt"),NO=[],W2=0,lb=1,up=2,wu=3,cb=4,dp=5,Nu=6;function Ad(e,n,r,a,l,c){var d=e.__transition;if(!d)e.__transition={};else if(r in d)return;SO(e,r,{name:n,index:a,group:l,on:wO,tween:NO,time:c.time,delay:c.delay,duration:c.duration,ease:c.ease,timer:null,state:W2})}function dg(e,n){var r=Yn(e,n);if(r.state>W2)throw new Error("too late; already scheduled");return r}function ms(e,n){var r=Yn(e,n);if(r.state>wu)throw new Error("too late; already running");return r}function Yn(e,n){var r=e.__transition;if(!r||!(r=r[n]))throw new Error("transition not found");return r}function SO(e,n,r){var a=e.__transition,l;a[n]=r,r.timer=Z2(c,0,r.time);function c(h){r.state=lb,r.timer.restart(d,r.delay,r.time),r.delay<=h&&d(h-r.delay)}function d(h){var g,y,x,b;if(r.state!==lb)return m();for(g in a)if(b=a[g],b.name===r.name){if(b.state===wu)return ib(d);b.state===cb?(b.state=Nu,b.timer.stop(),b.on.call("interrupt",e,e.__data__,b.index,b.group),delete a[g]):+gup&&a.state=0&&(n=n.slice(0,r)),!n||n==="start"})}function JO(e,n,r){var a,l,c=QO(n)?dg:ms;return function(){var d=c(this,e),f=d.on;f!==a&&(l=(a=f).copy()).on(n,r),d.on=l}}function e8(e,n){var r=this._id;return arguments.length<2?Yn(this.node(),r).on.on(e):this.each(JO(r,e,n))}function t8(e){return function(){var n=this.parentNode;for(var r in this.__transition)if(+r!==e)return;n&&n.removeChild(this)}}function n8(){return this.on("end.remove",t8(this._id))}function s8(e){var n=this._name,r=this._id;typeof e!="function"&&(e=ag(e));for(var a=this._groups,l=a.length,c=new Array(l),d=0;d()=>e;function k8(e,{sourceEvent:n,target:r,transform:a,dispatch:l}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},transform:{value:a,enumerable:!0,configurable:!0},_:{value:l}})}function Ls(e,n,r){this.k=e,this.x=n,this.y=r}Ls.prototype={constructor:Ls,scale:function(e){return e===1?this:new Ls(this.k*e,this.x,this.y)},translate:function(e,n){return e===0&n===0?this:new Ls(this.k,this.x+this.k*e,this.y+this.k*n)},apply:function(e){return[e[0]*this.k+this.x,e[1]*this.k+this.y]},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return[(e[0]-this.x)/this.k,(e[1]-this.y)/this.k]},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Md=new Ls(1,0,0);eS.prototype=Ls.prototype;function eS(e){for(;!e.__zoom;)if(!(e=e.parentNode))return Md;return e.__zoom}function Nh(e){e.stopImmediatePropagation()}function qi(e){e.preventDefault(),e.stopImmediatePropagation()}function A8(e){return(!e.ctrlKey||e.type==="wheel")&&!e.button}function M8(){var e=this;return e instanceof SVGElement?(e=e.ownerSVGElement||e,e.hasAttribute("viewBox")?(e=e.viewBox.baseVal,[[e.x,e.y],[e.x+e.width,e.y+e.height]]):[[0,0],[e.width.baseVal.value,e.height.baseVal.value]]):[[0,0],[e.clientWidth,e.clientHeight]]}function ub(){return this.__zoom||Md}function T8(e){return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*(e.ctrlKey?10:1)}function R8(){return navigator.maxTouchPoints||"ontouchstart"in this}function D8(e,n,r){var a=e.invertX(n[0][0])-r[0][0],l=e.invertX(n[1][0])-r[1][0],c=e.invertY(n[0][1])-r[0][1],d=e.invertY(n[1][1])-r[1][1];return e.translate(l>a?(a+l)/2:Math.min(0,a)||Math.max(0,l),d>c?(c+d)/2:Math.min(0,c)||Math.max(0,d))}function tS(){var e=A8,n=M8,r=D8,a=T8,l=R8,c=[0,1/0],d=[[-1/0,-1/0],[1/0,1/0]],f=250,m=bu,h=Ed("start","zoom","end"),g,y,x,b=500,S=150,N=0,j=10;function _(B){B.property("__zoom",ub).on("wheel.zoom",P,{passive:!1}).on("mousedown.zoom",q).on("dblclick.zoom",Q).filter(l).on("touchstart.zoom",ee).on("touchmove.zoom",G).on("touchend.zoom touchcancel.zoom",W).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}_.transform=function(B,U,k,L){var I=B.selection?B.selection():B;I.property("__zoom",ub),B!==I?R(B,U,k,L):I.interrupt().each(function(){D(this,arguments).event(L).start().zoom(null,typeof U=="function"?U.apply(this,arguments):U).end()})},_.scaleBy=function(B,U,k,L){_.scaleTo(B,function(){var I=this.__zoom.k,H=typeof U=="function"?U.apply(this,arguments):U;return I*H},k,L)},_.scaleTo=function(B,U,k,L){_.transform(B,function(){var I=n.apply(this,arguments),H=this.__zoom,C=k==null?T(I):typeof k=="function"?k.apply(this,arguments):k,$=H.invert(C),Y=typeof U=="function"?U.apply(this,arguments):U;return r(E(M(H,Y),C,$),I,d)},k,L)},_.translateBy=function(B,U,k,L){_.transform(B,function(){return r(this.__zoom.translate(typeof U=="function"?U.apply(this,arguments):U,typeof k=="function"?k.apply(this,arguments):k),n.apply(this,arguments),d)},null,L)},_.translateTo=function(B,U,k,L,I){_.transform(B,function(){var H=n.apply(this,arguments),C=this.__zoom,$=L==null?T(H):typeof L=="function"?L.apply(this,arguments):L;return r(Md.translate($[0],$[1]).scale(C.k).translate(typeof U=="function"?-U.apply(this,arguments):-U,typeof k=="function"?-k.apply(this,arguments):-k),H,d)},L,I)};function M(B,U){return U=Math.max(c[0],Math.min(c[1],U)),U===B.k?B:new Ls(U,B.x,B.y)}function E(B,U,k){var L=U[0]-k[0]*B.k,I=U[1]-k[1]*B.k;return L===B.x&&I===B.y?B:new Ls(B.k,L,I)}function T(B){return[(+B[0][0]+ +B[1][0])/2,(+B[0][1]+ +B[1][1])/2]}function R(B,U,k,L){B.on("start.zoom",function(){D(this,arguments).event(L).start()}).on("interrupt.zoom end.zoom",function(){D(this,arguments).event(L).end()}).tween("zoom",function(){var I=this,H=arguments,C=D(I,H).event(L),$=n.apply(I,H),Y=k==null?T($):typeof k=="function"?k.apply(I,H):k,V=Math.max($[1][0]-$[0][0],$[1][1]-$[0][1]),K=I.__zoom,fe=typeof U=="function"?U.apply(I,H):U,ue=m(K.invert(Y).concat(V/K.k),fe.invert(Y).concat(V/fe.k));return function(te){if(te===1)te=fe;else{var ie=ue(te),xe=V/ie[2];te=new Ls(xe,Y[0]-ie[0]*xe,Y[1]-ie[1]*xe)}C.zoom(null,te)}})}function D(B,U,k){return!k&&B.__zooming||new O(B,U)}function O(B,U){this.that=B,this.args=U,this.active=0,this.sourceEvent=null,this.extent=n.apply(B,U),this.taps=0}O.prototype={event:function(B){return B&&(this.sourceEvent=B),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(B,U){return this.mouse&&B!=="mouse"&&(this.mouse[1]=U.invert(this.mouse[0])),this.touch0&&B!=="touch"&&(this.touch0[1]=U.invert(this.touch0[0])),this.touch1&&B!=="touch"&&(this.touch1[1]=U.invert(this.touch1[0])),this.that.__zoom=U,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(B){var U=vn(this.that).datum();h.call(B,this.that,new k8(B,{sourceEvent:this.sourceEvent,target:_,transform:this.that.__zoom,dispatch:h}),U)}};function P(B,...U){if(!e.apply(this,arguments))return;var k=D(this,U).event(B),L=this.__zoom,I=Math.max(c[0],Math.min(c[1],L.k*Math.pow(2,a.apply(this,arguments)))),H=$n(B);if(k.wheel)(k.mouse[0][0]!==H[0]||k.mouse[0][1]!==H[1])&&(k.mouse[1]=L.invert(k.mouse[0]=H)),clearTimeout(k.wheel);else{if(L.k===I)return;k.mouse=[H,L.invert(H)],Su(this),k.start()}qi(B),k.wheel=setTimeout(C,S),k.zoom("mouse",r(E(M(L,I),k.mouse[0],k.mouse[1]),k.extent,d));function C(){k.wheel=null,k.end()}}function q(B,...U){if(x||!e.apply(this,arguments))return;var k=B.currentTarget,L=D(this,U,!0).event(B),I=vn(B.view).on("mousemove.zoom",Y,!0).on("mouseup.zoom",V,!0),H=$n(B,k),C=B.clientX,$=B.clientY;H2(B.view),Nh(B),L.mouse=[H,this.__zoom.invert(H)],Su(this),L.start();function Y(K){if(qi(K),!L.moved){var fe=K.clientX-C,ue=K.clientY-$;L.moved=fe*fe+ue*ue>N}L.event(K).zoom("mouse",r(E(L.that.__zoom,L.mouse[0]=$n(K,k),L.mouse[1]),L.extent,d))}function V(K){I.on("mousemove.zoom mouseup.zoom",null),$2(K.view,L.moved),qi(K),L.event(K).end()}}function Q(B,...U){if(e.apply(this,arguments)){var k=this.__zoom,L=$n(B.changedTouches?B.changedTouches[0]:B,this),I=k.invert(L),H=k.k*(B.shiftKey?.5:2),C=r(E(M(k,H),L,I),n.apply(this,U),d);qi(B),f>0?vn(this).transition().duration(f).call(R,C,L,B):vn(this).call(_.transform,C,L,B)}}function ee(B,...U){if(e.apply(this,arguments)){var k=B.touches,L=k.length,I=D(this,U,B.changedTouches.length===L).event(B),H,C,$,Y;for(Nh(B),C=0;C"[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001",error002:()=>"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",error003:e=>`Node type "${e}" not found. Using fallback type "default".`,error004:()=>"The React Flow parent container needs a width and a height to render the graph.",error005:()=>"Only child nodes can use a parent extent.",error006:()=>"Can't create edge. An edge needs a source and a target.",error007:e=>`The old edge with id=${e} does not exist.`,error009:e=>`Marker type "${e}" doesn't exist.`,error008:(e,{id:n,sourceHandle:r,targetHandle:a})=>`Couldn't create edge for ${e} handle id: "${e==="source"?r:a}", edge id: ${n}.`,error010:()=>"Handle: No node id found. Make sure to only use a Handle inside a custom Node.",error011:e=>`Edge type "${e}" not found. Using fallback type "default".`,error012:e=>`Node with id "${e}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,error013:(e="react")=>`It seems that you haven't loaded the styles. Please import '@xyflow/${e}/dist/style.css' or base.css to make sure everything is working properly.`,error014:()=>"useNodeConnections: No node ID found. Call useNodeConnections inside a custom Node or provide a node ID.",error015:()=>"It seems that you are trying to drag a node that is not initialized. Please use onNodesChange as explained in the docs."},cl=[[Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY],[Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY]],nS=["Enter"," ","Escape"],sS={"node.a11yDescription.default":"Press enter or space to select a node. Press delete to remove it and escape to cancel.","node.a11yDescription.keyboardDisabled":"Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.","node.a11yDescription.ariaLiveMessage":({direction:e,x:n,y:r})=>`Moved selected node ${e}. New position, x: ${n}, y: ${r}`,"edge.a11yDescription.default":"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.","controls.ariaLabel":"Control Panel","controls.zoomIn.ariaLabel":"Zoom In","controls.zoomOut.ariaLabel":"Zoom Out","controls.fitView.ariaLabel":"Fit View","controls.interactive.ariaLabel":"Toggle Interactivity","minimap.ariaLabel":"Mini Map","handle.ariaLabel":"Handle"};var _a;(function(e){e.Strict="strict",e.Loose="loose"})(_a||(_a={}));var ao;(function(e){e.Free="free",e.Vertical="vertical",e.Horizontal="horizontal"})(ao||(ao={}));var ul;(function(e){e.Partial="partial",e.Full="full"})(ul||(ul={}));const rS={inProgress:!1,isValid:null,from:null,fromHandle:null,fromPosition:null,fromNode:null,to:null,toHandle:null,toPosition:null,toNode:null};var Nr;(function(e){e.Bezier="default",e.Straight="straight",e.Step="step",e.SmoothStep="smoothstep",e.SimpleBezier="simplebezier"})(Nr||(Nr={}));var Gu;(function(e){e.Arrow="arrow",e.ArrowClosed="arrowclosed"})(Gu||(Gu={}));var ze;(function(e){e.Left="left",e.Top="top",e.Right="right",e.Bottom="bottom"})(ze||(ze={}));const db={[ze.Left]:ze.Right,[ze.Right]:ze.Left,[ze.Top]:ze.Bottom,[ze.Bottom]:ze.Top};function oS(e){return e===null?null:e?"valid":"invalid"}const aS=e=>"id"in e&&"source"in e&&"target"in e,O8=e=>"id"in e&&"position"in e&&!("source"in e)&&!("target"in e),mg=e=>"id"in e&&"internals"in e&&!("source"in e)&&!("target"in e),_l=(e,n=[0,0])=>{const{width:r,height:a}=Vs(e),l=e.origin??n,c=r*l[0],d=a*l[1];return{x:e.position.x-c,y:e.position.y-d}},z8=(e,n={nodeOrigin:[0,0]})=>{if(e.length===0)return{x:0,y:0,width:0,height:0};const r=e.reduce((a,l)=>{const c=typeof l=="string";let d=!n.nodeLookup&&!c?l:void 0;n.nodeLookup&&(d=c?n.nodeLookup.get(l):mg(l)?l:n.nodeLookup.get(l.id));const f=d?Xu(d,n.nodeOrigin):{x:0,y:0,x2:0,y2:0};return Td(a,f)},{x:1/0,y:1/0,x2:-1/0,y2:-1/0});return Rd(r)},El=(e,n={})=>{if(e.size===0)return{x:0,y:0,width:0,height:0};let r={x:1/0,y:1/0,x2:-1/0,y2:-1/0};return e.forEach(a=>{if(n.filter===void 0||n.filter(a)){const l=Xu(a);r=Td(r,l)}}),Rd(r)},hg=(e,n,[r,a,l]=[0,0,1],c=!1,d=!1)=>{const f={...kl(n,[r,a,l]),width:n.width/l,height:n.height/l},m=[];for(const h of e.values()){const{measured:g,selectable:y=!0,hidden:x=!1}=h;if(d&&!y||x)continue;const b=g.width??h.width??h.initialWidth??null,S=g.height??h.height??h.initialHeight??null,N=dl(f,Ca(h)),j=(b??0)*(S??0),_=c&&N>0;(!h.internals.handleBounds||_||N>=j||h.dragging)&&m.push(h)}return m},I8=(e,n)=>{const r=new Set;return e.forEach(a=>{r.add(a.id)}),n.filter(a=>r.has(a.source)||r.has(a.target))};function L8(e,n){const r=new Map,a=n?.nodes?new Set(n.nodes.map(l=>l.id)):null;return e.forEach(l=>{l.measured.width&&l.measured.height&&(n?.includeHiddenNodes||!l.hidden)&&(!a||a.has(l.id))&&r.set(l.id,l)}),r}async function H8({nodes:e,width:n,height:r,panZoom:a,minZoom:l,maxZoom:c},d){if(e.size===0)return Promise.resolve(!0);const f=L8(e,d),m=El(f),h=pg(m,n,r,d?.minZoom??l,d?.maxZoom??c,d?.padding??.1);return await a.setViewport(h,{duration:d?.duration,ease:d?.ease,interpolate:d?.interpolate}),Promise.resolve(!0)}function iS({nodeId:e,nextPosition:n,nodeLookup:r,nodeOrigin:a=[0,0],nodeExtent:l,onError:c}){const d=r.get(e),f=d.parentId?r.get(d.parentId):void 0,{x:m,y:h}=f?f.internals.positionAbsolute:{x:0,y:0},g=d.origin??a;let y=d.extent||l;if(d.extent==="parent"&&!d.expandParent)if(!f)c?.("005",cs.error005());else{const b=f.measured.width,S=f.measured.height;b&&S&&(y=[[m,h],[m+b,h+S]])}else f&&ka(d.extent)&&(y=[[d.extent[0][0]+m,d.extent[0][1]+h],[d.extent[1][0]+m,d.extent[1][1]+h]]);const x=ka(y)?go(n,y,d.measured):n;return(d.measured.width===void 0||d.measured.height===void 0)&&c?.("015",cs.error015()),{position:{x:x.x-m+(d.measured.width??0)*g[0],y:x.y-h+(d.measured.height??0)*g[1]},positionAbsolute:x}}async function $8({nodesToRemove:e=[],edgesToRemove:n=[],nodes:r,edges:a,onBeforeDelete:l}){const c=new Set(e.map(x=>x.id)),d=[];for(const x of r){if(x.deletable===!1)continue;const b=c.has(x.id),S=!b&&x.parentId&&d.find(N=>N.id===x.parentId);(b||S)&&d.push(x)}const f=new Set(n.map(x=>x.id)),m=a.filter(x=>x.deletable!==!1),g=I8(d,m);for(const x of m)f.has(x.id)&&!g.find(S=>S.id===x.id)&&g.push(x);if(!l)return{edges:g,nodes:d};const y=await l({nodes:d,edges:g});return typeof y=="boolean"?y?{edges:g,nodes:d}:{edges:[],nodes:[]}:y}const Ea=(e,n=0,r=1)=>Math.min(Math.max(e,n),r),go=(e={x:0,y:0},n,r)=>({x:Ea(e.x,n[0][0],n[1][0]-(r?.width??0)),y:Ea(e.y,n[0][1],n[1][1]-(r?.height??0))});function lS(e,n,r){const{width:a,height:l}=Vs(r),{x:c,y:d}=r.internals.positionAbsolute;return go(e,[[c,d],[c+a,d+l]],n)}const fb=(e,n,r)=>er?-Ea(Math.abs(e-r),1,n)/n:0,cS=(e,n,r=15,a=40)=>{const l=fb(e.x,a,n.width-a)*r,c=fb(e.y,a,n.height-a)*r;return[l,c]},Td=(e,n)=>({x:Math.min(e.x,n.x),y:Math.min(e.y,n.y),x2:Math.max(e.x2,n.x2),y2:Math.max(e.y2,n.y2)}),fp=({x:e,y:n,width:r,height:a})=>({x:e,y:n,x2:e+r,y2:n+a}),Rd=({x:e,y:n,x2:r,y2:a})=>({x:e,y:n,width:r-e,height:a-n}),Ca=(e,n=[0,0])=>{const{x:r,y:a}=mg(e)?e.internals.positionAbsolute:_l(e,n);return{x:r,y:a,width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}},Xu=(e,n=[0,0])=>{const{x:r,y:a}=mg(e)?e.internals.positionAbsolute:_l(e,n);return{x:r,y:a,x2:r+(e.measured?.width??e.width??e.initialWidth??0),y2:a+(e.measured?.height??e.height??e.initialHeight??0)}},uS=(e,n)=>Rd(Td(fp(e),fp(n))),dl=(e,n)=>{const r=Math.max(0,Math.min(e.x+e.width,n.x+n.width)-Math.max(e.x,n.x)),a=Math.max(0,Math.min(e.y+e.height,n.y+n.height)-Math.max(e.y,n.y));return Math.ceil(r*a)},mb=e=>Bn(e.width)&&Bn(e.height)&&Bn(e.x)&&Bn(e.y),Bn=e=>!isNaN(e)&&isFinite(e),P8=(e,n)=>{},Cl=(e,n=[1,1])=>({x:n[0]*Math.round(e.x/n[0]),y:n[1]*Math.round(e.y/n[1])}),kl=({x:e,y:n},[r,a,l],c=!1,d=[1,1])=>{const f={x:(e-r)/l,y:(n-a)/l};return c?Cl(f,d):f},Zu=({x:e,y:n},[r,a,l])=>({x:e*l+r,y:n*l+a});function la(e,n){if(typeof e=="number")return Math.floor((n-n/(1+e))*.5);if(typeof e=="string"&&e.endsWith("px")){const r=parseFloat(e);if(!Number.isNaN(r))return Math.floor(r)}if(typeof e=="string"&&e.endsWith("%")){const r=parseFloat(e);if(!Number.isNaN(r))return Math.floor(n*r*.01)}return console.error(`[React Flow] The padding value "${e}" is invalid. Please provide a number or a string with a valid unit (px or %).`),0}function B8(e,n,r){if(typeof e=="string"||typeof e=="number"){const a=la(e,r),l=la(e,n);return{top:a,right:l,bottom:a,left:l,x:l*2,y:a*2}}if(typeof e=="object"){const a=la(e.top??e.y??0,r),l=la(e.bottom??e.y??0,r),c=la(e.left??e.x??0,n),d=la(e.right??e.x??0,n);return{top:a,right:d,bottom:l,left:c,x:c+d,y:a+l}}return{top:0,right:0,bottom:0,left:0,x:0,y:0}}function U8(e,n,r,a,l,c){const{x:d,y:f}=Zu(e,[n,r,a]),{x:m,y:h}=Zu({x:e.x+e.width,y:e.y+e.height},[n,r,a]),g=l-m,y=c-h;return{left:Math.floor(d),top:Math.floor(f),right:Math.floor(g),bottom:Math.floor(y)}}const pg=(e,n,r,a,l,c)=>{const d=B8(c,n,r),f=(n-d.x)/e.width,m=(r-d.y)/e.height,h=Math.min(f,m),g=Ea(h,a,l),y=e.x+e.width/2,x=e.y+e.height/2,b=n/2-y*g,S=r/2-x*g,N=U8(e,b,S,g,n,r),j={left:Math.min(N.left-d.left,0),top:Math.min(N.top-d.top,0),right:Math.min(N.right-d.right,0),bottom:Math.min(N.bottom-d.bottom,0)};return{x:b-j.left+j.right,y:S-j.top+j.bottom,zoom:g}},fl=()=>typeof navigator<"u"&&navigator?.userAgent?.indexOf("Mac")>=0;function ka(e){return e!=null&&e!=="parent"}function Vs(e){return{width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}}function dS(e){return(e.measured?.width??e.width??e.initialWidth)!==void 0&&(e.measured?.height??e.height??e.initialHeight)!==void 0}function fS(e,n={width:0,height:0},r,a,l){const c={...e},d=a.get(r);if(d){const f=d.origin||l;c.x+=d.internals.positionAbsolute.x-(n.width??0)*f[0],c.y+=d.internals.positionAbsolute.y-(n.height??0)*f[1]}return c}function hb(e,n){if(e.size!==n.size)return!1;for(const r of e)if(!n.has(r))return!1;return!0}function V8(){let e,n;return{promise:new Promise((a,l)=>{e=a,n=l}),resolve:e,reject:n}}function q8(e){return{...sS,...e||{}}}function Qi(e,{snapGrid:n=[0,0],snapToGrid:r=!1,transform:a,containerBounds:l}){const{x:c,y:d}=ss(e),f=kl({x:c-(l?.left??0),y:d-(l?.top??0)},a),{x:m,y:h}=r?Cl(f,n):f;return{xSnapped:m,ySnapped:h,...f}}const gg=e=>({width:e.offsetWidth,height:e.offsetHeight}),mS=e=>e?.getRootNode?.()||window?.document,F8=["INPUT","SELECT","TEXTAREA"];function hS(e){const n=e.composedPath?.()?.[0]||e.target;return n?.nodeType!==1?!1:F8.includes(n.nodeName)||n.hasAttribute("contenteditable")||!!n.closest(".nokey")}const pS=e=>"clientX"in e,ss=(e,n)=>{const r=pS(e),a=r?e.clientX:e.touches?.[0].clientX,l=r?e.clientY:e.touches?.[0].clientY;return{x:a-(n?.left??0),y:l-(n?.top??0)}},pb=(e,n,r,a,l)=>{const c=n.querySelectorAll(`.${e}`);return!c||!c.length?null:Array.from(c).map(d=>{const f=d.getBoundingClientRect();return{id:d.getAttribute("data-handleid"),type:e,nodeId:l,position:d.getAttribute("data-handlepos"),x:(f.left-r.left)/a,y:(f.top-r.top)/a,...gg(d)}})};function gS({sourceX:e,sourceY:n,targetX:r,targetY:a,sourceControlX:l,sourceControlY:c,targetControlX:d,targetControlY:f}){const m=e*.125+l*.375+d*.375+r*.125,h=n*.125+c*.375+f*.375+a*.125,g=Math.abs(m-e),y=Math.abs(h-n);return[m,h,g,y]}function au(e,n){return e>=0?.5*e:n*25*Math.sqrt(-e)}function gb({pos:e,x1:n,y1:r,x2:a,y2:l,c}){switch(e){case ze.Left:return[n-au(n-a,c),r];case ze.Right:return[n+au(a-n,c),r];case ze.Top:return[n,r-au(r-l,c)];case ze.Bottom:return[n,r+au(l-r,c)]}}function xS({sourceX:e,sourceY:n,sourcePosition:r=ze.Bottom,targetX:a,targetY:l,targetPosition:c=ze.Top,curvature:d=.25}){const[f,m]=gb({pos:r,x1:e,y1:n,x2:a,y2:l,c:d}),[h,g]=gb({pos:c,x1:a,y1:l,x2:e,y2:n,c:d}),[y,x,b,S]=gS({sourceX:e,sourceY:n,targetX:a,targetY:l,sourceControlX:f,sourceControlY:m,targetControlX:h,targetControlY:g});return[`M${e},${n} C${f},${m} ${h},${g} ${a},${l}`,y,x,b,S]}function yS({sourceX:e,sourceY:n,targetX:r,targetY:a}){const l=Math.abs(r-e)/2,c=r0}const X8=({source:e,sourceHandle:n,target:r,targetHandle:a})=>`xy-edge__${e}${n||""}-${r}${a||""}`,Z8=(e,n)=>n.some(r=>r.source===e.source&&r.target===e.target&&(r.sourceHandle===e.sourceHandle||!r.sourceHandle&&!e.sourceHandle)&&(r.targetHandle===e.targetHandle||!r.targetHandle&&!e.targetHandle)),W8=(e,n)=>{if(!e.source||!e.target)return n;let r;return aS(e)?r={...e}:r={...e,id:X8(e)},Z8(r,n)?n:(r.sourceHandle===null&&delete r.sourceHandle,r.targetHandle===null&&delete r.targetHandle,n.concat(r))};function vS({sourceX:e,sourceY:n,targetX:r,targetY:a}){const[l,c,d,f]=yS({sourceX:e,sourceY:n,targetX:r,targetY:a});return[`M ${e},${n}L ${r},${a}`,l,c,d,f]}const xb={[ze.Left]:{x:-1,y:0},[ze.Right]:{x:1,y:0},[ze.Top]:{x:0,y:-1},[ze.Bottom]:{x:0,y:1}},K8=({source:e,sourcePosition:n=ze.Bottom,target:r})=>n===ze.Left||n===ze.Right?e.xMath.sqrt(Math.pow(n.x-e.x,2)+Math.pow(n.y-e.y,2));function Q8({source:e,sourcePosition:n=ze.Bottom,target:r,targetPosition:a=ze.Top,center:l,offset:c,stepPosition:d}){const f=xb[n],m=xb[a],h={x:e.x+f.x*c,y:e.y+f.y*c},g={x:r.x+m.x*c,y:r.y+m.y*c},y=K8({source:h,sourcePosition:n,target:g}),x=y.x!==0?"x":"y",b=y[x];let S=[],N,j;const _={x:0,y:0},M={x:0,y:0},[,,E,T]=yS({sourceX:e.x,sourceY:e.y,targetX:r.x,targetY:r.y});if(f[x]*m[x]===-1){x==="x"?(N=l.x??h.x+(g.x-h.x)*d,j=l.y??(h.y+g.y)/2):(N=l.x??(h.x+g.x)/2,j=l.y??h.y+(g.y-h.y)*d);const D=[{x:N,y:h.y},{x:N,y:g.y}],O=[{x:h.x,y:j},{x:g.x,y:j}];f[x]===b?S=x==="x"?D:O:S=x==="x"?O:D}else{const D=[{x:h.x,y:g.y}],O=[{x:g.x,y:h.y}];if(x==="x"?S=f.x===b?O:D:S=f.y===b?D:O,n===a){const G=Math.abs(e[x]-r[x]);if(G<=c){const W=Math.min(c-1,c-G);f[x]===b?_[x]=(h[x]>e[x]?-1:1)*W:M[x]=(g[x]>r[x]?-1:1)*W}}if(n!==a){const G=x==="x"?"y":"x",W=f[x]===m[G],B=h[G]>g[G],U=h[G]=ee?(N=(P.x+q.x)/2,j=S[0].y):(N=S[0].x,j=(P.y+q.y)/2)}return[[e,{x:h.x+_.x,y:h.y+_.y},...S,{x:g.x+M.x,y:g.y+M.y},r],N,j,E,T]}function J8(e,n,r,a){const l=Math.min(yb(e,n)/2,yb(n,r)/2,a),{x:c,y:d}=n;if(e.x===c&&c===r.x||e.y===d&&d===r.y)return`L${c} ${d}`;if(e.y===d){const h=e.x{let T="";return E>0&&Er.id===n):e[0])||null}function hp(e,n){return e?typeof e=="string"?e:`${n?`${n}__`:""}${Object.keys(e).sort().map(a=>`${a}=${e[a]}`).join("&")}`:""}function t9(e,{id:n,defaultColor:r,defaultMarkerStart:a,defaultMarkerEnd:l}){const c=new Set;return e.reduce((d,f)=>([f.markerStart||a,f.markerEnd||l].forEach(m=>{if(m&&typeof m=="object"){const h=hp(m,n);c.has(h)||(d.push({id:h,color:m.color||r,...m}),c.add(h))}}),d),[]).sort((d,f)=>d.id.localeCompare(f.id))}const xg={nodeOrigin:[0,0],nodeExtent:cl,elevateNodesOnSelect:!0,defaults:{}},n9={...xg,checkEquality:!0};function yg(e,n){const r={...e};for(const a in n)n[a]!==void 0&&(r[a]=n[a]);return r}function s9(e,n,r){const a=yg(xg,r);for(const l of e.values())if(l.parentId)vg(l,e,n,a);else{const c=_l(l,a.nodeOrigin),d=ka(l.extent)?l.extent:a.nodeExtent,f=go(c,d,Vs(l));l.internals.positionAbsolute=f}}function pp(e,n,r,a){const l=yg(n9,a);let c=e.length>0;const d=new Map(n),f=l?.elevateNodesOnSelect?1e3:0;n.clear(),r.clear();for(const m of e){let h=d.get(m.id);if(l.checkEquality&&m===h?.internals.userNode)n.set(m.id,h);else{const g=_l(m,l.nodeOrigin),y=ka(m.extent)?m.extent:l.nodeExtent,x=go(g,y,Vs(m));h={...l.defaults,...m,measured:{width:m.measured?.width,height:m.measured?.height},internals:{positionAbsolute:x,handleBounds:m.measured?h?.internals.handleBounds:void 0,z:bS(m,f),userNode:m}},n.set(m.id,h)}(h.measured===void 0||h.measured.width===void 0||h.measured.height===void 0)&&!h.hidden&&(c=!1),m.parentId&&vg(h,n,r,a)}return c}function r9(e,n){if(!e.parentId)return;const r=n.get(e.parentId);r?r.set(e.id,e):n.set(e.parentId,new Map([[e.id,e]]))}function vg(e,n,r,a){const{elevateNodesOnSelect:l,nodeOrigin:c,nodeExtent:d}=yg(xg,a),f=e.parentId,m=n.get(f);if(!m){console.warn(`Parent node ${f} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);return}r9(e,r);const h=l?1e3:0,{x:g,y,z:x}=o9(e,m,c,d,h),{positionAbsolute:b}=e.internals,S=g!==b.x||y!==b.y;(S||x!==e.internals.z)&&n.set(e.id,{...e,internals:{...e.internals,positionAbsolute:S?{x:g,y}:b,z:x}})}function bS(e,n){return(Bn(e.zIndex)?e.zIndex:0)+(e.selected?n:0)}function o9(e,n,r,a,l){const{x:c,y:d}=n.internals.positionAbsolute,f=Vs(e),m=_l(e,r),h=ka(e.extent)?go(m,e.extent,f):m;let g=go({x:c+h.x,y:d+h.y},a,f);e.extent==="parent"&&(g=lS(g,f,n));const y=bS(e,l),x=n.internals.z??0;return{x:g.x,y:g.y,z:x>=y?x+1:y}}function bg(e,n,r,a=[0,0]){const l=[],c=new Map;for(const d of e){const f=n.get(d.parentId);if(!f)continue;const m=c.get(d.parentId)?.expandedRect??Ca(f),h=uS(m,d.rect);c.set(d.parentId,{expandedRect:h,parent:f})}return c.size>0&&c.forEach(({expandedRect:d,parent:f},m)=>{const h=f.internals.positionAbsolute,g=Vs(f),y=f.origin??a,x=d.x0||b>0||j||_)&&(l.push({id:m,type:"position",position:{x:f.position.x-x+j,y:f.position.y-b+_}}),r.get(m)?.forEach(M=>{e.some(E=>E.id===M.id)||l.push({id:M.id,type:"position",position:{x:M.position.x+x,y:M.position.y+b}})})),(g.width0){const x=bg(y,n,r,l);m.push(...x)}return{changes:m,updatedInternals:f}}async function i9({delta:e,panZoom:n,transform:r,translateExtent:a,width:l,height:c}){if(!n||!e.x&&!e.y)return Promise.resolve(!1);const d=await n.setViewportConstrained({x:r[0]+e.x,y:r[1]+e.y,zoom:r[2]},[[0,0],[l,c]],a),f=!!d&&(d.x!==r[0]||d.y!==r[1]||d.k!==r[2]);return Promise.resolve(f)}function Nb(e,n,r,a,l,c){let d=l;const f=a.get(d)||new Map;a.set(d,f.set(r,n)),d=`${l}-${e}`;const m=a.get(d)||new Map;if(a.set(d,m.set(r,n)),c){d=`${l}-${e}-${c}`;const h=a.get(d)||new Map;a.set(d,h.set(r,n))}}function wS(e,n,r){e.clear(),n.clear();for(const a of r){const{source:l,target:c,sourceHandle:d=null,targetHandle:f=null}=a,m={edgeId:a.id,source:l,target:c,sourceHandle:d,targetHandle:f},h=`${l}-${d}--${c}-${f}`,g=`${c}-${f}--${l}-${d}`;Nb("source",m,g,e,l,d),Nb("target",m,h,e,c,f),n.set(a.id,a)}}function NS(e,n){if(!e.parentId)return!1;const r=n.get(e.parentId);return r?r.selected?!0:NS(r,n):!1}function Sb(e,n,r){let a=e;do{if(a?.matches?.(n))return!0;if(a===r)return!1;a=a?.parentElement}while(a);return!1}function l9(e,n,r,a){const l=new Map;for(const[c,d]of e)if((d.selected||d.id===a)&&(!d.parentId||!NS(d,e))&&(d.draggable||n&&typeof d.draggable>"u")){const f=e.get(c);f&&l.set(c,{id:c,position:f.position||{x:0,y:0},distance:{x:r.x-f.internals.positionAbsolute.x,y:r.y-f.internals.positionAbsolute.y},extent:f.extent,parentId:f.parentId,origin:f.origin,expandParent:f.expandParent,internals:{positionAbsolute:f.internals.positionAbsolute||{x:0,y:0}},measured:{width:f.measured.width??0,height:f.measured.height??0}})}return l}function Sh({nodeId:e,dragItems:n,nodeLookup:r,dragging:a=!0}){const l=[];for(const[d,f]of n){const m=r.get(d)?.internals.userNode;m&&l.push({...m,position:f.position,dragging:a})}if(!e)return[l[0],l];const c=r.get(e)?.internals.userNode;return[c?{...c,position:n.get(e)?.position||c.position,dragging:a}:l[0],l]}function c9({dragItems:e,snapGrid:n,x:r,y:a}){const l=e.values().next().value;if(!l)return null;const c={x:r-l.distance.x,y:a-l.distance.y},d=Cl(c,n);return{x:d.x-c.x,y:d.y-c.y}}function u9({onNodeMouseDown:e,getStoreItems:n,onDragStart:r,onDrag:a,onDragStop:l}){let c={x:null,y:null},d=0,f=new Map,m=!1,h={x:0,y:0},g=null,y=!1,x=null,b=!1,S=!1,N=null;function j({noDragClassName:M,handleSelector:E,domNode:T,isSelectable:R,nodeId:D,nodeClickDistance:O=0}){x=vn(T);function P({x:G,y:W}){const{nodeLookup:B,nodeExtent:U,snapGrid:k,snapToGrid:L,nodeOrigin:I,onNodeDrag:H,onSelectionDrag:C,onError:$,updateNodePositions:Y}=n();c={x:G,y:W};let V=!1;const K=f.size>1,fe=K&&U?fp(El(f)):null,ue=K&&L?c9({dragItems:f,snapGrid:k,x:G,y:W}):null;for(const[te,ie]of f){if(!B.has(te))continue;let xe={x:G-ie.distance.x,y:W-ie.distance.y};L&&(xe=ue?{x:Math.round(xe.x+ue.x),y:Math.round(xe.y+ue.y)}:Cl(xe,k));let ve=null;if(K&&U&&!ie.extent&&fe){const{positionAbsolute:he}=ie.internals,X=he.x-fe.x+U[0][0],pe=he.x+ie.measured.width-fe.x2+U[1][0],Ne=he.y-fe.y+U[0][1],ye=he.y+ie.measured.height-fe.y2+U[1][1];ve=[[X,Ne],[pe,ye]]}const{position:be,positionAbsolute:ne}=iS({nodeId:te,nextPosition:xe,nodeLookup:B,nodeExtent:ve||U,nodeOrigin:I,onError:$});V=V||ie.position.x!==be.x||ie.position.y!==be.y,ie.position=be,ie.internals.positionAbsolute=ne}if(S=S||V,!!V&&(Y(f,!0),N&&(a||H||!D&&C))){const[te,ie]=Sh({nodeId:D,dragItems:f,nodeLookup:B});a?.(N,f,te,ie),H?.(N,te,ie),D||C?.(N,ie)}}async function q(){if(!g)return;const{transform:G,panBy:W,autoPanSpeed:B,autoPanOnNodeDrag:U}=n();if(!U){m=!1,cancelAnimationFrame(d);return}const[k,L]=cS(h,g,B);(k!==0||L!==0)&&(c.x=(c.x??0)-k/G[2],c.y=(c.y??0)-L/G[2],await W({x:k,y:L})&&P(c)),d=requestAnimationFrame(q)}function Q(G){const{nodeLookup:W,multiSelectionActive:B,nodesDraggable:U,transform:k,snapGrid:L,snapToGrid:I,selectNodesOnDrag:H,onNodeDragStart:C,onSelectionDragStart:$,unselectNodesAndEdges:Y}=n();y=!0,(!H||!R)&&!B&&D&&(W.get(D)?.selected||Y()),R&&H&&D&&e?.(D);const V=Qi(G.sourceEvent,{transform:k,snapGrid:L,snapToGrid:I,containerBounds:g});if(c=V,f=l9(W,U,V,D),f.size>0&&(r||C||!D&&$)){const[K,fe]=Sh({nodeId:D,dragItems:f,nodeLookup:W});r?.(G.sourceEvent,f,K,fe),C?.(G.sourceEvent,K,fe),D||$?.(G.sourceEvent,fe)}}const ee=P2().clickDistance(O).on("start",G=>{const{domNode:W,nodeDragThreshold:B,transform:U,snapGrid:k,snapToGrid:L}=n();g=W?.getBoundingClientRect()||null,b=!1,S=!1,N=G.sourceEvent,B===0&&Q(G),c=Qi(G.sourceEvent,{transform:U,snapGrid:k,snapToGrid:L,containerBounds:g}),h=ss(G.sourceEvent,g)}).on("drag",G=>{const{autoPanOnNodeDrag:W,transform:B,snapGrid:U,snapToGrid:k,nodeDragThreshold:L,nodeLookup:I}=n(),H=Qi(G.sourceEvent,{transform:B,snapGrid:U,snapToGrid:k,containerBounds:g});if(N=G.sourceEvent,(G.sourceEvent.type==="touchmove"&&G.sourceEvent.touches.length>1||D&&!I.has(D))&&(b=!0),!b){if(!m&&W&&y&&(m=!0,q()),!y){const C=H.xSnapped-(c.x??0),$=H.ySnapped-(c.y??0);Math.sqrt(C*C+$*$)>L&&Q(G)}(c.x!==H.xSnapped||c.y!==H.ySnapped)&&f&&y&&(h=ss(G.sourceEvent,g),P(H))}}).on("end",G=>{if(!(!y||b)&&(m=!1,y=!1,cancelAnimationFrame(d),f.size>0)){const{nodeLookup:W,updateNodePositions:B,onNodeDragStop:U,onSelectionDragStop:k}=n();if(S&&(B(f,!1),S=!1),l||U||!D&&k){const[L,I]=Sh({nodeId:D,dragItems:f,nodeLookup:W,dragging:!1});l?.(G.sourceEvent,f,L,I),U?.(G.sourceEvent,L,I),D||k?.(G.sourceEvent,I)}}}).filter(G=>{const W=G.target;return!G.button&&(!M||!Sb(W,`.${M}`,T))&&(!E||Sb(W,E,T))});x.call(ee)}function _(){x?.on(".drag",null)}return{update:j,destroy:_}}function d9(e,n,r){const a=[],l={x:e.x-r,y:e.y-r,width:r*2,height:r*2};for(const c of n.values())dl(l,Ca(c))>0&&a.push(c);return a}const f9=250;function m9(e,n,r,a){let l=[],c=1/0;const d=d9(e,r,n+f9);for(const f of d){const m=[...f.internals.handleBounds?.source??[],...f.internals.handleBounds?.target??[]];for(const h of m){if(a.nodeId===h.nodeId&&a.type===h.type&&a.id===h.id)continue;const{x:g,y}=ml(f,h,h.position,!0),x=Math.sqrt(Math.pow(g-e.x,2)+Math.pow(y-e.y,2));x>n||(x1){const f=a.type==="source"?"target":"source";return l.find(m=>m.type===f)??l[0]}return l[0]}function SS(e,n,r,a,l,c=!1){const d=a.get(e);if(!d)return null;const f=l==="strict"?d.internals.handleBounds?.[n]:[...d.internals.handleBounds?.source??[],...d.internals.handleBounds?.target??[]],m=(r?f?.find(h=>h.id===r):f?.[0])??null;return m&&c?{...m,...ml(d,m,m.position,!0)}:m}function jS(e,n){return e||(n?.classList.contains("target")?"target":n?.classList.contains("source")?"source":null)}function h9(e,n){let r=null;return n?r=!0:e&&!n&&(r=!1),r}const _S=()=>!0;function p9(e,{connectionMode:n,connectionRadius:r,handleId:a,nodeId:l,edgeUpdaterType:c,isTarget:d,domNode:f,nodeLookup:m,lib:h,autoPanOnConnect:g,flowId:y,panBy:x,cancelConnection:b,onConnectStart:S,onConnect:N,onConnectEnd:j,isValidConnection:_=_S,onReconnectEnd:M,updateConnection:E,getTransform:T,getFromHandle:R,autoPanSpeed:D,dragThreshold:O=1,handleDomNode:P}){const q=mS(e.target);let Q=0,ee;const{x:G,y:W}=ss(e),B=jS(c,P),U=f?.getBoundingClientRect();let k=!1;if(!U||!B)return;const L=SS(l,B,a,m,n);if(!L)return;let I=ss(e,U),H=!1,C=null,$=!1,Y=null;function V(){if(!g||!U)return;const[be,ne]=cS(I,U,D);x({x:be,y:ne}),Q=requestAnimationFrame(V)}const K={...L,nodeId:l,type:B,position:L.position},fe=m.get(l);let te={inProgress:!0,isValid:null,from:ml(fe,K,ze.Left,!0),fromHandle:K,fromPosition:K.position,fromNode:fe,to:I,toHandle:null,toPosition:db[K.position],toNode:null};function ie(){k=!0,E(te),S?.(e,{nodeId:l,handleId:a,handleType:B})}O===0&&ie();function xe(be){if(!k){const{x:pe,y:Ne}=ss(be),ye=pe-G,Oe=Ne-W;if(!(ye*ye+Oe*Oe>O*O))return;ie()}if(!R()||!K){ve(be);return}const ne=T();I=ss(be,U),ee=m9(kl(I,ne,!1,[1,1]),r,m,K),H||(V(),H=!0);const he=ES(be,{handle:ee,connectionMode:n,fromNodeId:l,fromHandleId:a,fromType:d?"target":"source",isValidConnection:_,doc:q,lib:h,flowId:y,nodeLookup:m});Y=he.handleDomNode,C=he.connection,$=h9(!!ee,he.isValid);const X={...te,isValid:$,to:he.toHandle&&$?Zu({x:he.toHandle.x,y:he.toHandle.y},ne):I,toHandle:he.toHandle,toPosition:$&&he.toHandle?he.toHandle.position:db[K.position],toNode:he.toHandle?m.get(he.toHandle.nodeId):null};$&&ee&&te.toHandle&&X.toHandle&&te.toHandle.type===X.toHandle.type&&te.toHandle.nodeId===X.toHandle.nodeId&&te.toHandle.id===X.toHandle.id&&te.to.x===X.to.x&&te.to.y===X.to.y||(E(X),te=X)}function ve(be){if(k){(ee||Y)&&C&&$&&N?.(C);const{inProgress:ne,...he}=te,X={...he,toPosition:te.toHandle?te.toPosition:null};j?.(be,X),c&&M?.(be,X)}b(),cancelAnimationFrame(Q),H=!1,$=!1,C=null,Y=null,q.removeEventListener("mousemove",xe),q.removeEventListener("mouseup",ve),q.removeEventListener("touchmove",xe),q.removeEventListener("touchend",ve)}q.addEventListener("mousemove",xe),q.addEventListener("mouseup",ve),q.addEventListener("touchmove",xe),q.addEventListener("touchend",ve)}function ES(e,{handle:n,connectionMode:r,fromNodeId:a,fromHandleId:l,fromType:c,doc:d,lib:f,flowId:m,isValidConnection:h=_S,nodeLookup:g}){const y=c==="target",x=n?d.querySelector(`.${f}-flow__handle[data-id="${m}-${n?.nodeId}-${n?.id}-${n?.type}"]`):null,{x:b,y:S}=ss(e),N=d.elementFromPoint(b,S),j=N?.classList.contains(`${f}-flow__handle`)?N:x,_={handleDomNode:j,isValid:!1,connection:null,toHandle:null};if(j){const M=jS(void 0,j),E=j.getAttribute("data-nodeid"),T=j.getAttribute("data-handleid"),R=j.classList.contains("connectable"),D=j.classList.contains("connectableend");if(!E||!M)return _;const O={source:y?E:a,sourceHandle:y?T:l,target:y?a:E,targetHandle:y?l:T};_.connection=O;const q=R&&D&&(r===_a.Strict?y&&M==="source"||!y&&M==="target":E!==a||T!==l);_.isValid=q&&h(O),_.toHandle=SS(E,M,T,g,r,!0)}return _}const gp={onPointerDown:p9,isValid:ES};function g9({domNode:e,panZoom:n,getTransform:r,getViewScale:a}){const l=vn(e);function c({translateExtent:f,width:m,height:h,zoomStep:g=1,pannable:y=!0,zoomable:x=!0,inversePan:b=!1}){const S=E=>{if(E.sourceEvent.type!=="wheel"||!n)return;const T=r(),R=E.sourceEvent.ctrlKey&&fl()?10:1,D=-E.sourceEvent.deltaY*(E.sourceEvent.deltaMode===1?.05:E.sourceEvent.deltaMode?1:.002)*g,O=T[2]*Math.pow(2,D*R);n.scaleTo(O)};let N=[0,0];const j=E=>{(E.sourceEvent.type==="mousedown"||E.sourceEvent.type==="touchstart")&&(N=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY])},_=E=>{const T=r();if(E.sourceEvent.type!=="mousemove"&&E.sourceEvent.type!=="touchmove"||!n)return;const R=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY],D=[R[0]-N[0],R[1]-N[1]];N=R;const O=a()*Math.max(T[2],Math.log(T[2]))*(b?-1:1),P={x:T[0]-D[0]*O,y:T[1]-D[1]*O},q=[[0,0],[m,h]];n.setViewportConstrained({x:P.x,y:P.y,zoom:T[2]},q,f)},M=tS().on("start",j).on("zoom",y?_:null).on("zoom.wheel",x?S:null);l.call(M,{})}function d(){l.on("zoom",null)}return{update:c,destroy:d,pointer:$n}}const x9=(e,n)=>e.x!==n.x||e.y!==n.y||e.zoom!==n.k,Dd=e=>({x:e.x,y:e.y,zoom:e.k}),jh=({x:e,y:n,zoom:r})=>Md.translate(e,n).scale(r),ca=(e,n)=>e.target.closest(`.${n}`),CS=(e,n)=>n===2&&Array.isArray(e)&&e.includes(2),y9=e=>((e*=2)<=1?e*e*e:(e-=2)*e*e+2)/2,_h=(e,n=0,r=y9,a=()=>{})=>{const l=typeof n=="number"&&n>0;return l||a(),l?e.transition().duration(n).ease(r).on("end",a):e},kS=e=>{const n=e.ctrlKey&&fl()?10:1;return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*n};function v9({zoomPanValues:e,noWheelClassName:n,d3Selection:r,d3Zoom:a,panOnScrollMode:l,panOnScrollSpeed:c,zoomOnPinch:d,onPanZoomStart:f,onPanZoom:m,onPanZoomEnd:h}){return g=>{if(ca(g,n))return!1;g.preventDefault(),g.stopImmediatePropagation();const y=r.property("__zoom").k||1;if(g.ctrlKey&&d){const j=$n(g),_=kS(g),M=y*Math.pow(2,_);a.scaleTo(r,M,j,g);return}const x=g.deltaMode===1?20:1;let b=l===ao.Vertical?0:g.deltaX*x,S=l===ao.Horizontal?0:g.deltaY*x;!fl()&&g.shiftKey&&l!==ao.Vertical&&(b=g.deltaY*x,S=0),a.translateBy(r,-(b/y)*c,-(S/y)*c,{internal:!0});const N=Dd(r.property("__zoom"));clearTimeout(e.panScrollTimeout),e.isPanScrolling||(e.isPanScrolling=!0,f?.(g,N)),e.isPanScrolling&&(m?.(g,N),e.panScrollTimeout=setTimeout(()=>{h?.(g,N),e.isPanScrolling=!1},150))}}function b9({noWheelClassName:e,preventScrolling:n,d3ZoomHandler:r}){return function(a,l){const c=a.type==="wheel",d=!n&&c&&!a.ctrlKey,f=ca(a,e);if(a.ctrlKey&&c&&f&&a.preventDefault(),d||f)return null;a.preventDefault(),r.call(this,a,l)}}function w9({zoomPanValues:e,onDraggingChange:n,onPanZoomStart:r}){return a=>{if(a.sourceEvent?.internal)return;const l=Dd(a.transform);e.mouseButton=a.sourceEvent?.button||0,e.isZoomingOrPanning=!0,e.prevViewport=l,a.sourceEvent?.type==="mousedown"&&n(!0),r&&r?.(a.sourceEvent,l)}}function N9({zoomPanValues:e,panOnDrag:n,onPaneContextMenu:r,onTransformChange:a,onPanZoom:l}){return c=>{e.usedRightMouseButton=!!(r&&CS(n,e.mouseButton??0)),c.sourceEvent?.sync||a([c.transform.x,c.transform.y,c.transform.k]),l&&!c.sourceEvent?.internal&&l?.(c.sourceEvent,Dd(c.transform))}}function S9({zoomPanValues:e,panOnDrag:n,panOnScroll:r,onDraggingChange:a,onPanZoomEnd:l,onPaneContextMenu:c}){return d=>{if(!d.sourceEvent?.internal&&(e.isZoomingOrPanning=!1,c&&CS(n,e.mouseButton??0)&&!e.usedRightMouseButton&&d.sourceEvent&&c(d.sourceEvent),e.usedRightMouseButton=!1,a(!1),l&&x9(e.prevViewport,d.transform))){const f=Dd(d.transform);e.prevViewport=f,clearTimeout(e.timerId),e.timerId=setTimeout(()=>{l?.(d.sourceEvent,f)},r?150:0)}}}function j9({zoomActivationKeyPressed:e,zoomOnScroll:n,zoomOnPinch:r,panOnDrag:a,panOnScroll:l,zoomOnDoubleClick:c,userSelectionActive:d,noWheelClassName:f,noPanClassName:m,lib:h}){return g=>{const y=e||n,x=r&&g.ctrlKey;if(g.button===1&&g.type==="mousedown"&&(ca(g,`${h}-flow__node`)||ca(g,`${h}-flow__edge`)))return!0;if(!a&&!y&&!l&&!c&&!r||d||ca(g,f)&&g.type==="wheel"||ca(g,m)&&(g.type!=="wheel"||l&&g.type==="wheel"&&!e)||!r&&g.ctrlKey&&g.type==="wheel")return!1;if(!r&&g.type==="touchstart"&&g.touches?.length>1)return g.preventDefault(),!1;if(!y&&!l&&!x&&g.type==="wheel"||!a&&(g.type==="mousedown"||g.type==="touchstart")||Array.isArray(a)&&!a.includes(g.button)&&g.type==="mousedown")return!1;const b=Array.isArray(a)&&a.includes(g.button)||!g.button||g.button<=1;return(!g.ctrlKey||g.type==="wheel")&&b}}function _9({domNode:e,minZoom:n,maxZoom:r,paneClickDistance:a,translateExtent:l,viewport:c,onPanZoom:d,onPanZoomStart:f,onPanZoomEnd:m,onDraggingChange:h}){const g={isZoomingOrPanning:!1,usedRightMouseButton:!1,prevViewport:{x:0,y:0,zoom:0},mouseButton:0,timerId:void 0,panScrollTimeout:void 0,isPanScrolling:!1},y=e.getBoundingClientRect(),x=tS().clickDistance(!Bn(a)||a<0?0:a).scaleExtent([n,r]).translateExtent(l),b=vn(e).call(x);E({x:c.x,y:c.y,zoom:Ea(c.zoom,n,r)},[[0,0],[y.width,y.height]],l);const S=b.on("wheel.zoom"),N=b.on("dblclick.zoom");x.wheelDelta(kS);function j(G,W){return b?new Promise(B=>{x?.interpolate(W?.interpolate==="linear"?Ki:bu).transform(_h(b,W?.duration,W?.ease,()=>B(!0)),G)}):Promise.resolve(!1)}function _({noWheelClassName:G,noPanClassName:W,onPaneContextMenu:B,userSelectionActive:U,panOnScroll:k,panOnDrag:L,panOnScrollMode:I,panOnScrollSpeed:H,preventScrolling:C,zoomOnPinch:$,zoomOnScroll:Y,zoomOnDoubleClick:V,zoomActivationKeyPressed:K,lib:fe,onTransformChange:ue}){U&&!g.isZoomingOrPanning&&M();const ie=k&&!K&&!U?v9({zoomPanValues:g,noWheelClassName:G,d3Selection:b,d3Zoom:x,panOnScrollMode:I,panOnScrollSpeed:H,zoomOnPinch:$,onPanZoomStart:f,onPanZoom:d,onPanZoomEnd:m}):b9({noWheelClassName:G,preventScrolling:C,d3ZoomHandler:S});if(b.on("wheel.zoom",ie,{passive:!1}),!U){const ve=w9({zoomPanValues:g,onDraggingChange:h,onPanZoomStart:f});x.on("start",ve);const be=N9({zoomPanValues:g,panOnDrag:L,onPaneContextMenu:!!B,onPanZoom:d,onTransformChange:ue});x.on("zoom",be);const ne=S9({zoomPanValues:g,panOnDrag:L,panOnScroll:k,onPaneContextMenu:B,onPanZoomEnd:m,onDraggingChange:h});x.on("end",ne)}const xe=j9({zoomActivationKeyPressed:K,panOnDrag:L,zoomOnScroll:Y,panOnScroll:k,zoomOnDoubleClick:V,zoomOnPinch:$,userSelectionActive:U,noPanClassName:W,noWheelClassName:G,lib:fe});x.filter(xe),V?b.on("dblclick.zoom",N):b.on("dblclick.zoom",null)}function M(){x.on("zoom",null)}async function E(G,W,B){const U=jh(G),k=x?.constrain()(U,W,B);return k&&await j(k),new Promise(L=>L(k))}async function T(G,W){const B=jh(G);return await j(B,W),new Promise(U=>U(B))}function R(G){if(b){const W=jh(G),B=b.property("__zoom");(B.k!==G.zoom||B.x!==G.x||B.y!==G.y)&&x?.transform(b,W,null,{sync:!0})}}function D(){const G=b?eS(b.node()):{x:0,y:0,k:1};return{x:G.x,y:G.y,zoom:G.k}}function O(G,W){return b?new Promise(B=>{x?.interpolate(W?.interpolate==="linear"?Ki:bu).scaleTo(_h(b,W?.duration,W?.ease,()=>B(!0)),G)}):Promise.resolve(!1)}function P(G,W){return b?new Promise(B=>{x?.interpolate(W?.interpolate==="linear"?Ki:bu).scaleBy(_h(b,W?.duration,W?.ease,()=>B(!0)),G)}):Promise.resolve(!1)}function q(G){x?.scaleExtent(G)}function Q(G){x?.translateExtent(G)}function ee(G){const W=!Bn(G)||G<0?0:G;x?.clickDistance(W)}return{update:_,destroy:M,setViewport:T,setViewportConstrained:E,getViewport:D,scaleTo:O,scaleBy:P,setScaleExtent:q,setTranslateExtent:Q,syncViewport:R,setClickDistance:ee}}var Aa;(function(e){e.Line="line",e.Handle="handle"})(Aa||(Aa={}));function E9({width:e,prevWidth:n,height:r,prevHeight:a,affectsX:l,affectsY:c}){const d=e-n,f=r-a,m=[d>0?1:d<0?-1:0,f>0?1:f<0?-1:0];return d&&l&&(m[0]=m[0]*-1),f&&c&&(m[1]=m[1]*-1),m}function C9(e){const n=e.includes("right")||e.includes("left"),r=e.includes("bottom")||e.includes("top"),a=e.includes("left"),l=e.includes("top");return{isHorizontal:n,isVertical:r,affectsX:a,affectsY:l}}function yr(e,n){return Math.max(0,n-e)}function vr(e,n){return Math.max(0,e-n)}function iu(e,n,r){return Math.max(0,n-e,e-r)}function jb(e,n){return e?!n:n}function k9(e,n,r,a,l,c,d,f){let{affectsX:m,affectsY:h}=n;const{isHorizontal:g,isVertical:y}=n,x=g&&y,{xSnapped:b,ySnapped:S}=r,{minWidth:N,maxWidth:j,minHeight:_,maxHeight:M}=a,{x:E,y:T,width:R,height:D,aspectRatio:O}=e;let P=Math.floor(g?b-e.pointerX:0),q=Math.floor(y?S-e.pointerY:0);const Q=R+(m?-P:P),ee=D+(h?-q:q),G=-c[0]*R,W=-c[1]*D;let B=iu(Q,N,j),U=iu(ee,_,M);if(d){let I=0,H=0;m&&P<0?I=yr(E+P+G,d[0][0]):!m&&P>0&&(I=vr(E+Q+G,d[1][0])),h&&q<0?H=yr(T+q+W,d[0][1]):!h&&q>0&&(H=vr(T+ee+W,d[1][1])),B=Math.max(B,I),U=Math.max(U,H)}if(f){let I=0,H=0;m&&P>0?I=vr(E+P,f[0][0]):!m&&P<0&&(I=yr(E+Q,f[1][0])),h&&q>0?H=vr(T+q,f[0][1]):!h&&q<0&&(H=yr(T+ee,f[1][1])),B=Math.max(B,I),U=Math.max(U,H)}if(l){if(g){const I=iu(Q/O,_,M)*O;if(B=Math.max(B,I),d){let H=0;!m&&!h||m&&!h&&x?H=vr(T+W+Q/O,d[1][1])*O:H=yr(T+W+(m?P:-P)/O,d[0][1])*O,B=Math.max(B,H)}if(f){let H=0;!m&&!h||m&&!h&&x?H=yr(T+Q/O,f[1][1])*O:H=vr(T+(m?P:-P)/O,f[0][1])*O,B=Math.max(B,H)}}if(y){const I=iu(ee*O,N,j)/O;if(U=Math.max(U,I),d){let H=0;!m&&!h||h&&!m&&x?H=vr(E+ee*O+G,d[1][0])/O:H=yr(E+(h?q:-q)*O+G,d[0][0])/O,U=Math.max(U,H)}if(f){let H=0;!m&&!h||h&&!m&&x?H=yr(E+ee*O,f[1][0])/O:H=vr(E+(h?q:-q)*O,f[0][0])/O,U=Math.max(U,H)}}}q=q+(q<0?U:-U),P=P+(P<0?B:-B),l&&(x?Q>ee*O?q=(jb(m,h)?-P:P)/O:P=(jb(m,h)?-q:q)*O:g?(q=P/O,h=m):(P=q*O,m=h));const k=m?E+P:E,L=h?T+q:T;return{width:R+(m?-P:P),height:D+(h?-q:q),x:c[0]*P*(m?-1:1)+k,y:c[1]*q*(h?-1:1)+L}}const AS={width:0,height:0,x:0,y:0},A9={...AS,pointerX:0,pointerY:0,aspectRatio:1};function M9(e){return[[0,0],[e.measured.width,e.measured.height]]}function T9(e,n,r){const a=n.position.x+e.position.x,l=n.position.y+e.position.y,c=e.measured.width??0,d=e.measured.height??0,f=r[0]*c,m=r[1]*d;return[[a-f,l-m],[a+c-f,l+d-m]]}function R9({domNode:e,nodeId:n,getStoreItems:r,onChange:a,onEnd:l}){const c=vn(e);function d({controlPosition:m,boundaries:h,keepAspectRatio:g,resizeDirection:y,onResizeStart:x,onResize:b,onResizeEnd:S,shouldResize:N}){let j={...AS},_={...A9};const M=C9(m);let E,T=null,R=[],D,O,P;const q=P2().on("start",Q=>{const{nodeLookup:ee,transform:G,snapGrid:W,snapToGrid:B,nodeOrigin:U,paneDomNode:k}=r();if(E=ee.get(n),!E)return;T=k?.getBoundingClientRect()??null;const{xSnapped:L,ySnapped:I}=Qi(Q.sourceEvent,{transform:G,snapGrid:W,snapToGrid:B,containerBounds:T});j={width:E.measured.width??0,height:E.measured.height??0,x:E.position.x??0,y:E.position.y??0},_={...j,pointerX:L,pointerY:I,aspectRatio:j.width/j.height},D=void 0,E.parentId&&(E.extent==="parent"||E.expandParent)&&(D=ee.get(E.parentId),O=D&&E.extent==="parent"?M9(D):void 0),R=[],P=void 0;for(const[H,C]of ee)if(C.parentId===n&&(R.push({id:H,position:{...C.position},extent:C.extent}),C.extent==="parent"||C.expandParent)){const $=T9(C,E,C.origin??U);P?P=[[Math.min($[0][0],P[0][0]),Math.min($[0][1],P[0][1])],[Math.max($[1][0],P[1][0]),Math.max($[1][1],P[1][1])]]:P=$}x?.(Q,{...j})}).on("drag",Q=>{const{transform:ee,snapGrid:G,snapToGrid:W,nodeOrigin:B}=r(),U=Qi(Q.sourceEvent,{transform:ee,snapGrid:G,snapToGrid:W,containerBounds:T}),k=[];if(!E)return;const{x:L,y:I,width:H,height:C}=j,$={},Y=E.origin??B,{width:V,height:K,x:fe,y:ue}=k9(_,M,U,h,g,Y,O,P),te=V!==H,ie=K!==C,xe=fe!==L&&te,ve=ue!==I&&ie;if(!xe&&!ve&&!te&&!ie)return;if((xe||ve||Y[0]===1||Y[1]===1)&&($.x=xe?fe:j.x,$.y=ve?ue:j.y,j.x=$.x,j.y=$.y,R.length>0)){const X=fe-L,pe=ue-I;for(const Ne of R)Ne.position={x:Ne.position.x-X+Y[0]*(V-H),y:Ne.position.y-pe+Y[1]*(K-C)},k.push(Ne)}if((te||ie)&&($.width=te&&(!y||y==="horizontal")?V:j.width,$.height=ie&&(!y||y==="vertical")?K:j.height,j.width=$.width,j.height=$.height),D&&E.expandParent){const X=Y[0]*($.width??0);$.x&&$.x{S?.(Q,{...j}),l?.({...j})});c.call(q)}function f(){c.on(".drag",null)}return{update:d,destroy:f}}var Eh={exports:{}},Ch={},kh={exports:{}},Ah={};/** * @license React * use-sync-external-store-shim.production.js * diff --git a/python/packages/devui/frontend/package-lock.json b/python/packages/devui/frontend/package-lock.json new file mode 100644 index 0000000000..913d50baa2 --- /dev/null +++ b/python/packages/devui/frontend/package-lock.json @@ -0,0 +1,5285 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@tailwindcss/vite": "^4.1.12", + "@xyflow/react": "^12.8.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.540.0", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.12", + "zustand": "^5.0.8" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/node": "^24.3.0", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^5.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "tw-animate-css": "^1.3.7", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.11" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.32", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz", + "integrity": "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", + "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", + "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", + "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", + "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", + "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", + "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", + "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", + "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", + "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", + "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", + "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", + "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", + "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", + "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", + "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", + "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", + "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", + "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", + "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", + "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", + "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", + "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-x64": "4.1.12", + "@tailwindcss/oxide-freebsd-x64": "4.1.12", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-x64-musl": "4.1.12", + "@tailwindcss/oxide-wasm32-wasi": "4.1.12", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz", + "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz", + "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", + "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz", + "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz", + "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz", + "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz", + "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz", + "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz", + "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz", + "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.5", + "@emnapi/runtime": "^1.4.5", + "@emnapi/wasi-threads": "^1.0.4", + "@napi-rs/wasm-runtime": "^0.2.12", + "@tybys/wasm-util": "^0.10.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz", + "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz", + "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.12.tgz", + "integrity": "sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.12", + "@tailwindcss/oxide": "4.1.12", + "tailwindcss": "4.1.12" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/@types/react": { + "version": "19.1.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", + "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", + "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", + "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/type-utils": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.40.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz", + "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz", + "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.40.0", + "@typescript-eslint/types": "^8.40.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz", + "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz", + "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz", + "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz", + "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz", + "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.40.0", + "@typescript-eslint/tsconfig-utils": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz", + "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz", + "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.1.tgz", + "integrity": "sha512-DE4UNaBXwtVoDJ0ccBdLVjFTWL70NRuWNCxEieTI3lrq9ORB9aOCQEKstwDXBl87NvFdbqh/p7eINGyj0BthJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.3", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.32", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xyflow/react": { + "version": "12.8.4", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.4.tgz", + "integrity": "sha512-bqUu4T5QSHiCFPkoH+b+LROKwQJdLvcjhGbNW9c1dLafCBRjmH1IYz0zPE+lRDXCtQ9kRyFxz3tG19+8VORJ1w==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.68", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/react/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.68", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.68.tgz", + "integrity": "sha512-QDG2wxIG4qX+uF8yzm1ULVZrcXX3MxPBoxv7O52FWsX87qIImOqifUhfa/TwsvLdzn7ic2DDBH1uI8TKbdNTYA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001736", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz", + "integrity": "sha512-ImpN5gLEY8gWeqfLUyEF4b7mYWcYoR2Si1VhnrbM4JizRFmfGaAQ12PhNykq6nvI4XvKLrsp8Xde74D5phJOSw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.208", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", + "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.540.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.540.0.tgz", + "integrity": "sha512-armkCAqQvO62EIX4Hq7hqX/q11WSZu0Jd23cnnqx0/49yIxGXyL/zyZfBxNN9YDx0ensPTb4L+DjTh3yQXUxtQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", + "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.47.1", + "@rollup/rollup-android-arm64": "4.47.1", + "@rollup/rollup-darwin-arm64": "4.47.1", + "@rollup/rollup-darwin-x64": "4.47.1", + "@rollup/rollup-freebsd-arm64": "4.47.1", + "@rollup/rollup-freebsd-x64": "4.47.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", + "@rollup/rollup-linux-arm-musleabihf": "4.47.1", + "@rollup/rollup-linux-arm64-gnu": "4.47.1", + "@rollup/rollup-linux-arm64-musl": "4.47.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", + "@rollup/rollup-linux-ppc64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-musl": "4.47.1", + "@rollup/rollup-linux-s390x-gnu": "4.47.1", + "@rollup/rollup-linux-x64-gnu": "4.47.1", + "@rollup/rollup-linux-x64-musl": "4.47.1", + "@rollup/rollup-win32-arm64-msvc": "4.47.1", + "@rollup/rollup-win32-ia32-msvc": "4.47.1", + "@rollup/rollup-win32-x64-msvc": "4.47.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", + "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.7.tgz", + "integrity": "sha512-lvLb3hTIpB5oGsk8JmLoAjeCHV58nKa2zHYn8yWOoG5JJusH3bhJlF2DLAZ/5NmJ+jyH3ssiAx/2KmbhavJy/A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.40.0.tgz", + "integrity": "sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx b/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx index 193991f8e0..bf7be1eab9 100644 --- a/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx +++ b/python/packages/devui/frontend/src/components/features/workflow/workflow-input-form.tsx @@ -169,6 +169,7 @@ function FormField({ } case "number": + case "integer": return (