Ver Fonte

fix: harden generic event kernel boundaries

Problem: partial schema validation and unguarded resolver/fallback/handler boundaries could leak exceptions or accept invalid event arguments. Risk: standard JSON Schema validation and async facade checks may reject definitions or calls previously tolerated; EventAgent concurrency is restored pending bounded scheduling.
zhenyu.hu há 2 semanas atrás
pai
commit
0bac6ce86e

+ 1 - 0
pyproject.toml

@@ -7,6 +7,7 @@ requires-python = ">=3.11"
 dependencies = [
 dependencies = [
     "fastapi>=0.111.0",
     "fastapi>=0.111.0",
     "httpx>=0.27.0",
     "httpx>=0.27.0",
+    "jsonschema>=4.26.0",
     "pydantic-settings>=2.3.0",
     "pydantic-settings>=2.3.0",
     "uvicorn[standard]>=0.30.0",
     "uvicorn[standard]>=0.30.0",
     "websockets>=12.0",
     "websockets>=12.0",

+ 14 - 8
src/agent_lab/application/event_agent.py

@@ -1,3 +1,4 @@
+import asyncio
 import json
 import json
 from collections.abc import AsyncIterator, Iterable, Sequence
 from collections.abc import AsyncIterator, Iterable, Sequence
 from dataclasses import dataclass
 from dataclasses import dataclass
@@ -9,6 +10,7 @@ from agent_lab.application.events import (
     EventExecutionContext,
     EventExecutionContext,
     EventKernel,
     EventKernel,
     EventRequest,
     EventRequest,
+    ResolvedEventArguments,
 )
 )
 from agent_lab.application.tools import (
 from agent_lab.application.tools import (
     ToolExecutionContext,
     ToolExecutionContext,
@@ -91,17 +93,17 @@ class EventAgent:
     ) -> list[ChatMessage]:
     ) -> list[ChatMessage]:
         for event in events:
         for event in events:
             self._raw_chunks_by_event[event.id] = []
             self._raw_chunks_by_event[event.id] = []
-        replies: list[ChatMessage] = []
-        for event in events:
-            replies.append(
-                await self.handle(
+        return await asyncio.gather(
+            *[
+                self.handle(
                     event,
                     event,
                     history=history,
                     history=history,
                     system_prompt=system_prompt,
                     system_prompt=system_prompt,
                     extra_body=extra_body,
                     extra_body=extra_body,
                 )
                 )
-            )
-        return replies
+                for event in events
+            ]
+        )
 
 
     def raw_model_chunks(
     def raw_model_chunks(
         self,
         self,
@@ -121,7 +123,7 @@ class EventAgent:
         definition: EventDefinition,
         definition: EventDefinition,
         request: EventRequest,
         request: EventRequest,
         context: EventExecutionContext,
         context: EventExecutionContext,
-    ) -> dict[str, Any] | None:
+    ) -> ResolvedEventArguments | None:
         assert self.chat_client is not None
         assert self.chat_client is not None
         tool = self.registry.tool_schema(request.name)
         tool = self.registry.tool_schema(request.name)
         if tool is None:
         if tool is None:
@@ -158,7 +160,11 @@ class EventAgent:
 
 
             if item.kind == "provider_tool_call" and item.event is not None:
             if item.kind == "provider_tool_call" and item.event is not None:
                 self._raw_chunks_by_event[event.id] = raw_chunks
                 self._raw_chunks_by_event[event.id] = raw_chunks
-                return dict(item.event.arguments)
+                return ResolvedEventArguments(
+                    event_name=item.event.name,
+                    arguments=dict(item.event.arguments),
+                    raw_arguments=item.event.raw_arguments,
+                )
 
 
         self._raw_chunks_by_event[event.id] = raw_chunks
         self._raw_chunks_by_event[event.id] = raw_chunks
         return None
         return None

+ 2 - 0
src/agent_lab/application/events/__init__.py

@@ -9,6 +9,7 @@ from agent_lab.application.events.models import (
     EventResult,
     EventResult,
     EventSource,
     EventSource,
     EventStatus,
     EventStatus,
+    ResolvedEventArguments,
     ResultPolicy,
     ResultPolicy,
     RiskLevel,
     RiskLevel,
 )
 )
@@ -27,6 +28,7 @@ __all__ = [
     "EventResult",
     "EventResult",
     "EventSource",
     "EventSource",
     "EventStatus",
     "EventStatus",
+    "ResolvedEventArguments",
     "ResultPolicy",
     "ResultPolicy",
     "RiskLevel",
     "RiskLevel",
 ]
 ]

+ 248 - 57
src/agent_lab/application/events/kernel.py

@@ -1,10 +1,13 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import inspect
 import inspect
+import json
 from collections.abc import Awaitable, Callable, Iterable
 from collections.abc import Awaitable, Callable, Iterable
 from dataclasses import replace
 from dataclasses import replace
 from typing import Any
 from typing import Any
 
 
+from jsonschema.exceptions import ValidationError
+
 from agent_lab.application.events.models import (
 from agent_lab.application.events.models import (
     EventDefinition,
     EventDefinition,
     EventExecutionContext,
     EventExecutionContext,
@@ -12,13 +15,15 @@ from agent_lab.application.events.models import (
     EventResult,
     EventResult,
     EventSource,
     EventSource,
     EventStatus,
     EventStatus,
+    ResolvedEventArguments,
 )
 )
 from agent_lab.application.events.registry import EventRegistry
 from agent_lab.application.events.registry import EventRegistry
 
 
 
 
+ArgumentFallbackResult = ResolvedEventArguments | None
 ArgumentFallback = Callable[
 ArgumentFallback = Callable[
     [EventDefinition, EventRequest, EventExecutionContext],
     [EventDefinition, EventRequest, EventExecutionContext],
-    Awaitable[dict[str, Any] | None],
+    ArgumentFallbackResult | Awaitable[ArgumentFallbackResult],
 ]
 ]
 
 
 
 
@@ -43,12 +48,17 @@ class EventKernel:
             return early_result
             return early_result
         assert definition is not None
         assert definition is not None
         resolved_context = context or EventExecutionContext()
         resolved_context = context or EventExecutionContext()
-        arguments = self._deterministic_arguments(
+
+        resolved, resolution_error = self._resolve_deterministic(
             definition,
             definition,
             request,
             request,
             resolved_context,
             resolved_context,
         )
         )
-        validation_error = self._validation_error(definition, arguments)
+        if resolution_error is not None:
+            return resolution_error
+        assert resolved is not None
+
+        validation_error = self._validation_error(definition, resolved.arguments)
         used_fallback = False
         used_fallback = False
         if (
         if (
             validation_error is not None
             validation_error is not None
@@ -57,27 +67,56 @@ class EventKernel:
             and self.argument_fallback is not None
             and self.argument_fallback is not None
         ):
         ):
             used_fallback = True
             used_fallback = True
-            fallback_request = replace(request, arguments=arguments)
-            fallback_arguments = await self.argument_fallback(
+            fallback_request = replace(
+                request,
+                arguments=resolved.arguments,
+                raw_arguments=resolved.raw_arguments,
+            )
+            try:
+                fallback_value = self.argument_fallback(
+                    definition,
+                    fallback_request,
+                    resolved_context,
+                )
+                if inspect.isawaitable(fallback_value):
+                    fallback_value = await fallback_value
+            except Exception as exc:
+                return self._resolution_error(
+                    definition,
+                    request,
+                    f"event argument fallback failed: {exc}",
+                    resolved=resolved,
+                    used_fallback=True,
+                )
+            if fallback_value is not None:
+                resolved, fallback_error = self._normalize_fallback(
+                    definition,
+                    request,
+                    fallback_value,
+                )
+                if fallback_error is not None:
+                    return fallback_error
+                assert resolved is not None
+            validation_error = self._validation_error(
                 definition,
                 definition,
-                fallback_request,
-                resolved_context,
+                resolved.arguments,
             )
             )
-            if fallback_arguments is not None:
-                arguments = fallback_arguments
-            validation_error = self._validation_error(definition, arguments)
 
 
         if validation_error is not None:
         if validation_error is not None:
             return self._result(
             return self._result(
                 definition,
                 definition,
                 request,
                 request,
                 EventStatus.INVALID_ARGUMENTS,
                 EventStatus.INVALID_ARGUMENTS,
-                arguments=arguments,
+                resolved=resolved,
                 error=validation_error,
                 error=validation_error,
                 used_fallback=used_fallback,
                 used_fallback=used_fallback,
             )
             )
 
 
-        resolved_request = replace(request, arguments=arguments)
+        resolved_request = replace(
+            request,
+            arguments=resolved.arguments,
+            raw_arguments=resolved.raw_arguments,
+        )
         try:
         try:
             payload = definition.handler(resolved_request)
             payload = definition.handler(resolved_request)
             if inspect.isawaitable(payload):
             if inspect.isawaitable(payload):
@@ -87,15 +126,24 @@ class EventKernel:
                 definition,
                 definition,
                 request,
                 request,
                 EventStatus.HANDLER_ERROR,
                 EventStatus.HANDLER_ERROR,
-                arguments=arguments,
+                resolved=resolved,
                 error=f"event handler failed: {exc}",
                 error=f"event handler failed: {exc}",
                 used_fallback=used_fallback,
                 used_fallback=used_fallback,
             )
             )
+        if not isinstance(payload, dict):
+            return self._result(
+                definition,
+                request,
+                EventStatus.HANDLER_ERROR,
+                resolved=resolved,
+                error="event handler returned non-object payload",
+                used_fallback=used_fallback,
+            )
         return self._result(
         return self._result(
             definition,
             definition,
             request,
             request,
             EventStatus.SUCCESS,
             EventStatus.SUCCESS,
-            arguments=arguments,
+            resolved=resolved,
             payload=dict(payload),
             payload=dict(payload),
             used_fallback=used_fallback,
             used_fallback=used_fallback,
         )
         )
@@ -111,37 +159,69 @@ class EventKernel:
         if early_result is not None:
         if early_result is not None:
             return early_result
             return early_result
         assert definition is not None
         assert definition is not None
-        arguments = self._deterministic_arguments(
+        resolved, resolution_error = self._resolve_deterministic(
             definition,
             definition,
             request,
             request,
             context or EventExecutionContext(),
             context or EventExecutionContext(),
         )
         )
-        validation_error = self._validation_error(definition, arguments)
+        if resolution_error is not None:
+            return resolution_error
+        assert resolved is not None
+        validation_error = self._validation_error(definition, resolved.arguments)
         if validation_error is not None:
         if validation_error is not None:
             return self._result(
             return self._result(
                 definition,
                 definition,
                 request,
                 request,
                 EventStatus.INVALID_ARGUMENTS,
                 EventStatus.INVALID_ARGUMENTS,
-                arguments=arguments,
+                resolved=resolved,
                 error=validation_error,
                 error=validation_error,
             )
             )
+        if inspect.iscoroutinefunction(definition.handler):
+            return self._result(
+                definition,
+                request,
+                EventStatus.HANDLER_ERROR,
+                resolved=resolved,
+                error="event handler failed: async event handlers require EventKernel.execute",
+            )
+        resolved_request = replace(
+            request,
+            arguments=resolved.arguments,
+            raw_arguments=resolved.raw_arguments,
+        )
         try:
         try:
-            payload = definition.handler(replace(request, arguments=arguments))
-            if inspect.isawaitable(payload):
-                raise TypeError("async event handlers require EventKernel.execute")
+            payload = definition.handler(resolved_request)
         except Exception as exc:
         except Exception as exc:
             return self._result(
             return self._result(
                 definition,
                 definition,
                 request,
                 request,
                 EventStatus.HANDLER_ERROR,
                 EventStatus.HANDLER_ERROR,
-                arguments=arguments,
+                resolved=resolved,
                 error=f"event handler failed: {exc}",
                 error=f"event handler failed: {exc}",
             )
             )
+        if inspect.isawaitable(payload):
+            if inspect.iscoroutine(payload):
+                payload.close()
+            return self._result(
+                definition,
+                request,
+                EventStatus.HANDLER_ERROR,
+                resolved=resolved,
+                error="event handler failed: async event handlers require EventKernel.execute",
+            )
+        if not isinstance(payload, dict):
+            return self._result(
+                definition,
+                request,
+                EventStatus.HANDLER_ERROR,
+                resolved=resolved,
+                error="event handler returned non-object payload",
+            )
         return self._result(
         return self._result(
             definition,
             definition,
             request,
             request,
             EventStatus.SUCCESS,
             EventStatus.SUCCESS,
-            arguments=arguments,
+            resolved=resolved,
             payload=dict(payload),
             payload=dict(payload),
         )
         )
 
 
@@ -158,6 +238,7 @@ class EventKernel:
                 status=EventStatus.UNKNOWN,
                 status=EventStatus.UNKNOWN,
                 source=request.source,
                 source=request.source,
                 arguments=dict(request.arguments),
                 arguments=dict(request.arguments),
+                raw_arguments=request.raw_arguments,
                 error="unknown event",
                 error="unknown event",
             )
             )
         if enabled_names is not None and request.name not in set(enabled_names):
         if enabled_names is not None and request.name not in set(enabled_names):
@@ -165,56 +246,162 @@ class EventKernel:
                 definition,
                 definition,
                 request,
                 request,
                 EventStatus.DISABLED,
                 EventStatus.DISABLED,
-                arguments=dict(request.arguments),
+                resolved=ResolvedEventArguments(
+                    event_name=request.name,
+                    arguments=dict(request.arguments),
+                    raw_arguments=request.raw_arguments,
+                ),
                 error="event disabled",
                 error="event disabled",
             )
             )
         return definition, None
         return definition, None
 
 
-    def _deterministic_arguments(
+    def _resolve_deterministic(
         self,
         self,
         definition: EventDefinition,
         definition: EventDefinition,
         request: EventRequest,
         request: EventRequest,
         context: EventExecutionContext,
         context: EventExecutionContext,
-    ) -> dict[str, Any]:
+    ) -> tuple[ResolvedEventArguments | None, EventResult | None]:
         if request.source is EventSource.PROVIDER_RESOLVED:
         if request.source is EventSource.PROVIDER_RESOLVED:
-            return dict(request.arguments)
-        if definition.resolver is None:
-            return dict(request.arguments)
-        return dict(definition.resolver(request, context))
+            return (
+                ResolvedEventArguments(
+                    event_name=request.name,
+                    arguments=dict(request.arguments),
+                    raw_arguments=request.raw_arguments,
+                ),
+                None,
+            )
+        try:
+            arguments = (
+                definition.resolver(request, context)
+                if definition.resolver is not None
+                else dict(request.arguments)
+            )
+        except Exception as exc:
+            return None, self._resolution_error(
+                definition,
+                request,
+                f"event argument resolver failed: {exc}",
+            )
+        if not isinstance(arguments, dict):
+            return None, self._resolution_error(
+                definition,
+                request,
+                "event argument resolver returned invalid payload",
+            )
+        try:
+            raw_arguments = self._json_arguments(arguments)
+        except (TypeError, ValueError) as exc:
+            return None, self._resolution_error(
+                definition,
+                request,
+                f"event argument resolver failed to serialize: {exc}",
+            )
+        return (
+            ResolvedEventArguments(
+                event_name=request.name,
+                arguments=dict(arguments),
+                raw_arguments=raw_arguments,
+            ),
+            None,
+        )
+
+    def _normalize_fallback(
+        self,
+        definition: EventDefinition,
+        request: EventRequest,
+        value: Any,
+    ) -> tuple[ResolvedEventArguments | None, EventResult | None]:
+        if not isinstance(value, ResolvedEventArguments):
+            return None, self._resolution_error(
+                definition,
+                request,
+                "event argument fallback returned invalid payload",
+                used_fallback=True,
+            )
+        if not isinstance(value.arguments, dict):
+            return None, self._resolution_error(
+                definition,
+                request,
+                "event argument fallback returned invalid payload",
+                used_fallback=True,
+            )
+        if value.event_name != request.name:
+            return None, self._resolution_error(
+                definition,
+                request,
+                f"fallback returned tool {value.event_name} for {request.name}",
+                resolved=value,
+                used_fallback=True,
+            )
+        try:
+            raw_arguments = json.loads(value.raw_arguments)
+        except (TypeError, json.JSONDecodeError):
+            return None, self._resolution_error(
+                definition,
+                request,
+                "fallback raw arguments are not valid JSON",
+                resolved=value,
+                used_fallback=True,
+            )
+        if raw_arguments != value.arguments:
+            return None, self._resolution_error(
+                definition,
+                request,
+                "fallback raw arguments do not match parsed arguments",
+                resolved=value,
+                used_fallback=True,
+            )
+        return value, None
 
 
     def _validation_error(
     def _validation_error(
         self,
         self,
         definition: EventDefinition,
         definition: EventDefinition,
         arguments: dict[str, Any],
         arguments: dict[str, Any],
     ) -> str | None:
     ) -> str | None:
-        required = definition.parameters.get("required", [])
-        missing = [name for name in required if name not in arguments]
-        if missing:
-            return f"missing required arguments: {', '.join(missing)}"
-
-        properties = definition.parameters.get("properties", {})
-        for name, value in arguments.items():
-            expected_name = properties.get(name, {}).get("type")
-            if expected_name is not None and not self._matches_json_type(
-                value,
-                expected_name,
-            ):
-                return f"invalid argument type for {name}: expected {expected_name}"
+        validator = self.registry.validator(definition.name)
+        assert validator is not None
+        try:
+            validator.validate(arguments)
+        except ValidationError as exc:
+            if exc.validator == "required":
+                missing = [
+                    name
+                    for name in exc.validator_value
+                    if name not in exc.instance
+                ]
+                return f"missing required arguments: {', '.join(missing)}"
+            if exc.validator == "type" and exc.path:
+                return (
+                    f"invalid argument type for {exc.path[-1]}: "
+                    f"expected {exc.validator_value}"
+                )
+            return f"invalid event arguments: {exc.message}"
+        except Exception as exc:
+            return f"event argument validation failed: {exc}"
         return None
         return None
 
 
-    def _matches_json_type(self, value: Any, expected_name: str) -> bool:
-        if expected_name == "number":
-            return not isinstance(value, bool) and isinstance(value, (int, float))
-        if expected_name == "integer":
-            return not isinstance(value, bool) and isinstance(value, int)
-        python_types = {
-            "string": str,
-            "boolean": bool,
-            "object": dict,
-            "array": list,
-        }
-        expected_type = python_types.get(expected_name)
-        return expected_type is None or isinstance(value, expected_type)
+    def _resolution_error(
+        self,
+        definition: EventDefinition,
+        request: EventRequest,
+        error: str,
+        *,
+        resolved: ResolvedEventArguments | None = None,
+        used_fallback: bool = False,
+    ) -> EventResult:
+        return self._result(
+            definition,
+            request,
+            EventStatus.RESOLUTION_ERROR,
+            resolved=resolved
+            or ResolvedEventArguments(
+                event_name=request.name,
+                arguments=dict(request.arguments),
+                raw_arguments=request.raw_arguments,
+            ),
+            error=error,
+            used_fallback=used_fallback,
+        )
 
 
     def _result(
     def _result(
         self,
         self,
@@ -222,7 +409,7 @@ class EventKernel:
         request: EventRequest,
         request: EventRequest,
         status: EventStatus,
         status: EventStatus,
         *,
         *,
-        arguments: dict[str, Any],
+        resolved: ResolvedEventArguments,
         payload: dict[str, Any] | None = None,
         payload: dict[str, Any] | None = None,
         error: str | None = None,
         error: str | None = None,
         used_fallback: bool = False,
         used_fallback: bool = False,
@@ -232,7 +419,8 @@ class EventKernel:
             event_name=request.name,
             event_name=request.name,
             status=status,
             status=status,
             source=request.source,
             source=request.source,
-            arguments=dict(arguments),
+            arguments=dict(resolved.arguments),
+            raw_arguments=resolved.raw_arguments,
             payload=payload or {},
             payload=payload or {},
             error=error,
             error=error,
             used_fallback=used_fallback,
             used_fallback=used_fallback,
@@ -245,3 +433,6 @@ class EventKernel:
             timeout_seconds=definition.timeout_seconds,
             timeout_seconds=definition.timeout_seconds,
             terminal=definition.terminal,
             terminal=definition.terminal,
         )
         )
+
+    def _json_arguments(self, arguments: dict[str, Any]) -> str:
+        return json.dumps(arguments, ensure_ascii=False, separators=(",", ":"))

+ 9 - 0
src/agent_lab/application/events/models.py

@@ -18,6 +18,7 @@ class EventStatus(StrEnum):
     UNKNOWN = "unknown"
     UNKNOWN = "unknown"
     DISABLED = "disabled"
     DISABLED = "disabled"
     INVALID_ARGUMENTS = "invalid_arguments"
     INVALID_ARGUMENTS = "invalid_arguments"
+    RESOLUTION_ERROR = "resolution_error"
     HANDLER_ERROR = "handler_error"
     HANDLER_ERROR = "handler_error"
 
 
 
 
@@ -55,6 +56,13 @@ class EventRequest:
     raw_arguments: str = "{}"
     raw_arguments: str = "{}"
 
 
 
 
+@dataclass(frozen=True)
+class ResolvedEventArguments:
+    event_name: str
+    arguments: dict[str, Any]
+    raw_arguments: str
+
+
 EventHandlerResult = dict[str, Any] | Awaitable[dict[str, Any]]
 EventHandlerResult = dict[str, Any] | Awaitable[dict[str, Any]]
 EventHandler = Callable[[EventRequest], EventHandlerResult]
 EventHandler = Callable[[EventRequest], EventHandlerResult]
 EventArgumentResolver = Callable[
 EventArgumentResolver = Callable[
@@ -89,6 +97,7 @@ class EventResult:
     status: EventStatus
     status: EventStatus
     source: EventSource
     source: EventSource
     arguments: dict[str, Any] = field(default_factory=dict)
     arguments: dict[str, Any] = field(default_factory=dict)
+    raw_arguments: str = "{}"
     payload: dict[str, Any] = field(default_factory=dict)
     payload: dict[str, Any] = field(default_factory=dict)
     error: str | None = None
     error: str | None = None
     used_fallback: bool = False
     used_fallback: bool = False

+ 16 - 0
src/agent_lab/application/events/registry.py

@@ -3,23 +3,39 @@ from __future__ import annotations
 from collections.abc import Iterable
 from collections.abc import Iterable
 from copy import deepcopy
 from copy import deepcopy
 
 
+from jsonschema import Draft202012Validator
+from jsonschema.exceptions import SchemaError
+
 from agent_lab.application.events.models import EventDefinition
 from agent_lab.application.events.models import EventDefinition
 
 
 
 
 class EventRegistry:
 class EventRegistry:
     def __init__(self, definitions: Iterable[EventDefinition] = ()) -> None:
     def __init__(self, definitions: Iterable[EventDefinition] = ()) -> None:
         self._definitions: dict[str, EventDefinition] = {}
         self._definitions: dict[str, EventDefinition] = {}
+        self._validators: dict[str, Draft202012Validator] = {}
         for definition in definitions:
         for definition in definitions:
             self.register(definition)
             self.register(definition)
 
 
     def register(self, definition: EventDefinition) -> None:
     def register(self, definition: EventDefinition) -> None:
         if definition.name in self._definitions:
         if definition.name in self._definitions:
             raise ValueError(f"duplicate event definition: {definition.name}")
             raise ValueError(f"duplicate event definition: {definition.name}")
+        try:
+            Draft202012Validator.check_schema(definition.parameters)
+        except SchemaError as exc:
+            raise ValueError(
+                f"invalid event schema for {definition.name}: {exc.message}"
+            ) from None
         self._definitions[definition.name] = definition
         self._definitions[definition.name] = definition
+        self._validators[definition.name] = Draft202012Validator(
+            definition.parameters
+        )
 
 
     def definition(self, name: str) -> EventDefinition | None:
     def definition(self, name: str) -> EventDefinition | None:
         return self._definitions.get(name)
         return self._definitions.get(name)
 
 
+    def validator(self, name: str) -> Draft202012Validator | None:
+        return self._validators.get(name)
+
     def catalog(self, enabled_names: Iterable[str] | None = None) -> list[dict]:
     def catalog(self, enabled_names: Iterable[str] | None = None) -> list[dict]:
         enabled = set(enabled_names) if enabled_names is not None else None
         enabled = set(enabled_names) if enabled_names is not None else None
         return [
         return [

+ 34 - 4
src/agent_lab/application/tools.py

@@ -1,4 +1,5 @@
-from collections.abc import Callable, Iterable, Sequence
+import inspect
+from collections.abc import Awaitable, Callable, Iterable, Sequence
 from copy import deepcopy
 from copy import deepcopy
 from dataclasses import dataclass
 from dataclasses import dataclass
 from typing import Any
 from typing import Any
@@ -23,7 +24,10 @@ from agent_lab.domain.messages import ChatMessage
 ToolExecutionContext = EventExecutionContext
 ToolExecutionContext = EventExecutionContext
 
 
 
 
-ToolHandler = Callable[[ToolCallEvent], dict[str, Any]]
+ToolHandler = Callable[
+    [ToolCallEvent],
+    dict[str, Any] | Awaitable[dict[str, Any]],
+]
 ToolArgumentResolver = Callable[
 ToolArgumentResolver = Callable[
     [ToolCallEvent, ToolExecutionContext],
     [ToolCallEvent, ToolExecutionContext],
     dict[str, Any],
     dict[str, Any],
@@ -110,6 +114,23 @@ class ToolRegistry:
         )
         )
         return self.tool_payload(result)
         return self.tool_payload(result)
 
 
+    async def handle_async(
+        self,
+        event: ToolCallEvent,
+        context: ToolExecutionContext | None = None,
+    ) -> dict[str, Any]:
+        result = await self.kernel.execute(
+            self.event_request(event),
+            context=context or ToolExecutionContext(history=()),
+        )
+        return self.tool_payload(result)
+
+    async def execute_async(self, event: ToolCallEvent) -> dict[str, Any]:
+        result = await self.kernel.execute(
+            self.event_request(event, source=EventSource.PROVIDER_RESOLVED)
+        )
+        return self.tool_payload(result)
+
     def event_request(
     def event_request(
         self,
         self,
         event: ToolCallEvent,
         event: ToolCallEvent,
@@ -133,6 +154,7 @@ class ToolRegistry:
             error = "tool disabled"
             error = "tool disabled"
         elif result.status is EventStatus.HANDLER_ERROR:
         elif result.status is EventStatus.HANDLER_ERROR:
             error = error.replace("event handler failed:", "tool handler failed:", 1)
             error = error.replace("event handler failed:", "tool handler failed:", 1)
+            error = error.replace("EventKernel.execute", "execute_async")
         return {"tool": result.event_name, "error": error}
         return {"tool": result.event_name, "error": error}
 
 
     def _to_event_definition(self, definition: ToolDefinition) -> EventDefinition:
     def _to_event_definition(self, definition: ToolDefinition) -> EventDefinition:
@@ -147,8 +169,16 @@ class ToolRegistry:
                 return {}
                 return {}
             return deepcopy(request.arguments)
             return deepcopy(request.arguments)
 
 
-        def handler(request: EventRequest) -> dict[str, Any]:
-            return definition.handler(self._tool_event(request))
+        if inspect.iscoroutinefunction(definition.handler):
+
+            async def handler(request: EventRequest) -> dict[str, Any]:
+                return await definition.handler(self._tool_event(request))
+        else:
+
+            def handler(
+                request: EventRequest,
+            ) -> dict[str, Any] | Awaitable[dict[str, Any]]:
+                return definition.handler(self._tool_event(request))
 
 
         return EventDefinition(
         return EventDefinition(
             name=definition.name,
             name=definition.name,

+ 102 - 0
tests/test_event_agent.py

@@ -1,3 +1,4 @@
+import asyncio
 import json
 import json
 from collections.abc import AsyncIterator
 from collections.abc import AsyncIterator
 
 
@@ -94,6 +95,25 @@ class WrongSourceToolCallChatClient:
         yield self.item
         yield self.item
 
 
 
 
+class WrongNameToolCallingChatClient(ToolCallingChatClient):
+    async def stream_chat(
+        self,
+        messages: list[ChatMessage],
+        tools: list[dict],
+        params: AgentParams,
+        tool_choice: dict | None = None,
+    ) -> AsyncIterator[StreamItem]:
+        self.calls.append({"messages": list(messages), "tools": list(tools)})
+        yield StreamItem.provider_tool_call(
+            ToolCallEvent(
+                id="wrong-call",
+                name="wrong.tool",
+                arguments=self.arguments,
+                raw_arguments=json.dumps(self.arguments),
+            )
+        )
+
+
 @pytest.mark.asyncio
 @pytest.mark.asyncio
 async def test_event_agent_uses_deterministic_resolver_before_llm_fallback():
 async def test_event_agent_uses_deterministic_resolver_before_llm_fallback():
     chat_client = ToolCallingChatClient({"message": "LLM generated handoff"})
     chat_client = ToolCallingChatClient({"message": "LLM generated handoff"})
@@ -205,6 +225,88 @@ async def test_event_agent_calls_llm_once_for_incomplete_deterministic_arguments
     }
     }
 
 
 
 
+@pytest.mark.asyncio
+async def test_event_agent_rejects_fallback_provider_call_for_wrong_tool_name():
+    chat_client = WrongNameToolCallingChatClient({"message": "wrong"})
+    registry = ToolRegistry(
+        [
+            ToolDefinition(
+                name="expected.tool",
+                description="Expected tool.",
+                parameters={
+                    "type": "object",
+                    "properties": {"message": {"type": "string"}},
+                    "required": ["message"],
+                },
+                handler=lambda event: {"tool": event.name},
+                argument_resolver=lambda event, context: {},
+            )
+        ]
+    )
+
+    reply = await EventAgent(
+        enabled_tools=["expected.tool"],
+        registry=registry,
+        chat_client=chat_client,
+    ).handle(
+        ToolCallEvent(
+            id="call-1",
+            name="expected.tool",
+            arguments={},
+            raw_arguments="{}",
+        )
+    )
+
+    assert json.loads(reply.content) == {
+        "tool": "expected.tool",
+        "error": "fallback returned tool wrong.tool for expected.tool",
+    }
+
+
+@pytest.mark.asyncio
+async def test_event_agent_handle_many_starts_independent_handlers_concurrently():
+    started: list[str] = []
+    both_started = asyncio.Event()
+
+    async def handler(event: ToolCallEvent) -> dict:
+        started.append(event.name)
+        if len(started) == 2:
+            both_started.set()
+        await asyncio.wait_for(both_started.wait(), timeout=0.2)
+        return {"tool": event.name}
+
+    registry = ToolRegistry(
+        [
+            ToolDefinition(
+                name=name,
+                description=f"Handle {name}.",
+                parameters={"type": "object"},
+                handler=handler,
+            )
+            for name in ("event.first", "event.second")
+        ]
+    )
+    events = [
+        ToolCallEvent(id=f"call-{index}", name=name, arguments={}, raw_arguments="{}")
+        for index, name in enumerate(("event.first", "event.second"), start=1)
+    ]
+
+    replies = await asyncio.wait_for(
+        EventAgent(
+            enabled_tools=[event.name for event in events],
+            registry=registry,
+        ).handle_many(events, history=[]),
+        timeout=0.5,
+    )
+
+    assert started == ["event.first", "event.second"]
+    assert [reply.name for reply in replies] == ["event.first", "event.second"]
+    assert [json.loads(reply.content) for reply in replies] == [
+        {"tool": "event.first"},
+        {"tool": "event.second"},
+    ]
+
+
 @pytest.mark.asyncio
 @pytest.mark.asyncio
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
     "item",
     "item",

+ 407 - 14
tests/test_event_kernel.py

@@ -1,5 +1,8 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
+import gc
+import json
+import warnings
 from typing import Any
 from typing import Any
 
 
 import pytest
 import pytest
@@ -11,6 +14,7 @@ from agent_lab.application.events import (
     EventKernel,
     EventKernel,
     EventRegistry,
     EventRegistry,
     EventRequest,
     EventRequest,
+    ResolvedEventArguments,
     EventSource,
     EventSource,
     EventStatus,
     EventStatus,
     ResultPolicy,
     ResultPolicy,
@@ -77,6 +81,13 @@ def test_registry_rejects_duplicate_definition_names():
         EventRegistry([_definition(), _definition()])
         EventRegistry([_definition(), _definition()])
 
 
 
 
+def test_registry_rejects_invalid_draft_2020_12_schema():
+    definition = _definition(parameters={"type": 42})
+
+    with pytest.raises(ValueError, match="invalid event schema for example.lookup"):
+        EventRegistry([definition])
+
+
 def test_tool_registry_public_api_remains_compatible():
 def test_tool_registry_public_api_remains_compatible():
     registry = ToolRegistry(
     registry = ToolRegistry(
         [
         [
@@ -161,9 +172,13 @@ async def test_kernel_calls_fallback_once_when_required_arguments_are_incomplete
         definition: EventDefinition,
         definition: EventDefinition,
         request: EventRequest,
         request: EventRequest,
         context: EventExecutionContext,
         context: EventExecutionContext,
-    ) -> dict[str, Any]:
+    ) -> ResolvedEventArguments:
         fallback_calls.append(dict(request.arguments))
         fallback_calls.append(dict(request.arguments))
-        return {"query": "resolved once"}
+        return ResolvedEventArguments(
+            event_name=definition.name,
+            arguments={"query": "resolved once"},
+            raw_arguments='{"query":"resolved once"}',
+        )
 
 
     registry = EventRegistry([_definition(resolver=lambda request, context: {})])
     registry = EventRegistry([_definition(resolver=lambda request, context: {})])
 
 
@@ -182,10 +197,14 @@ async def test_kernel_calls_fallback_once_when_required_arguments_are_incomplete
 async def test_kernel_does_not_fallback_when_definition_disallows_it():
 async def test_kernel_does_not_fallback_when_definition_disallows_it():
     fallback_calls = 0
     fallback_calls = 0
 
 
-    async def fallback(*args: Any) -> dict[str, Any]:
+    async def fallback(*args: Any) -> ResolvedEventArguments:
         nonlocal fallback_calls
         nonlocal fallback_calls
         fallback_calls += 1
         fallback_calls += 1
-        return {"query": "not allowed"}
+        return ResolvedEventArguments(
+            event_name="example.lookup",
+            arguments={"query": "not allowed"},
+            raw_arguments='{"query":"not allowed"}',
+        )
 
 
     registry = EventRegistry(
     registry = EventRegistry(
         [
         [
@@ -217,10 +236,14 @@ async def test_provider_resolved_arguments_are_not_rewritten_or_fallen_back():
         resolver_calls += 1
         resolver_calls += 1
         return {"query": "rewritten"}
         return {"query": "rewritten"}
 
 
-    async def fallback(*args: Any) -> dict[str, Any]:
+    async def fallback(*args: Any) -> ResolvedEventArguments:
         nonlocal fallback_calls
         nonlocal fallback_calls
         fallback_calls += 1
         fallback_calls += 1
-        return {"query": "fallback"}
+        return ResolvedEventArguments(
+            event_name="example.lookup",
+            arguments={"query": "fallback"},
+            raw_arguments='{"query":"fallback"}',
+        )
 
 
     registry = EventRegistry([_definition(resolver=resolver)])
     registry = EventRegistry([_definition(resolver=resolver)])
 
 
@@ -244,10 +267,14 @@ async def test_provider_resolved_arguments_are_not_rewritten_or_fallen_back():
 async def test_provider_resolved_missing_arguments_return_invalid_without_fallback():
 async def test_provider_resolved_missing_arguments_return_invalid_without_fallback():
     fallback_calls = 0
     fallback_calls = 0
 
 
-    async def fallback(*args: Any) -> dict[str, Any]:
+    async def fallback(*args: Any) -> ResolvedEventArguments:
         nonlocal fallback_calls
         nonlocal fallback_calls
         fallback_calls += 1
         fallback_calls += 1
-        return {"query": "fallback"}
+        return ResolvedEventArguments(
+            event_name="example.lookup",
+            arguments={"query": "fallback"},
+            raw_arguments='{"query":"fallback"}',
+        )
 
 
     result = await EventKernel(
     result = await EventKernel(
         EventRegistry([_definition()]), argument_fallback=fallback
         EventRegistry([_definition()]), argument_fallback=fallback
@@ -331,6 +358,363 @@ async def test_kernel_normalizes_handler_exceptions():
     assert result.error == "event handler failed: boom"
     assert result.error == "event handler failed: boom"
 
 
 
 
+@pytest.mark.asyncio
+async def test_kernel_validates_complete_draft_2020_12_schema():
+    definition = _definition(
+        parameters={
+            "type": "object",
+            "properties": {
+                "mode": {"enum": ["quick", "deep"]},
+                "target": {"type": ["string", "null"]},
+                "filters": {
+                    "type": "array",
+                    "items": {
+                        "type": "object",
+                        "properties": {"score": {"type": "number", "minimum": 0}},
+                        "required": ["score"],
+                        "additionalProperties": False,
+                    },
+                },
+            },
+            "required": ["mode", "target", "filters"],
+            "additionalProperties": False,
+        },
+        handler=lambda request: {"event": request.name},
+    )
+    kernel = EventKernel(EventRegistry([definition]))
+
+    valid = await kernel.execute(
+        EventRequest(
+            id="valid",
+            name=definition.name,
+            arguments={
+                "mode": "deep",
+                "target": None,
+                "filters": [{"score": 0.5}],
+            },
+            source=EventSource.PROVIDER_RESOLVED,
+        ),
+        enabled_names=[definition.name],
+    )
+    invalid = await kernel.execute(
+        EventRequest(
+            id="invalid",
+            name=definition.name,
+            arguments={
+                "mode": "other",
+                "target": 7,
+                "filters": [{"score": -1, "extra": True}],
+                "unexpected": True,
+            },
+            source=EventSource.PROVIDER_RESOLVED,
+        ),
+        enabled_names=[definition.name],
+    )
+
+    assert valid.status is EventStatus.SUCCESS
+    assert invalid.status is EventStatus.INVALID_ARGUMENTS
+    assert invalid.error.startswith("invalid event arguments:")
+
+
+@pytest.mark.asyncio
+async def test_kernel_normalizes_validator_runtime_exceptions():
+    definition = _definition(parameters={"$ref": "urn:agent-lab:missing-schema"})
+
+    result = await EventKernel(EventRegistry([definition])).execute(
+        EventRequest(
+            id="event-1",
+            name=definition.name,
+            arguments={},
+            source=EventSource.PROVIDER_RESOLVED,
+        ),
+        enabled_names=[definition.name],
+    )
+
+    assert result.status is EventStatus.INVALID_ARGUMENTS
+    assert result.error.startswith("event argument validation failed:")
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("boundary", ["resolver", "fallback"])
+async def test_kernel_normalizes_resolution_boundary_exceptions(boundary: str):
+    def resolver(request: EventRequest, context: EventExecutionContext) -> dict[str, Any]:
+        if boundary == "resolver":
+            raise RuntimeError("resolver boom")
+        return {}
+
+    async def fallback(*args: Any) -> ResolvedEventArguments:
+        raise RuntimeError("fallback boom")
+
+    result = await EventKernel(
+        EventRegistry([_definition(resolver=resolver)]),
+        argument_fallback=fallback,
+    ).execute(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.RESOLUTION_ERROR
+    assert result.error == f"event argument {boundary} failed: {boundary} boom"
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("boundary", ["resolver", "fallback"])
+async def test_kernel_normalizes_invalid_resolution_payloads(boundary: str):
+    resolver = (
+        (lambda request, context: None)
+        if boundary == "resolver"
+        else (lambda request, context: {})
+    )
+
+    async def fallback(*args: Any) -> Any:
+        return {"query": "legacy bare mapping"}
+
+    result = await EventKernel(
+        EventRegistry([_definition(resolver=resolver)]),
+        argument_fallback=fallback,
+    ).execute(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.RESOLUTION_ERROR
+    assert result.error == f"event argument {boundary} returned invalid payload"
+
+
+@pytest.mark.asyncio
+async def test_kernel_normalizes_non_json_resolver_arguments():
+    result = await EventKernel(
+        EventRegistry(
+            [
+                _definition(
+                    resolver=lambda request, context: {"query": object()}
+                )
+            ]
+        )
+    ).execute(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.RESOLUTION_ERROR
+    assert result.error.startswith("event argument resolver failed to serialize:")
+
+
+@pytest.mark.asyncio
+async def test_kernel_normalizes_non_object_fallback_arguments():
+    async def fallback(*args: Any) -> ResolvedEventArguments:
+        return ResolvedEventArguments(
+            event_name="example.lookup",
+            arguments=["not", "an", "object"],  # type: ignore[arg-type]
+            raw_arguments='["not","an","object"]',
+        )
+
+    result = await EventKernel(
+        EventRegistry([_definition(resolver=lambda request, context: {})]),
+        argument_fallback=fallback,
+    ).execute(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.RESOLUTION_ERROR
+    assert result.error == "event argument fallback returned invalid payload"
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+    ("resolved", "expected_error"),
+    [
+        (
+            ResolvedEventArguments(
+                event_name="another.event",
+                arguments={"query": "value"},
+                raw_arguments='{"query":"value"}',
+            ),
+            "fallback returned tool another.event for example.lookup",
+        ),
+        (
+            ResolvedEventArguments(
+                event_name="example.lookup",
+                arguments={"query": "value"},
+                raw_arguments="not-json",
+            ),
+            "fallback raw arguments are not valid JSON",
+        ),
+        (
+            ResolvedEventArguments(
+                event_name="example.lookup",
+                arguments={"query": "parsed"},
+                raw_arguments='{"query":"raw"}',
+            ),
+            "fallback raw arguments do not match parsed arguments",
+        ),
+    ],
+)
+async def test_kernel_rejects_inconsistent_structured_fallback(
+    resolved: ResolvedEventArguments,
+    expected_error: str,
+):
+    async def fallback(*args: Any) -> ResolvedEventArguments:
+        return resolved
+
+    result = await EventKernel(
+        EventRegistry([_definition(resolver=lambda request, context: {})]),
+        argument_fallback=fallback,
+    ).execute(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.RESOLUTION_ERROR
+    assert result.error == expected_error
+
+
+@pytest.mark.asyncio
+async def test_kernel_passes_consistent_fallback_arguments_and_raw_json_to_handler():
+    captured: list[EventRequest] = []
+
+    async def fallback(*args: Any) -> ResolvedEventArguments:
+        return ResolvedEventArguments(
+            event_name="example.lookup",
+            arguments={"query": "resolved"},
+            raw_arguments='{"query":"resolved"}',
+        )
+
+    result = await EventKernel(
+        EventRegistry(
+            [
+                _definition(
+                    resolver=lambda request, context: {},
+                    handler=lambda request: captured.append(request) or {"ok": True},
+                )
+            ]
+        ),
+        argument_fallback=fallback,
+    ).execute(
+        EventRequest(id="event-1", name="example.lookup"),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.SUCCESS
+    assert captured[0].arguments == json.loads(captured[0].raw_arguments)
+    assert result.raw_arguments == captured[0].raw_arguments
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("payload", [None, "text", 1, ["item"]])
+async def test_kernel_normalizes_invalid_handler_payloads(payload: Any):
+    result = await EventKernel(
+        EventRegistry([_definition(handler=lambda request: payload)])
+    ).execute(
+        EventRequest(
+            id="event-1",
+            name="example.lookup",
+            arguments={"query": "value"},
+            source=EventSource.PROVIDER_RESOLVED,
+        ),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.HANDLER_ERROR
+    assert result.error == "event handler returned non-object payload"
+
+
+@pytest.mark.asyncio
+async def test_async_kernel_supports_async_handler():
+    async def handler(request: EventRequest) -> dict[str, Any]:
+        return {"query": request.arguments["query"]}
+
+    result = await EventKernel(
+        EventRegistry([_definition(handler=handler)])
+    ).execute(
+        EventRequest(
+            id="event-1",
+            name="example.lookup",
+            arguments={"query": "async"},
+            source=EventSource.PROVIDER_RESOLVED,
+        ),
+        enabled_names=["example.lookup"],
+    )
+
+    assert result.status is EventStatus.SUCCESS
+    assert result.payload == {"query": "async"}
+
+
+@pytest.mark.asyncio
+async def test_tool_registry_async_entry_points_support_async_handler():
+    async def handler(event: ToolCallEvent) -> dict[str, Any]:
+        return {"tool": event.name, "query": event.arguments["query"]}
+
+    registry = ToolRegistry(
+        [
+            ToolDefinition(
+                name="async.lookup",
+                description="Async lookup.",
+                parameters={
+                    "type": "object",
+                    "properties": {"query": {"type": "string"}},
+                    "required": ["query"],
+                },
+                handler=handler,
+            )
+        ]
+    )
+    event = ToolCallEvent(
+        id="call-1",
+        name="async.lookup",
+        arguments={"query": "value"},
+        raw_arguments='{"query":"value"}',
+    )
+
+    assert await registry.handle_async(event) == {
+        "tool": "async.lookup",
+        "query": "value",
+    }
+    assert await registry.execute_async(event) == {
+        "tool": "async.lookup",
+        "query": "value",
+    }
+
+
+def test_tool_registry_sync_facade_rejects_async_handler_without_runtime_warning():
+    called = False
+
+    async def handler(event: ToolCallEvent) -> dict[str, Any]:
+        nonlocal called
+        called = True
+        return {"tool": event.name}
+
+    registry = ToolRegistry(
+        [
+            ToolDefinition(
+                name="async.lookup",
+                description="Async lookup.",
+                parameters={"type": "object"},
+                handler=handler,
+            )
+        ]
+    )
+    event = ToolCallEvent(
+        id="call-1",
+        name="async.lookup",
+        arguments={},
+        raw_arguments="{}",
+    )
+
+    with warnings.catch_warnings(record=True) as captured:
+        warnings.simplefilter("always")
+        payload = registry.execute(event)
+        gc.collect()
+
+    assert payload == {
+        "tool": "async.lookup",
+        "error": "tool handler failed: async event handlers require execute_async",
+    }
+    assert called is False
+    assert not [warning for warning in captured if issubclass(warning.category, RuntimeWarning)]
+
+
 @pytest.mark.asyncio
 @pytest.mark.asyncio
 async def test_kernel_rejects_boolean_for_json_number_arguments():
 async def test_kernel_rejects_boolean_for_json_number_arguments():
     definition = _definition(
     definition = _definition(
@@ -391,11 +775,20 @@ async def test_definition_metadata_survives_registration_and_result_creation():
     assert result.terminal is True
     assert result.terminal is True
 
 
 
 
-def test_kernel_source_has_no_builtin_event_name_branches():
-    from pathlib import Path
+@pytest.mark.asyncio
+@pytest.mark.parametrize("name", ["alpha.one", "beta-two", "任意.事件"])
+async def test_kernel_applies_identical_behavior_to_arbitrary_event_names(name: str):
+    definition = _definition(name=name)
 
 
-    source = Path("src/agent_lab/application/events/kernel.py").read_text()
+    result = await EventKernel(EventRegistry([definition])).execute(
+        EventRequest(
+            id="event-1",
+            name=name,
+            arguments={"query": "value"},
+            source=EventSource.PROVIDER_RESOLVED,
+        ),
+        enabled_names=[name],
+    )
 
 
-    assert "handoff_note" not in source
-    assert "mock_search" not in source
-    assert "mock_ticket" not in source
+    assert result.status is EventStatus.SUCCESS
+    assert result.payload == {"event": name, "query": "value"}

+ 175 - 0
uv.lock

@@ -9,6 +9,7 @@ source = { editable = "." }
 dependencies = [
 dependencies = [
     { name = "fastapi" },
     { name = "fastapi" },
     { name = "httpx" },
     { name = "httpx" },
+    { name = "jsonschema" },
     { name = "pydantic-settings" },
     { name = "pydantic-settings" },
     { name = "uvicorn", extra = ["standard"] },
     { name = "uvicorn", extra = ["standard"] },
     { name = "websockets" },
     { name = "websockets" },
@@ -24,6 +25,7 @@ dev = [
 requires-dist = [
 requires-dist = [
     { name = "fastapi", specifier = ">=0.111.0" },
     { name = "fastapi", specifier = ">=0.111.0" },
     { name = "httpx", specifier = ">=0.27.0" },
     { name = "httpx", specifier = ">=0.27.0" },
+    { name = "jsonschema", specifier = ">=4.26.0" },
     { name = "pydantic-settings", specifier = ">=2.3.0" },
     { name = "pydantic-settings", specifier = ">=2.3.0" },
     { name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" },
     { name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" },
     { name = "websockets", specifier = ">=12.0" },
     { name = "websockets", specifier = ">=12.0" },
@@ -66,6 +68,15 @@ wheels = [
     { url = "https://mirrors.aliyun.com/pypi/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72" },
     { url = "https://mirrors.aliyun.com/pypi/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72" },
 ]
 ]
 
 
+[[package]]
+name = "attrs"
+version = "26.1.0"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32" }
+wheels = [
+    { url = "https://mirrors.aliyun.com/pypi/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309" },
+]
+
 [[package]]
 [[package]]
 name = "certifi"
 name = "certifi"
 version = "2026.6.17"
 version = "2026.6.17"
@@ -210,6 +221,33 @@ wheels = [
     { url = "https://mirrors.aliyun.com/pypi/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" },
     { url = "https://mirrors.aliyun.com/pypi/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" },
 ]
 ]
 
 
+[[package]]
+name = "jsonschema"
+version = "4.26.0"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+dependencies = [
+    { name = "attrs" },
+    { name = "jsonschema-specifications" },
+    { name = "referencing" },
+    { name = "rpds-py" },
+]
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326" }
+wheels = [
+    { url = "https://mirrors.aliyun.com/pypi/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+dependencies = [
+    { name = "referencing" },
+]
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d" }
+wheels = [
+    { url = "https://mirrors.aliyun.com/pypi/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe" },
+]
+
 [[package]]
 [[package]]
 name = "packaging"
 name = "packaging"
 version = "26.2"
 version = "26.2"
@@ -461,6 +499,143 @@ wheels = [
     { url = "https://mirrors.aliyun.com/pypi/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b" },
     { url = "https://mirrors.aliyun.com/pypi/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b" },
 ]
 ]
 
 
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+dependencies = [
+    { name = "attrs" },
+    { name = "rpds-py" },
+    { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8" }
+wheels = [
+    { url = "https://mirrors.aliyun.com/pypi/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "2026.6.3"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = { url = "https://mirrors.aliyun.com/pypi/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4" }
+wheels = [
+    { url = "https://mirrors.aliyun.com/pypi/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76" },
+    { url = "https://mirrors.aliyun.com/pypi/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826" },
+]
+
 [[package]]
 [[package]]
 name = "starlette"
 name = "starlette"
 version = "1.3.1"
 version = "1.3.1"