From 92d761d0df134334d67c132434c163d18ec2880e Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 22:04:15 -0500 Subject: [PATCH 01/27] x --- libs/deepagents/backends/protocol.py | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/libs/deepagents/backends/protocol.py b/libs/deepagents/backends/protocol.py index 8cb529d9..638531e7 100644 --- a/libs/deepagents/backends/protocol.py +++ b/libs/deepagents/backends/protocol.py @@ -145,4 +145,49 @@ def edit( ... +@dataclass +class ExecuteResponse: + """Result of code execution. + + Simplified schema optimized for LLM consumption. + """ + + output: str + """Combined stdout and stderr output of the executed command.""" + + exit_code: int | None = None + """The process exit code. 0 indicates success, non-zero indicates failure.""" + + truncated: bool = False + """Whether the output was truncated due to backend limitations.""" + + +@runtime_checkable +class SandboxBackendProtocol(BackendProtocol, Protocol): + """Protocol for sandboxed backends with isolated runtime. + + Sandboxed backends run in isolated environments (e.g., separate processes, + containers) and communicate via defined interfaces. + """ + + def execute( + self, + command: str, + *, + timeout: int = 30 * 60, + ) -> ExecuteResponse: + """Execute a command in the process. + + Simplified interface optimized for LLM consumption. + + Args: + command: Full shell command string to execute. + timeout: Maximum execution time in seconds (default: 30 minutes). + + Returns: + ExecuteResponse with combined output, exit code, optional signal, and truncation flag. + """ + ... + + BackendFactory: TypeAlias = Callable[[ToolRuntime], BackendProtocol] From 5e1e3ba70eb70e4e73cfcea6569144fd21bacb05 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 23:26:46 -0500 Subject: [PATCH 02/27] x --- libs/deepagents/graph.py | 10 +- libs/deepagents/middleware/filesystem.py | 170 ++++++++++++++++++++--- 2 files changed, 154 insertions(+), 26 deletions(-) diff --git a/libs/deepagents/graph.py b/libs/deepagents/graph.py index f2eeee59..12cb5761 100644 --- a/libs/deepagents/graph.py +++ b/libs/deepagents/graph.py @@ -57,9 +57,12 @@ def create_deep_agent( """Create a deep agent. This agent will by default have access to a tool to write todos (write_todos), - six file editing tools: write_file, ls, read_file, edit_file, glob_search, grep_search, + seven file and execution tools: ls, read_file, write_file, edit_file, glob, grep, execute, and a tool to call subagents. + The execute tool allows running shell commands if the backend implements SandboxBackendProtocol. + For non-sandbox backends, the execute tool will return an error message. + Args: model: The model to use. Defaults to Claude Sonnet 4. tools: The tools the agent should have access to. @@ -80,8 +83,9 @@ def create_deep_agent( context_schema: The schema of the deep agent. checkpointer: Optional checkpointer for persisting agent state between runs. store: Optional store for persistent storage (required if backend uses StoreBackend). - backend: Optional backend for file storage. Pass either a Backend instance or a - callable factory like `lambda rt: StateBackend(rt)`. + backend: Optional backend for file storage and execution. Pass either a Backend instance + or a callable factory like `lambda rt: StateBackend(rt)`. For execution support, + use a backend that implements SandboxBackendProtocol (e.g., DockerSandboxBackend). interrupt_on: Optional Dict[str, bool | InterruptOnConfig] mapping tool names to interrupt configs. debug: Whether to enable debug mode. Passed through to create_agent. diff --git a/libs/deepagents/middleware/filesystem.py b/libs/deepagents/middleware/filesystem.py index 89f843c1..445d10c0 100644 --- a/libs/deepagents/middleware/filesystem.py +++ b/libs/deepagents/middleware/filesystem.py @@ -19,7 +19,14 @@ from typing_extensions import TypedDict from deepagents.backends import StateBackend -from deepagents.backends.protocol import BackendFactory, BackendProtocol, EditResult, WriteResult +from deepagents.backends.protocol import ( + BackendFactory, + BackendProtocol, + EditResult, + ExecuteResponse, + SandboxBackendProtocol, + WriteResult, +) from deepagents.backends.utils import ( format_content_with_line_numbers, format_grep_matches, @@ -211,6 +218,22 @@ class FilesystemState(AgentState): - Search Python files only: `grep(pattern="import", glob="*.py")` - Show matching lines: `grep(pattern="error", output_mode="content")`""" +EXECUTE_TOOL_DESCRIPTION = """Execute a command in the sandbox environment. + +Usage: +- The command parameter is the full shell command string to execute +- Commands run in an isolated sandbox environment +- Returns combined stdout/stderr output with exit code +- The timeout parameter sets maximum execution time in seconds (default: 30 minutes) + +Examples: +- execute(command="ls -la") +- execute(command="python script.py", timeout=60) +- execute(command="npm install && npm test") + +Note: This tool is only available if the backend supports execution (SandboxBackendProtocol). +If execution is not supported, the tool will return an error message.""" + FILESYSTEM_SYSTEM_PROMPT = """## Filesystem Tools `ls`, `read_file`, `write_file`, `edit_file`, `glob`, `grep` You have access to a filesystem which you can interact with using these tools. @@ -223,6 +246,13 @@ class FilesystemState(AgentState): - glob: find files matching a pattern (e.g., "**/*.py") - grep: search for text within files""" +EXECUTION_SYSTEM_PROMPT = """## Execute Tool `execute` + +You have access to an `execute` tool for running shell commands in a sandboxed environment. +Use this tool to run commands, scripts, tests, builds, and other shell operations. + +- execute: run a shell command in the sandbox (returns output and exit code)""" + def _get_backend(backend: BACKEND_TYPES, runtime: ToolRuntime) -> BackendProtocol: """Get the resolved backend instance from backend or factory. @@ -440,6 +470,54 @@ def grep( return grep +def _execute_tool_generator( + backend: BackendProtocol | Callable[[ToolRuntime], BackendProtocol], + custom_description: str | None = None, +) -> BaseTool: + """Generate the execute tool for sandbox command execution. + + Args: + backend: Backend to use for execution, or a factory function that takes runtime and returns a backend. + custom_description: Optional custom description for the tool. + + Returns: + Configured execute tool that runs commands if backend supports SandboxBackendProtocol. + """ + tool_description = custom_description or EXECUTE_TOOL_DESCRIPTION + + @tool(description=tool_description) + def execute( + command: str, + runtime: ToolRuntime[None, FilesystemState], + timeout: int = 30 * 60, + ) -> str: + resolved_backend = _get_backend(backend, runtime) + + # Runtime check - fail gracefully if not supported + if not isinstance(resolved_backend, SandboxBackendProtocol): + return ( + "Error: Execution not available. This agent's backend " + "does not support command execution (SandboxBackendProtocol). " + "To use the execute tool, provide a backend that implements SandboxBackendProtocol." + ) + + result: ExecuteResponse = resolved_backend.execute(command, timeout=timeout) + + # Format output for LLM consumption + parts = [result.output] + + if result.exit_code is not None: + status = "succeeded" if result.exit_code == 0 else "failed" + parts.append(f"\n[Command {status} with exit code {result.exit_code}]") + + if result.truncated: + parts.append("\n[Output was truncated due to size limits]") + + return "".join(parts) + + return execute + + TOOL_GENERATORS = { "ls": _ls_tool_generator, "read_file": _read_file_tool_generator, @@ -447,6 +525,7 @@ def grep( "edit_file": _edit_file_tool_generator, "glob": _glob_tool_generator, "grep": _grep_tool_generator, + "execute": _execute_tool_generator, } @@ -454,14 +533,14 @@ def _get_filesystem_tools( backend: BackendProtocol, custom_tool_descriptions: dict[str, str] | None = None, ) -> list[BaseTool]: - """Get filesystem tools. + """Get filesystem and execution tools. Args: - backend: Backend to use for file storage, or a factory function that takes runtime and returns a backend. + backend: Backend to use for file storage and optional execution, or a factory function that takes runtime and returns a backend. custom_tool_descriptions: Optional custom descriptions for tools. Returns: - List of configured filesystem tools (ls, read_file, write_file, edit_file, glob, grep). + List of configured tools (ls, read_file, write_file, edit_file, glob, grep, execute). """ if custom_tool_descriptions is None: custom_tool_descriptions = {} @@ -483,16 +562,18 @@ def _get_filesystem_tools( class FilesystemMiddleware(AgentMiddleware): - """Middleware for providing filesystem tools to an agent. + """Middleware for providing filesystem and optional execution tools to an agent. - This middleware adds six filesystem tools to the agent: ls, read_file, write_file, - edit_file, glob, and grep. Files can be stored using any backend that implements - the BackendProtocol. + This middleware adds seven tools to the agent: ls, read_file, write_file, + edit_file, glob, grep, and execute. Files can be stored using any backend that implements + the BackendProtocol. If the backend implements SandboxBackendProtocol, the execute tool + will run commands in the sandbox; otherwise, it will return an error. Args: - backend: Backend for file storage. If not provided, defaults to StateBackend + backend: Backend for file storage and optional execution. If not provided, defaults to StateBackend (ephemeral storage in agent state). For persistent storage or hybrid setups, - use CompositeBackend with custom routes. + use CompositeBackend with custom routes. For execution support, use a backend + that implements SandboxBackendProtocol. system_prompt: Optional custom system prompt override. custom_tool_descriptions: Optional custom tool descriptions override. tool_token_limit_before_evict: Optional token limit before evicting a tool result to the filesystem. @@ -500,15 +581,20 @@ class FilesystemMiddleware(AgentMiddleware): Example: ```python from deepagents.middleware.filesystem import FilesystemMiddleware - from deepagents.memory.backends import StateBackend, StoreBackend, CompositeBackend + from deepagents.backends import StateBackend, StoreBackend, CompositeBackend from langchain.agents import create_agent - # Ephemeral storage only (default) + # Ephemeral storage only (default, no execution) agent = create_agent(middleware=[FilesystemMiddleware()]) # With hybrid storage (ephemeral + persistent /memories/) backend = CompositeBackend(default=StateBackend(), routes={"/memories/": StoreBackend()}) - agent = create_agent(middleware=[FilesystemMiddleware(memory_backend=backend)]) + agent = create_agent(middleware=[FilesystemMiddleware(backend=backend)]) + + # With sandbox backend (supports execution) + from my_sandbox import DockerSandboxBackend + sandbox = DockerSandboxBackend(container_id="my-container") + agent = create_agent(middleware=[FilesystemMiddleware(backend=sandbox)]) ``` """ @@ -525,7 +611,8 @@ def __init__( """Initialize the filesystem middleware. Args: - backend: Backend for file storage, or a factory callable. Defaults to StateBackend if not provided. + backend: Backend for file storage and optional execution, or a factory callable. + Defaults to StateBackend if not provided. system_prompt: Optional custom system prompt override. custom_tool_descriptions: Optional custom tool descriptions override. tool_token_limit_before_evict: Optional token limit before evicting a tool result to the filesystem. @@ -535,13 +622,16 @@ def __init__( # Use provided backend or default to StateBackend factory self.backend = backend if backend is not None else (lambda rt: StateBackend(rt)) - # Set system prompt (allow full override) - self.system_prompt = system_prompt if system_prompt is not None else FILESYSTEM_SYSTEM_PROMPT + # Track resolved backend for system prompt generation + self._cached_backend: BackendProtocol | None = None + + # Set system prompt (allow full override or None to generate dynamically) + self._custom_system_prompt = system_prompt self.tools = _get_filesystem_tools(self.backend, custom_tool_descriptions) def _get_backend(self, runtime: ToolRuntime) -> BackendProtocol: - """Get the resolved backend instance from backend or factory. + """Get the resolved backend instance from backend or factory, with caching. Args: runtime: The tool runtime context. @@ -558,7 +648,7 @@ def wrap_model_call( request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse], ) -> ModelResponse: - """Update the system prompt to include instructions on using the filesystem. + """Update the system prompt to include instructions on using the filesystem and execution. Args: request: The model request being processed. @@ -567,8 +657,25 @@ def wrap_model_call( Returns: The model response from the handler. """ - if self.system_prompt is not None: - request.system_prompt = request.system_prompt + "\n\n" + self.system_prompt if request.system_prompt else self.system_prompt + # Use custom system prompt if provided, otherwise generate dynamically + if self._custom_system_prompt is not None: + system_prompt = self._custom_system_prompt + else: + # Build dynamic system prompt based on backend capabilities + prompt_parts = [FILESYSTEM_SYSTEM_PROMPT] + + # Add execution instructions if backend supports it + # Note: We can only check if backend is already resolved + if self._cached_backend and isinstance(self._cached_backend, SandboxBackendProtocol): + prompt_parts.append(EXECUTION_SYSTEM_PROMPT) + + system_prompt = "\n\n".join(prompt_parts) + + if system_prompt: + request.system_prompt = ( + request.system_prompt + "\n\n" + system_prompt + if request.system_prompt else system_prompt + ) return handler(request) async def awrap_model_call( @@ -576,7 +683,7 @@ async def awrap_model_call( request: ModelRequest, handler: Callable[[ModelRequest], Awaitable[ModelResponse]], ) -> ModelResponse: - """(async) Update the system prompt to include instructions on using the filesystem. + """(async) Update the system prompt to include instructions on using the filesystem and execution. Args: request: The model request being processed. @@ -585,8 +692,25 @@ async def awrap_model_call( Returns: The model response from the handler. """ - if self.system_prompt is not None: - request.system_prompt = request.system_prompt + "\n\n" + self.system_prompt if request.system_prompt else self.system_prompt + # Use custom system prompt if provided, otherwise generate dynamically + if self._custom_system_prompt is not None: + system_prompt = self._custom_system_prompt + else: + # Build dynamic system prompt based on backend capabilities + prompt_parts = [FILESYSTEM_SYSTEM_PROMPT] + + # Add execution instructions if backend supports it + # Note: We can only check if backend is already resolved + if self._cached_backend and isinstance(self._cached_backend, SandboxBackendProtocol): + prompt_parts.append(EXECUTION_SYSTEM_PROMPT) + + system_prompt = "\n\n".join(prompt_parts) + + if system_prompt: + request.system_prompt = ( + request.system_prompt + "\n\n" + system_prompt + if request.system_prompt else system_prompt + ) return await handler(request) def _process_large_message( From 9955fbe195aad591932899877686f3468b371126 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 10:36:45 -0500 Subject: [PATCH 03/27] x --- libs/deepagents/backends/composite.py | 41 ++++++++++++++++++++- libs/deepagents/graph.py | 2 +- libs/deepagents/middleware/filesystem.py | 46 ++++++++++++++++-------- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/libs/deepagents/backends/composite.py b/libs/deepagents/backends/composite.py index 89bf3729..dbc9270d 100644 --- a/libs/deepagents/backends/composite.py +++ b/libs/deepagents/backends/composite.py @@ -1,6 +1,14 @@ """CompositeBackend: Route operations to different backends based on path prefix.""" -from deepagents.backends.protocol import BackendProtocol, EditResult, FileInfo, GrepMatch, WriteResult +from deepagents.backends.protocol import ( + BackendProtocol, + EditResult, + ExecuteResponse, + FileInfo, + GrepMatch, + SandboxBackendProtocol, + WriteResult, +) from deepagents.backends.state import StateBackend @@ -211,3 +219,34 @@ def edit( except Exception: pass return res + + def execute( + self, + command: str, + *, + timeout: int = 30 * 60, + ) -> ExecuteResponse: + """Execute a command via the default backend. + + Execution is not path-specific, so it always delegates to the default backend. + The default backend must implement SandboxBackendProtocol for this to work. + + Args: + command: Full shell command string to execute. + timeout: Maximum execution time in seconds (default: 30 minutes). + + Returns: + ExecuteResponse with combined output, exit code, and truncation flag. + + Raises: + NotImplementedError: If default backend doesn't support execution. + """ + if isinstance(self.default, SandboxBackendProtocol): + return self.default.execute(command, timeout=timeout) + + # This shouldn't be reached if the runtime check in the execute tool works correctly, + # but we include it as a safety fallback. + raise NotImplementedError( + "Default backend doesn't support command execution (SandboxBackendProtocol). " + "To enable execution, provide a default backend that implements SandboxBackendProtocol." + ) diff --git a/libs/deepagents/graph.py b/libs/deepagents/graph.py index 12cb5761..63ff078b 100644 --- a/libs/deepagents/graph.py +++ b/libs/deepagents/graph.py @@ -85,7 +85,7 @@ def create_deep_agent( store: Optional store for persistent storage (required if backend uses StoreBackend). backend: Optional backend for file storage and execution. Pass either a Backend instance or a callable factory like `lambda rt: StateBackend(rt)`. For execution support, - use a backend that implements SandboxBackendProtocol (e.g., DockerSandboxBackend). + use a backend that implements SandboxBackendProtocol. interrupt_on: Optional Dict[str, bool | InterruptOnConfig] mapping tool names to interrupt configs. debug: Whether to enable debug mode. Passed through to create_agent. diff --git a/libs/deepagents/middleware/filesystem.py b/libs/deepagents/middleware/filesystem.py index 445d10c0..b363939a 100644 --- a/libs/deepagents/middleware/filesystem.py +++ b/libs/deepagents/middleware/filesystem.py @@ -224,11 +224,10 @@ class FilesystemState(AgentState): - The command parameter is the full shell command string to execute - Commands run in an isolated sandbox environment - Returns combined stdout/stderr output with exit code -- The timeout parameter sets maximum execution time in seconds (default: 30 minutes) Examples: - execute(command="ls -la") -- execute(command="python script.py", timeout=60) +- execute(command="python script.py") - execute(command="npm install && npm test") Note: This tool is only available if the backend supports execution (SandboxBackendProtocol). @@ -470,6 +469,29 @@ def grep( return grep +def _supports_execution(backend: BackendProtocol) -> bool: + """Check if a backend supports command execution. + + For CompositeBackend, checks if the default backend supports execution. + For other backends, checks if they implement SandboxBackendProtocol. + + Args: + backend: The backend to check. + + Returns: + True if the backend supports execution, False otherwise. + """ + # Import here to avoid circular dependency + from deepagents.backends.composite import CompositeBackend + + # For CompositeBackend, check the default backend + if isinstance(backend, CompositeBackend): + return isinstance(backend.default, SandboxBackendProtocol) + + # For other backends, use isinstance check + return isinstance(backend, SandboxBackendProtocol) + + def _execute_tool_generator( backend: BackendProtocol | Callable[[ToolRuntime], BackendProtocol], custom_description: str | None = None, @@ -489,19 +511,18 @@ def _execute_tool_generator( def execute( command: str, runtime: ToolRuntime[None, FilesystemState], - timeout: int = 30 * 60, ) -> str: resolved_backend = _get_backend(backend, runtime) # Runtime check - fail gracefully if not supported - if not isinstance(resolved_backend, SandboxBackendProtocol): + if not _supports_execution(resolved_backend): return ( "Error: Execution not available. This agent's backend " "does not support command execution (SandboxBackendProtocol). " "To use the execute tool, provide a backend that implements SandboxBackendProtocol." ) - result: ExecuteResponse = resolved_backend.execute(command, timeout=timeout) + result: ExecuteResponse = resolved_backend.execute(command) # Format output for LLM consumption parts = [result.output] @@ -593,6 +614,7 @@ class FilesystemMiddleware(AgentMiddleware): # With sandbox backend (supports execution) from my_sandbox import DockerSandboxBackend + sandbox = DockerSandboxBackend(container_id="my-container") agent = create_agent(middleware=[FilesystemMiddleware(backend=sandbox)]) ``` @@ -666,16 +688,13 @@ def wrap_model_call( # Add execution instructions if backend supports it # Note: We can only check if backend is already resolved - if self._cached_backend and isinstance(self._cached_backend, SandboxBackendProtocol): + if self._cached_backend and _supports_execution(self._cached_backend): prompt_parts.append(EXECUTION_SYSTEM_PROMPT) system_prompt = "\n\n".join(prompt_parts) if system_prompt: - request.system_prompt = ( - request.system_prompt + "\n\n" + system_prompt - if request.system_prompt else system_prompt - ) + request.system_prompt = request.system_prompt + "\n\n" + system_prompt if request.system_prompt else system_prompt return handler(request) async def awrap_model_call( @@ -701,16 +720,13 @@ async def awrap_model_call( # Add execution instructions if backend supports it # Note: We can only check if backend is already resolved - if self._cached_backend and isinstance(self._cached_backend, SandboxBackendProtocol): + if self._cached_backend and _supports_execution(self._cached_backend): prompt_parts.append(EXECUTION_SYSTEM_PROMPT) system_prompt = "\n\n".join(prompt_parts) if system_prompt: - request.system_prompt = ( - request.system_prompt + "\n\n" + system_prompt - if request.system_prompt else system_prompt - ) + request.system_prompt = request.system_prompt + "\n\n" + system_prompt if request.system_prompt else system_prompt return await handler(request) def _process_large_message( From a83993a9c211da57ce5b2eefb29107c6e11c955b Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 10:43:02 -0500 Subject: [PATCH 04/27] x --- libs/deepagents/middleware/filesystem.py | 2 +- .../backends/test_composite_backend.py | 84 ++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/libs/deepagents/middleware/filesystem.py b/libs/deepagents/middleware/filesystem.py index b363939a..7968e34c 100644 --- a/libs/deepagents/middleware/filesystem.py +++ b/libs/deepagents/middleware/filesystem.py @@ -522,7 +522,7 @@ def execute( "To use the execute tool, provide a backend that implements SandboxBackendProtocol." ) - result: ExecuteResponse = resolved_backend.execute(command) + result = resolved_backend.execute(command) # Format output for LLM consumption parts = [result.output] diff --git a/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py b/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py index a813c6d7..3cbbf18f 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py @@ -2,11 +2,12 @@ from deepagents.backends.composite import CompositeBackend from deepagents.backends.filesystem import FilesystemBackend -from deepagents.backends.protocol import WriteResult +from deepagents.backends.protocol import ExecuteResponse, SandboxBackendProtocol, WriteResult from deepagents.backends.state import StateBackend from deepagents.backends.store import StoreBackend from langchain.tools import ToolRuntime from langgraph.store.memory import InMemoryStore +import pytest def make_runtime(tid: str = "tc"): @@ -386,3 +387,84 @@ def test_composite_backend_intercept_large_tool_result_routed_to_store(): stored_item = rt.store.get(("filesystem",), "/test_routed_123") assert stored_item is not None assert stored_item.value["content"] == [large_content] + + +# Mock sandbox backend for testing execute functionality +class MockSandboxBackend(StateBackend): + """Mock sandbox backend that implements SandboxBackendProtocol.""" + + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + """Mock execute that returns the command as output.""" + return ExecuteResponse( + output=f"Executed: {command}", + exit_code=0, + truncated=False, + ) + + +def test_composite_backend_execute_with_sandbox_default(): + """Test that CompositeBackend.execute() delegates to sandbox default backend.""" + rt = make_runtime("t_exec1") + sandbox = MockSandboxBackend(rt) + store = StoreBackend(rt) + + comp = CompositeBackend(default=sandbox, routes={"/memories/": store}) + + # Execute should work since default backend supports it + result = comp.execute("ls -la") + assert isinstance(result, ExecuteResponse) + assert result.output == "Executed: ls -la" + assert result.exit_code == 0 + assert result.truncated is False + + +def test_composite_backend_execute_without_sandbox_default(): + """Test that CompositeBackend.execute() fails when default doesn't support execution.""" + rt = make_runtime("t_exec2") + state_backend = StateBackend(rt) # StateBackend doesn't implement SandboxBackendProtocol + store = StoreBackend(rt) + + comp = CompositeBackend(default=state_backend, routes={"/memories/": store}) + + # Execute should raise NotImplementedError since default backend doesn't support it + with pytest.raises(NotImplementedError, match="doesn't support command execution"): + comp.execute("ls -la") + + +def test_composite_backend_supports_execution_check(): + """Test the isinstance check works correctly for CompositeBackend.""" + rt = make_runtime("t_exec3") + + # CompositeBackend with sandbox default should pass isinstance check + sandbox = MockSandboxBackend(rt) + comp_with_sandbox = CompositeBackend(default=sandbox, routes={}) + # Note: CompositeBackend itself has execute() method, so isinstance will pass + # but the actual support depends on the default backend + assert hasattr(comp_with_sandbox, "execute") + + # CompositeBackend with non-sandbox default should still have execute() method + # but will raise NotImplementedError when called + state = StateBackend(rt) + comp_without_sandbox = CompositeBackend(default=state, routes={}) + assert hasattr(comp_without_sandbox, "execute") + + +def test_composite_backend_execute_with_routed_backends(): + """Test that execution doesn't interfere with file routing.""" + rt = make_runtime("t_exec4") + sandbox = MockSandboxBackend(rt) + store = StoreBackend(rt) + + comp = CompositeBackend(default=sandbox, routes={"/memories/": store}) + + # Write files to both backends + comp.write("/local.txt", "local content") + comp.write("/memories/persistent.txt", "persistent content") + + # Execute should still work + result = comp.execute("echo test") + assert result.output == "Executed: echo test" + + # File operations should still work + assert "local content" in comp.read("/local.txt") + assert "persistent content" in comp.read("/memories/persistent.txt") From c61fab7ea7d6bab957ca821065f9ccec789eba18 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 11:05:59 -0500 Subject: [PATCH 05/27] x --- libs/deepagents/middleware/filesystem.py | 27 ++++++++++++++----- .../backends/test_composite_backend.py | 4 +-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/libs/deepagents/middleware/filesystem.py b/libs/deepagents/middleware/filesystem.py index 7968e34c..02be4812 100644 --- a/libs/deepagents/middleware/filesystem.py +++ b/libs/deepagents/middleware/filesystem.py @@ -23,7 +23,6 @@ BackendFactory, BackendProtocol, EditResult, - ExecuteResponse, SandboxBackendProtocol, WriteResult, ) @@ -554,19 +553,31 @@ def _get_filesystem_tools( backend: BackendProtocol, custom_tool_descriptions: dict[str, str] | None = None, ) -> list[BaseTool]: - """Get filesystem and execution tools. + """Get filesystem and optionally execution tools. Args: backend: Backend to use for file storage and optional execution, or a factory function that takes runtime and returns a backend. custom_tool_descriptions: Optional custom descriptions for tools. Returns: - List of configured tools (ls, read_file, write_file, edit_file, glob, grep, execute). + List of configured tools. Always includes: ls, read_file, write_file, edit_file, glob, grep. + Includes execute only if backend supports execution and is not a factory function. """ if custom_tool_descriptions is None: custom_tool_descriptions = {} tools = [] + + # Check if we should include execute tool + # Only include it if backend is already instantiated and supports execution + include_execute = False + if not callable(backend): # Backend is already instantiated, not a factory + include_execute = _supports_execution(backend) + for tool_name, tool_generator in TOOL_GENERATORS.items(): + # Skip execute tool if backend doesn't support it + if tool_name == "execute" and not include_execute: + continue + tool = tool_generator(backend, custom_tool_descriptions.get(tool_name)) tools.append(tool) return tools @@ -585,10 +596,12 @@ def _get_filesystem_tools( class FilesystemMiddleware(AgentMiddleware): """Middleware for providing filesystem and optional execution tools to an agent. - This middleware adds seven tools to the agent: ls, read_file, write_file, - edit_file, glob, grep, and execute. Files can be stored using any backend that implements - the BackendProtocol. If the backend implements SandboxBackendProtocol, the execute tool - will run commands in the sandbox; otherwise, it will return an error. + This middleware adds filesystem tools to the agent: ls, read_file, write_file, + edit_file, glob, and grep. Files can be stored using any backend that implements + the BackendProtocol. + + If the backend implements SandboxBackendProtocol, an execute tool is also added + for running shell commands. Args: backend: Backend for file storage and optional execution. If not provided, defaults to StateBackend diff --git a/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py b/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py index 3cbbf18f..09befa03 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py @@ -1,13 +1,13 @@ from pathlib import Path +import pytest from deepagents.backends.composite import CompositeBackend from deepagents.backends.filesystem import FilesystemBackend -from deepagents.backends.protocol import ExecuteResponse, SandboxBackendProtocol, WriteResult +from deepagents.backends.protocol import ExecuteResponse, WriteResult from deepagents.backends.state import StateBackend from deepagents.backends.store import StoreBackend from langchain.tools import ToolRuntime from langgraph.store.memory import InMemoryStore -import pytest def make_runtime(tid: str = "tc"): From cbc88b861014f4db9b464c8d697c5ae7773c293e Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 11:12:00 -0500 Subject: [PATCH 06/27] x --- libs/deepagents/middleware/filesystem.py | 74 +++++++++++-------- .../tests/unit_tests/test_middleware.py | 22 +++--- 2 files changed, 56 insertions(+), 40 deletions(-) diff --git a/libs/deepagents/middleware/filesystem.py b/libs/deepagents/middleware/filesystem.py index 02be4812..92d4ceb3 100644 --- a/libs/deepagents/middleware/filesystem.py +++ b/libs/deepagents/middleware/filesystem.py @@ -553,31 +553,20 @@ def _get_filesystem_tools( backend: BackendProtocol, custom_tool_descriptions: dict[str, str] | None = None, ) -> list[BaseTool]: - """Get filesystem and optionally execution tools. + """Get filesystem and execution tools. Args: backend: Backend to use for file storage and optional execution, or a factory function that takes runtime and returns a backend. custom_tool_descriptions: Optional custom descriptions for tools. Returns: - List of configured tools. Always includes: ls, read_file, write_file, edit_file, glob, grep. - Includes execute only if backend supports execution and is not a factory function. + List of configured tools: ls, read_file, write_file, edit_file, glob, grep, execute. """ if custom_tool_descriptions is None: custom_tool_descriptions = {} tools = [] - # Check if we should include execute tool - # Only include it if backend is already instantiated and supports execution - include_execute = False - if not callable(backend): # Backend is already instantiated, not a factory - include_execute = _supports_execution(backend) - for tool_name, tool_generator in TOOL_GENERATORS.items(): - # Skip execute tool if backend doesn't support it - if tool_name == "execute" and not include_execute: - continue - tool = tool_generator(backend, custom_tool_descriptions.get(tool_name)) tools.append(tool) return tools @@ -657,16 +646,13 @@ def __init__( # Use provided backend or default to StateBackend factory self.backend = backend if backend is not None else (lambda rt: StateBackend(rt)) - # Track resolved backend for system prompt generation - self._cached_backend: BackendProtocol | None = None - # Set system prompt (allow full override or None to generate dynamically) self._custom_system_prompt = system_prompt self.tools = _get_filesystem_tools(self.backend, custom_tool_descriptions) def _get_backend(self, runtime: ToolRuntime) -> BackendProtocol: - """Get the resolved backend instance from backend or factory, with caching. + """Get the resolved backend instance from backend or factory. Args: runtime: The tool runtime context. @@ -683,7 +669,7 @@ def wrap_model_call( request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse], ) -> ModelResponse: - """Update the system prompt to include instructions on using the filesystem and execution. + """Update the system prompt and filter tools based on backend capabilities. Args: request: The model request being processed. @@ -692,22 +678,37 @@ def wrap_model_call( Returns: The model response from the handler. """ + # Check if execute tool is present and if backend supports it + has_execute_tool = any((tool.name if hasattr(tool, "name") else tool.get("name")) == "execute" for tool in request.tools) + + backend_supports_execution = False + if has_execute_tool: + # Resolve backend to check execution support + backend = self._get_backend(request.runtime) + backend_supports_execution = _supports_execution(backend) + + # If execute tool exists but backend doesn't support it, filter it out + if not backend_supports_execution: + filtered_tools = [tool for tool in request.tools if (tool.name if hasattr(tool, "name") else tool.get("name")) != "execute"] + request = request.override(tools=filtered_tools) + has_execute_tool = False + # Use custom system prompt if provided, otherwise generate dynamically if self._custom_system_prompt is not None: system_prompt = self._custom_system_prompt else: - # Build dynamic system prompt based on backend capabilities + # Build dynamic system prompt based on available tools prompt_parts = [FILESYSTEM_SYSTEM_PROMPT] - # Add execution instructions if backend supports it - # Note: We can only check if backend is already resolved - if self._cached_backend and _supports_execution(self._cached_backend): + # Add execution instructions if execute tool is available + if has_execute_tool and backend_supports_execution: prompt_parts.append(EXECUTION_SYSTEM_PROMPT) system_prompt = "\n\n".join(prompt_parts) if system_prompt: - request.system_prompt = request.system_prompt + "\n\n" + system_prompt if request.system_prompt else system_prompt + request = request.override(system_prompt=request.system_prompt + "\n\n" + system_prompt if request.system_prompt else system_prompt) + return handler(request) async def awrap_model_call( @@ -715,7 +716,7 @@ async def awrap_model_call( request: ModelRequest, handler: Callable[[ModelRequest], Awaitable[ModelResponse]], ) -> ModelResponse: - """(async) Update the system prompt to include instructions on using the filesystem and execution. + """(async) Update the system prompt and filter tools based on backend capabilities. Args: request: The model request being processed. @@ -724,22 +725,37 @@ async def awrap_model_call( Returns: The model response from the handler. """ + # Check if execute tool is present and if backend supports it + has_execute_tool = any((tool.name if hasattr(tool, "name") else tool.get("name")) == "execute" for tool in request.tools) + + backend_supports_execution = False + if has_execute_tool: + # Resolve backend to check execution support + backend = self._get_backend(request.runtime) + backend_supports_execution = _supports_execution(backend) + + # If execute tool exists but backend doesn't support it, filter it out + if not backend_supports_execution: + filtered_tools = [tool for tool in request.tools if (tool.name if hasattr(tool, "name") else tool.get("name")) != "execute"] + request = request.override(tools=filtered_tools) + has_execute_tool = False + # Use custom system prompt if provided, otherwise generate dynamically if self._custom_system_prompt is not None: system_prompt = self._custom_system_prompt else: - # Build dynamic system prompt based on backend capabilities + # Build dynamic system prompt based on available tools prompt_parts = [FILESYSTEM_SYSTEM_PROMPT] - # Add execution instructions if backend supports it - # Note: We can only check if backend is already resolved - if self._cached_backend and _supports_execution(self._cached_backend): + # Add execution instructions if execute tool is available + if has_execute_tool and backend_supports_execution: prompt_parts.append(EXECUTION_SYSTEM_PROMPT) system_prompt = "\n\n".join(prompt_parts) if system_prompt: - request.system_prompt = request.system_prompt + "\n\n" + system_prompt if request.system_prompt else system_prompt + request = request.override(system_prompt=request.system_prompt + "\n\n" + system_prompt if request.system_prompt else system_prompt) + return await handler(request) def _process_large_message( diff --git a/libs/deepagents/tests/unit_tests/test_middleware.py b/libs/deepagents/tests/unit_tests/test_middleware.py index 45131006..775d3602 100644 --- a/libs/deepagents/tests/unit_tests/test_middleware.py +++ b/libs/deepagents/tests/unit_tests/test_middleware.py @@ -12,7 +12,7 @@ from deepagents.backends import CompositeBackend, StateBackend, StoreBackend from deepagents.backends.utils import create_file_data, truncate_if_too_long, update_file_data -from deepagents.middleware.filesystem import FILESYSTEM_SYSTEM_PROMPT, FileData, FilesystemMiddleware, FilesystemState +from deepagents.middleware.filesystem import FileData, FilesystemMiddleware, FilesystemState from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware from deepagents.middleware.subagents import SubAgentMiddleware @@ -64,33 +64,33 @@ class TestFilesystemMiddleware: def test_init_default(self): middleware = FilesystemMiddleware() assert callable(middleware.backend) - assert middleware.system_prompt == FILESYSTEM_SYSTEM_PROMPT - assert len(middleware.tools) == 6 + assert middleware._custom_system_prompt is None + assert len(middleware.tools) == 7 # All tools including execute def test_init_with_composite_backend(self): backend_factory = lambda rt: build_composite_state_backend(rt, routes={"/memories/": (lambda r: StoreBackend(r))}) middleware = FilesystemMiddleware(backend=backend_factory) assert callable(middleware.backend) - assert middleware.system_prompt == FILESYSTEM_SYSTEM_PROMPT - assert len(middleware.tools) == 6 + assert middleware._custom_system_prompt is None + assert len(middleware.tools) == 7 # All tools including execute def test_init_custom_system_prompt_default(self): middleware = FilesystemMiddleware(system_prompt="Custom system prompt") assert callable(middleware.backend) - assert middleware.system_prompt == "Custom system prompt" - assert len(middleware.tools) == 6 + assert middleware._custom_system_prompt == "Custom system prompt" + assert len(middleware.tools) == 7 # All tools including execute def test_init_custom_system_prompt_with_composite(self): backend_factory = lambda rt: build_composite_state_backend(rt, routes={"/memories/": (lambda r: StoreBackend(r))}) middleware = FilesystemMiddleware(backend=backend_factory, system_prompt="Custom system prompt") assert callable(middleware.backend) - assert middleware.system_prompt == "Custom system prompt" - assert len(middleware.tools) == 6 + assert middleware._custom_system_prompt == "Custom system prompt" + assert len(middleware.tools) == 7 # All tools including execute def test_init_custom_tool_descriptions_default(self): middleware = FilesystemMiddleware(custom_tool_descriptions={"ls": "Custom ls tool description"}) assert callable(middleware.backend) - assert middleware.system_prompt == FILESYSTEM_SYSTEM_PROMPT + assert middleware._custom_system_prompt is None ls_tool = next(tool for tool in middleware.tools if tool.name == "ls") assert ls_tool.description == "Custom ls tool description" @@ -98,7 +98,7 @@ def test_init_custom_tool_descriptions_with_composite(self): backend_factory = lambda rt: build_composite_state_backend(rt, routes={"/memories/": (lambda r: StoreBackend(r))}) middleware = FilesystemMiddleware(backend=backend_factory, custom_tool_descriptions={"ls": "Custom ls tool description"}) assert callable(middleware.backend) - assert middleware.system_prompt == FILESYSTEM_SYSTEM_PROMPT + assert middleware._custom_system_prompt is None ls_tool = next(tool for tool in middleware.tools if tool.name == "ls") assert ls_tool.description == "Custom ls tool description" From 6c06e988b574a0a95e8046121069016195817d6d Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 11:17:26 -0500 Subject: [PATCH 07/27] x --- libs/deepagents/middleware/filesystem.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/deepagents/middleware/filesystem.py b/libs/deepagents/middleware/filesystem.py index 92d4ceb3..ed29a704 100644 --- a/libs/deepagents/middleware/filesystem.py +++ b/libs/deepagents/middleware/filesystem.py @@ -521,7 +521,11 @@ def execute( "To use the execute tool, provide a backend that implements SandboxBackendProtocol." ) - result = resolved_backend.execute(command) + try: + result = resolved_backend.execute(command) + except NotImplementedError as e: + # Handle case where execute() exists but raises NotImplementedError + return f"Error: Execution not available. {e}" # Format output for LLM consumption parts = [result.output] From 46466f5156123aa6e3111d0ee284dc2162642f36 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 11:24:24 -0500 Subject: [PATCH 08/27] x --- .../test_filesystem_middleware.py | 140 +++++++++++++++ .../tests/unit_tests/test_middleware.py | 159 ++++++++++++++++++ 2 files changed, 299 insertions(+) diff --git a/libs/deepagents/tests/integration_tests/test_filesystem_middleware.py b/libs/deepagents/tests/integration_tests/test_filesystem_middleware.py index f4b711a8..b796fe19 100644 --- a/libs/deepagents/tests/integration_tests/test_filesystem_middleware.py +++ b/libs/deepagents/tests/integration_tests/test_filesystem_middleware.py @@ -2,6 +2,7 @@ import pytest from langchain.agents import create_agent +from langchain.tools import ToolRuntime from langchain_anthropic import ChatAnthropic from langchain_core.messages import HumanMessage from langgraph.checkpoint.memory import MemorySaver @@ -931,6 +932,145 @@ def test_default_backend_fallback(self): read_message = next(msg for msg in messages if msg.type == "tool" and msg.name == "read_file") assert "Hello World" in read_message.content + def test_execute_tool_filtered_for_non_sandbox_backend(self): + """Verify execute tool is filtered out when backend doesn't support it.""" + from langchain.agents import AgentMiddleware + from deepagents.backends.protocol import ExecuteResponse + + # Track what tools are passed to the model + captured_tools = [] + + class CapturingMiddleware(AgentMiddleware): + def wrap_model_call(self, request, handler): + # Capture tool names + captured_tools.clear() + captured_tools.extend([tool.name if hasattr(tool, "name") else tool.get("name") for tool in request.tools]) + return handler(request) + + # Test with StateBackend (no execution support) + agent = create_agent( + model=ChatAnthropic(model="claude-sonnet-4-20250514"), + middleware=[ + FilesystemMiddleware(backend=lambda rt: StateBackend(rt)), + CapturingMiddleware(), + ], + ) + + agent.invoke({"messages": [HumanMessage(content="List files")]}) + + # Execute tool should NOT be in the tools passed to model + assert "execute" not in captured_tools + assert "read_file" in captured_tools + assert "write_file" in captured_tools + + # Test with sandbox backend (has execution support) + class MockSandboxBackend(StateBackend): + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + return ExecuteResponse(output="test", exit_code=0, truncated=False) + + agent_with_sandbox = create_agent( + model=ChatAnthropic(model="claude-sonnet-4-20250514"), + middleware=[ + FilesystemMiddleware(backend=lambda rt: MockSandboxBackend(rt)), + CapturingMiddleware(), + ], + ) + + captured_tools.clear() + agent_with_sandbox.invoke({"messages": [HumanMessage(content="List files")]}) + + # Execute tool SHOULD be in the tools passed to model + assert "execute" in captured_tools + assert "read_file" in captured_tools + + def test_system_prompt_includes_execute_instructions_only_when_supported(self): + """Verify EXECUTION_SYSTEM_PROMPT is only added when backend supports execution.""" + from langchain.agents import AgentMiddleware + from deepagents.backends.protocol import ExecuteResponse + from deepagents.middleware.filesystem import EXECUTION_SYSTEM_PROMPT + + # Track system prompts passed to the model + captured_prompts = [] + + class CapturingMiddleware(AgentMiddleware): + def wrap_model_call(self, request, handler): + captured_prompts.clear() + if request.system_prompt: + captured_prompts.append(request.system_prompt) + return handler(request) + + # Test with StateBackend (no execution support) + agent = create_agent( + model=ChatAnthropic(model="claude-sonnet-4-20250514"), + middleware=[ + FilesystemMiddleware(backend=lambda rt: StateBackend(rt)), + CapturingMiddleware(), + ], + ) + + agent.invoke({"messages": [HumanMessage(content="List files")]}) + + # System prompt should NOT include execute instructions + assert len(captured_prompts) > 0 + prompt = captured_prompts[0] + assert "execute" not in prompt.lower() or "Execute Tool" not in prompt + + # Test with sandbox backend (has execution support) + class MockSandboxBackend(StateBackend): + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + return ExecuteResponse(output="test", exit_code=0, truncated=False) + + agent_with_sandbox = create_agent( + model=ChatAnthropic(model="claude-sonnet-4-20250514"), + middleware=[ + FilesystemMiddleware(backend=lambda rt: MockSandboxBackend(rt)), + CapturingMiddleware(), + ], + ) + + captured_prompts.clear() + agent_with_sandbox.invoke({"messages": [HumanMessage(content="List files")]}) + + # System prompt SHOULD include execute instructions + assert len(captured_prompts) > 0 + prompt = captured_prompts[0] + assert "Execute Tool" in prompt or "execute" in prompt + + def test_composite_backend_execution_support_detection(self): + """Verify _supports_execution correctly detects CompositeBackend capabilities.""" + from deepagents.middleware.filesystem import _supports_execution + from deepagents.backends.protocol import ExecuteResponse + + # Mock sandbox backend + class MockSandboxBackend(StateBackend): + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + return ExecuteResponse(output="test", exit_code=0, truncated=False) + + # Create runtimes + state = {"messages": [], "files": {}} + rt = ToolRuntime( + state=state, + context=None, + tool_call_id="test", + store=InMemoryStore(), + stream_writer=lambda _: None, + config={}, + ) + + # Test CompositeBackend with sandbox default + comp_with_sandbox = CompositeBackend( + default=MockSandboxBackend(rt), + routes={"/memories/": StoreBackend(rt)}, + ) + assert _supports_execution(comp_with_sandbox) + + # Test CompositeBackend with non-sandbox default + comp_without_sandbox = CompositeBackend( + default=StateBackend(rt), + routes={"/memories/": StoreBackend(rt)}, + ) + assert not _supports_execution(comp_without_sandbox) + # Take actions on multiple threads to test longterm memory def assert_longterm_mem_tools(agent, store): diff --git a/libs/deepagents/tests/unit_tests/test_middleware.py b/libs/deepagents/tests/unit_tests/test_middleware.py index 775d3602..a62476b8 100644 --- a/libs/deepagents/tests/unit_tests/test_middleware.py +++ b/libs/deepagents/tests/unit_tests/test_middleware.py @@ -919,6 +919,165 @@ def test_intercept_sanitizes_tool_call_id(self): assert isinstance(result, Command) assert "/large_tool_results/test_call_id" in result.update["files"] + def test_execute_tool_returns_error_when_backend_doesnt_support(self): + """Test that execute tool returns friendly error instead of raising exception.""" + state = FilesystemState(messages=[], files={}) + middleware = FilesystemMiddleware() # Default StateBackend doesn't support execution + + # Find the execute tool + execute_tool = next(tool for tool in middleware.tools if tool.name == "execute") + + # Create runtime with StateBackend + runtime = ToolRuntime( + state=state, + context=None, + tool_call_id="test_exec", + store=InMemoryStore(), + stream_writer=lambda _: None, + config={}, + ) + + # Execute should return error message, not raise exception + result = execute_tool.invoke({"command": "ls -la", "runtime": runtime}) + + assert isinstance(result, str) + assert "Error: Execution not available" in result + assert "does not support command execution" in result + + def test_execute_tool_output_formatting(self): + """Test execute tool formats output correctly.""" + from deepagents.backends.protocol import ExecuteResponse + + # Mock sandbox backend that returns specific output + class FormattingMockSandboxBackend(StateBackend): + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + return ExecuteResponse( + output="Hello world\nLine 2", + exit_code=0, + truncated=False, + ) + + state = FilesystemState(messages=[], files={}) + rt = ToolRuntime( + state=state, + context=None, + tool_call_id="test_fmt", + store=InMemoryStore(), + stream_writer=lambda _: None, + config={}, + ) + + backend = FormattingMockSandboxBackend(rt) + middleware = FilesystemMiddleware(backend=backend) + + execute_tool = next(tool for tool in middleware.tools if tool.name == "execute") + result = execute_tool.invoke({"command": "echo test", "runtime": rt}) + + assert "Hello world\nLine 2" in result + assert "succeeded" in result + assert "exit code 0" in result + + def test_execute_tool_output_formatting_with_failure(self): + """Test execute tool formats failure output correctly.""" + from deepagents.backends.protocol import ExecuteResponse + + # Mock sandbox backend that returns failure + class FailureMockSandboxBackend(StateBackend): + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + return ExecuteResponse( + output="Error: command not found", + exit_code=127, + truncated=False, + ) + + state = FilesystemState(messages=[], files={}) + rt = ToolRuntime( + state=state, + context=None, + tool_call_id="test_fail", + store=InMemoryStore(), + stream_writer=lambda _: None, + config={}, + ) + + backend = FailureMockSandboxBackend(rt) + middleware = FilesystemMiddleware(backend=backend) + + execute_tool = next(tool for tool in middleware.tools if tool.name == "execute") + result = execute_tool.invoke({"command": "nonexistent", "runtime": rt}) + + assert "Error: command not found" in result + assert "failed" in result + assert "exit code 127" in result + + def test_execute_tool_output_formatting_with_truncation(self): + """Test execute tool formats truncated output correctly.""" + from deepagents.backends.protocol import ExecuteResponse + + # Mock sandbox backend that returns truncated output + class TruncatedMockSandboxBackend(StateBackend): + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + return ExecuteResponse( + output="Very long output...", + exit_code=0, + truncated=True, + ) + + state = FilesystemState(messages=[], files={}) + rt = ToolRuntime( + state=state, + context=None, + tool_call_id="test_trunc", + store=InMemoryStore(), + stream_writer=lambda _: None, + config={}, + ) + + backend = TruncatedMockSandboxBackend(rt) + middleware = FilesystemMiddleware(backend=backend) + + execute_tool = next(tool for tool in middleware.tools if tool.name == "execute") + result = execute_tool.invoke({"command": "cat large_file", "runtime": rt}) + + assert "Very long output..." in result + assert "truncated" in result + + def test_supports_execution_helper_with_composite_backend(self): + """Test _supports_execution correctly identifies CompositeBackend capabilities.""" + from deepagents.middleware.filesystem import _supports_execution + from deepagents.backends.protocol import ExecuteResponse + + # Mock sandbox backend + class TestSandboxBackend(StateBackend): + def execute(self, command: str, *, timeout: int = 30 * 60) -> ExecuteResponse: + return ExecuteResponse(output="test", exit_code=0, truncated=False) + + state = FilesystemState(messages=[], files={}) + rt = ToolRuntime( + state=state, + context=None, + tool_call_id="test", + store=InMemoryStore(), + stream_writer=lambda _: None, + config={}, + ) + + # StateBackend doesn't support execution + state_backend = StateBackend(rt) + assert not _supports_execution(state_backend) + + # TestSandboxBackend supports execution + sandbox_backend = TestSandboxBackend(rt) + assert _supports_execution(sandbox_backend) + + # CompositeBackend with sandbox default supports execution + comp_with_sandbox = CompositeBackend(default=sandbox_backend, routes={}) + assert _supports_execution(comp_with_sandbox) + + # CompositeBackend with non-sandbox default doesn't support execution + comp_without_sandbox = CompositeBackend(default=state_backend, routes={}) + assert not _supports_execution(comp_without_sandbox) + class TestPatchToolCallsMiddleware: def test_first_message(self) -> None: From 6279ed862f21ecd8ec1cc2bd2b4ad091917144cd Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Tue, 4 Nov 2025 15:39:50 -0500 Subject: [PATCH 09/27] x --- libs/deepagents/integrations/__init__.py | 0 libs/deepagents/integrations/runloop.py | 356 +++++++++++++++++++++++ 2 files changed, 356 insertions(+) create mode 100644 libs/deepagents/integrations/__init__.py create mode 100644 libs/deepagents/integrations/runloop.py diff --git a/libs/deepagents/integrations/__init__.py b/libs/deepagents/integrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py new file mode 100644 index 00000000..2a057f7c --- /dev/null +++ b/libs/deepagents/integrations/runloop.py @@ -0,0 +1,356 @@ +"""BackendProtocol implementation for Runloop.""" + +import datetime +import os +from typing import Optional + +from deepagents.backends.protocol import BackendProtocol, WriteResult, EditResult +from deepagents.backends.utils import ( + FileInfo, + GrepMatch, + check_empty_content, + format_content_with_line_numbers, + perform_string_replacement, +) +from runloop_api_client import Runloop + + +class RunloopBackend: + """Backend that operates on files in a Runloop devbox. + + This implementation uses the Runloop API client to execute commands + and manipulate files within a remote devbox environment. + """ + + # NOTE: As an example, this currently uses a pre-allocated devbox. + # For the real version we would want to create a devbox as needed, + # run one or more commands, and then clean up when finished. + + def __init__( + self, + devbox_id: str, + client: Optional[Runloop] = None, + bearer_token: Optional[str] = None, + ) -> None: + """Initialize Runloop backend. + + Args: + devbox_id: ID of the Runloop devbox to operate on. + client: Optional existing Runloop client instance + bearer_token: Optional API key for creating a new client + (defaults to RUNLOOP_API_KEY environment variable) + """ + if client and bearer_token: + raise ValueError("Provide either client or bearer_token, not both.") + + if client is None: + bearer_token = bearer_token or os.environ.get("RUNLOOP_API_KEY", None) + if bearer_token is None: + raise ValueError("Either client or bearer_token must be provided.") + client = Runloop(bearer_token=bearer_token) + + self._client = client + self._devbox_id = devbox_id + + def exec(self, command: str) -> tuple[str, int]: + """Execute a command in the devbox and return (stdout, exit_status).""" + result = self._client.devboxes.execute_and_await_completion( + id=self._devbox_id, + command=command, + ) + # NOTE: could check exit status for error (non-zero) and + # return stderr here instead / in addition to stdout. + return (result.stdout or "", result.exit_status) + + +class RunloopProtocol(BackendProtocol): + def __init__(self, backend): + self._backend = backend + + def ls_info(self, path: str) -> list[FileInfo]: + """List files and directories in the specified directory (non-recursive). + + Args: + path: Directory path to list files from. + + Returns: + List of FileInfo dicts for files and directories directly in the directory. + Directories have a trailing / in their path and is_dir=True. + """ + # Use find to list only direct children + cmd = f"find '{path}' -maxdepth 1 -mindepth 1 -printf '%p %s %T@ %y %Y\\n' 2>/dev/null" + stdout, exit_code = self._backend.exec(cmd) + + if exit_code != 0 or not stdout.strip(): + # NOTE: this silently ignores errors; not sure what error + # handling semantics are needed here, but presumably not + # this. :) + return [] + + results: list[FileInfo] = [] + for line in stdout.strip().split("\n"): + if not line: + continue + + # Parse out the listing info. + (path, size, modified_secs, filetype, realtype) = line.split() + modtime = datetime.datetime.fromtimestamp(float(modified_secs)).isoformat() + + file_info: FileInfo = { + "path": path + "/" if filetype == "d" else path, + "is_dir": filetype == "d", + "is_file": filetype == "f", + "is_link": filetype == "l", + "size": size if filetype == "f" else 0, + "modified_at": modtime, + } + results.append(file_info) + + results.sort(key=lambda x: x.get("path", "")) + return results + + def read( + self, + file_path: str, + offset: int = 0, + limit: int = 2000, + ) -> str: + """Read file content with line numbers. + + Args: + file_path: File path to read + offset: Line offset to start reading from (0-indexed) + limit: Maximum number of lines to read + + Returns: + Formatted file content with line numbers, or error message. + """ + # Check if file exists and get content + start_line = offset + 1 + cmd = ( + f"if [ ! -f '{file_path}' ]; then " + f"echo 'Error: File not found'; exit 1; " + f"else " + f"tail -n +{start_line} '{file_path}' | head -n {limit}; " + f"fi" + ) + stdout, exit_code = self._backend.exec(cmd) + + if exit_code != 0 or "Error: File not found" in stdout: + return f"Error: File '{file_path}' not found" + + empty_msg = check_empty_content(stdout) + if empty_msg: + return empty_msg + + return format_content_with_line_numbers(stdout, start_line=start_line) + + def write( + self, + file_path: str, + content: str, + ) -> WriteResult: + """Create a new file with content. + + Args: + file_path: Path where to write the file + content: Content to write + + Returns: + WriteResult with path on success or error message on failure. + """ + # QUESTIONS: + # * is the intent here to only support text formats, as with read() and edit()? + # * for text, any assumptions/requirements about the character set? + + # Check if file already exists + check_cmd = f"test -e '{file_path}' && echo 'exists' || echo 'ok'" + stdout, _ = self._backend.exec(check_cmd) + + if "exists" in stdout: + return WriteResult( + error=f"Cannot write to {file_path} because it already exists. Read and then make an edit, or write to a new path." + ) + + # Use the upload_file() method from the Runloop API client. + try: + self._backend._client.devboxes.upload_file( + id=self._backend._devbox_id, + path=file_path, + file=content.encode("utf-8"), # NOTE: might want a different type? + ) + except Exception as e: + # TODO: catch specific exception + return WriteResult(error=f"Error writing file '{file_path}': {e}") + + return WriteResult(path=file_path) + + def edit( + self, + file_path: str, + old_string: str, + new_string: str, + replace_all: bool = False, + ) -> EditResult: + """Edit a file by replacing string occurrences. + + Args: + file_path: Path to the file to edit + old_string: String to find and replace + new_string: Replacement string + replace_all: If True, replace all occurrences + + Returns: + EditResult with path and occurrences on success or error on failure. + """ + # QUESTIONS: + # * this downloads the whole file to replace things locally; are files guaranteed to be small? + # * what semantics do you want for non-existant / empty files? + + try: + # fetch the file + response = self._backend._client.devboxes.download_file( + id=self._backend._devbox_id, + path=file_path + ) + + # do the replacements + new_text, occurrences = perform_string_replacement( + response.text(), old_string, new_string, replace_all + ) + + # write back + self._backend._client.devboxes.upload_file( + id=self._backend._devbox_id, + path=file_path, + file=new_text.encode("utf-8"), # NOTE: might want a different type? + ) + return EditResult(path=file_path, occurrences=occurrences) + + except Exception as e: + # TODO: catch specific exception + return EditResult(error=f"Error writing file '{file_path}': {e}") + + def grep_raw( + self, + pattern: str, + path: Optional[str] = None, + glob: Optional[str] = None, + ) -> list[GrepMatch] | str: + """Search for a pattern in files. + + Args: + pattern: Regular expression pattern to search for + path: Base path to search from (defaults to current directory) + glob: Optional glob pattern to filter files (e.g., "*.py") + + Returns: + List of GrepMatch dicts on success, or error string on invalid input. + """ + # Use grep to search files. NOTE: might need something + # differeent if you have other regex semantics. + search_path = path or "." + + # Build grep command + grep_opts = "-rHn" # recursive, with filename, with line number + + # Add glob pattern if specified + if glob: + grep_opts += f" --include='{glob}'" + + # Escape pattern for shell + pattern_escaped = pattern.replace("'", "\\'") + + cmd = f"grep {grep_opts} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true" + stdout, _ = self._backend.exec(cmd) + + if not stdout.strip(): + return [] + + # Parse grep output: path:line_number:content + matches: list[GrepMatch] = [] + for line in stdout.strip().split("\n"): + if not line: + continue + + # Split on first two colons to handle content with colons + parts = line.split(":", 2) + try: + file_path = parts[0] + line_num = int(parts[1]) + line_text = parts[2] + matches.append( + { + "path": file_path, + "line": line_num, + "text": line_text, + } + ) + except ValueError: + continue + + return matches + + def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: + """Find files matching a glob pattern. + + Args: + pattern: Glob pattern (e.g., "*.py", "**/*.ts") + path: Base path to search from + + Returns: + List of FileInfo dicts for matching files. + """ + # Use Python's glob module via remote execution + pattern_escaped = pattern.replace("'", "'\\''") + path_escaped = path.replace("'", "'\\''") + + # Use a more complicated command, to grab stat output from the + # matching files. Could be simplified if this isn't needed. + python_cmd = ( + f"python3 -c \"" + f"import glob, os, json; " + f"os.chdir('{path_escaped}'); " + f"matches = glob.glob('{pattern_escaped}', recursive=True); " + f"for m in matches: " + f" if os.path.isfile(m): " + f" s = os.stat(m); " + f" print(json.dumps({{'path': m, 'size': s.st_size, 'mtime': s.st_mtime}})); " + f"\" 2>/dev/null" + ) + + stdout, exit_code = self._backend.exec(python_cmd) + + if exit_code != 0 or not stdout.strip(): + return [] + + results: list[FileInfo] = [] + for line in stdout.strip().split("\n"): + if not line: + continue + + try: + import json + + data = json.loads(line) + # Convert relative path to absolute based on search path + file_path = data["path"] + if not file_path.startswith("/"): + if path == "/": + file_path = "/" + file_path + else: + file_path = path.rstrip("/") + "/" + file_path + + results.append( + { + "path": file_path, + "is_dir": False, + "size": data["size"], + "modified_at": str(data["mtime"]), + } + ) + except (json.JSONDecodeError, KeyError): + continue + + results.sort(key=lambda x: x.get("path", "")) + return results \ No newline at end of file From 7aaa9bb788a1f0c6867484a590742127c5883fad Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 09:30:42 -0500 Subject: [PATCH 10/27] x --- libs/deepagents/backends/pagination.py | 26 + libs/deepagents/backends/sandbox.py | 60 ++ libs/deepagents/integrations/daytona.py | 395 ++++++++++ libs/deepagents/integrations/runloop.py | 94 ++- pyproject.toml | 4 +- uv.lock | 926 ++++++++++++++++++++++++ 6 files changed, 1455 insertions(+), 50 deletions(-) create mode 100644 libs/deepagents/backends/pagination.py create mode 100644 libs/deepagents/backends/sandbox.py create mode 100644 libs/deepagents/integrations/daytona.py diff --git a/libs/deepagents/backends/pagination.py b/libs/deepagents/backends/pagination.py new file mode 100644 index 00000000..b7689a7b --- /dev/null +++ b/libs/deepagents/backends/pagination.py @@ -0,0 +1,26 @@ +from typing import Generic, TypedDict, TypeVar + +T = TypeVar("T") + + +class PaginationCursor(TypedDict): + """Pagination cursor for listing sandboxes.""" + + next_cursor: str | None + """Cursor for the next page of results. + + None OR empty string if there are no more results. + + string to be interpreted as an opaque token. + """ + has_more: bool + """Whether there are more results to fetch.""" + + +class PageResults(TypedDict, Generic[T]): + """Page results for listing sandboxes.""" + + items: list[T] + """List of sandbox IDs.""" + cursor: PaginationCursor + """Pagination cursor.""" \ No newline at end of file diff --git a/libs/deepagents/backends/sandbox.py b/libs/deepagents/backends/sandbox.py new file mode 100644 index 00000000..2e7687a4 --- /dev/null +++ b/libs/deepagents/backends/sandbox.py @@ -0,0 +1,60 @@ +import abc +from typing import TypedDict + +from deepagents.backends.fs import FileSystem, FileSystemCapabilities +from deepagents.backends.pagination import PageResults, PaginationCursor +from deepagents.backends.process import Process, ProcessCapabilities + + +class SandboxCapabilities(TypedDict): + """Capabilities of the sandbox backend.""" + + fs: FileSystemCapabilities + process: ProcessCapabilities + + +class Sandbox(abc.ABC): + """Abstract class for sandbox backends.""" + + id: str | None + """Unique identifier for the sandbox if applicable.""" + + fs: FileSystem + """Filesystem backend.""" + process: Process + """Process backend.""" + + @property + def get_capabilities(self) -> SandboxCapabilities: + """Get the capabilities of the sandbox backend.""" + raise NotImplementedError + + +class SandboxMetadata(TypedDict): + """Metadata for a sandbox instance.""" + + id: str + """Unique identifier for the sandbox.""" + + +class SandboxProvider(abc.ABC): + """Abstract class for sandbox providers.""" + + @abc.abstractmethod + def get_or_create(self, id: str | None = None, **kwargs) -> Sandbox: + """Get or create a sandbox instance by ID.""" + + @abc.abstractmethod + def delete(self, id: str) -> None: + """Delete a sandbox instance by ID. + + Do not raise an error if the sandbox does not exist. + """ + + @abc.abstractmethod + def list(self, *, cursor: PaginationCursor | None = None, **kwargs) -> PageResults[SandboxMetadata]: + """List all sandbox IDs.""" + + @abc.abstractmethod + def get_capabilities(self) -> SandboxCapabilities: + """Get the capabilities of the sandbox provider.""" diff --git a/libs/deepagents/integrations/daytona.py b/libs/deepagents/integrations/daytona.py new file mode 100644 index 00000000..2927b762 --- /dev/null +++ b/libs/deepagents/integrations/daytona.py @@ -0,0 +1,395 @@ +"""Daytona sandbox backend implementation.""" + +from __future__ import annotations + +import os +from typing import TYPE_CHECKING + +from deepagents.backends.protocol import FileInfo, GrepMatch, WriteResult, EditResult +from deepagents.backends.process import ExecuteResponse, Process, ProcessCapabilities + +from daytona import CreateSandboxFromSnapshotParams, Daytona, DaytonaConfig +from deepagents.backends.pagination import PageResults, PaginationCursor +from deepagents.backends.sandbox import Sandbox, SandboxCapabilities, SandboxMetadata, \ + SandboxProvider + +if TYPE_CHECKING: + from daytona import Sandbox as DaytonaSandboxClient + + +class DaytonaFileSystem: + """Daytona filesystem implementation conforming to BackendProtocol.""" + + def __init__(self, sandbox: DaytonaSandboxClient) -> None: + """Initialize the DaytonaFileSystem with a Daytona sandbox client.""" + self._sandbox = sandbox + + def ls_info(self, path: str) -> list[FileInfo]: + """Structured listing with file metadata.""" + files = self._sandbox.fs.list_files(path) + + result: list[FileInfo] = [] + for file in files: + # Convert Daytona format to our FileInfo format + file_info: FileInfo = {"path": file.name} + + # Add optional fields if present + if hasattr(file, "is_dir"): + file_info["is_dir"] = file.is_dir + if hasattr(file, "size"): + file_info["size"] = int(file.size) + if hasattr(file, "mod_time"): + file_info["modified_at"] = file.mod_time + + result.append(file_info) + + return result + + def read( + self, + file_path: str, + offset: int = 0, + limit: int = 2000, + ) -> str: + """Read file content with line numbers using a single shell command.""" + # Single command that checks file, reads lines, and formats with line numbers + # tail -n +N starts from line N, head limits output, nl adds line numbers + start_line = offset + 1 + cmd = ( + f"if [ ! -f '{file_path}' ]; then " + f"echo 'Error: File not found'; exit 1; " + f"elif [ ! -s '{file_path}' ]; then " + f"echo 'System reminder: File exists but has empty contents'; " + f"else " + f"tail -n +{start_line} '{file_path}' | head -n {limit} | nl -ba -nrn -w6 -s$'\\t' -v{start_line}; " + f"fi" + ) + result = self._sandbox.process.exec(cmd) + + output = result.result.rstrip() + exit_code = result.exit_code + + if exit_code != 0 or "Error: File not found" in output: + return f"Error: File '{file_path}' not found" + + return output + + def write( + self, + file_path: str, + content: str, + ) -> WriteResult: + """Create a new file. Returns WriteResult; error populated on failure.""" + # Escape content for shell safety + content_escaped = content.replace("'", "'\\''") + + # Check if file already exists + check_cmd = f"test -e '{file_path}' && echo 'exists' || echo 'not_exists'" + check_result = self._sandbox.process.exec(check_cmd) + + if check_result.result.strip() == "exists": + return WriteResult(error=f"Error: File '{file_path}' already exists") + + # Write the file using Python + python_code = ( + f"import os; " + f"os.makedirs(os.path.dirname('{file_path}') or '.', exist_ok=True); " + f"open('{file_path}', 'w').write('''{content_escaped}''')" + ) + + cmd = f'python3 -c "{python_code}" 2>&1' + result = self._sandbox.process.exec(cmd) + + if result.exit_code != 0: + return WriteResult(error=f"Error: Failed to write file '{file_path}': {result.result.strip()}") + + # External storage - no files_update needed + return WriteResult(path=file_path, files_update=None) + + def edit( + self, + file_path: str, + old_string: str, + new_string: str, + replace_all: bool = False, + ) -> EditResult: + """Edit a file by replacing string occurrences. Returns EditResult.""" + # Escape single quotes in the strings for shell safety + old_escaped = old_string.replace("'", "'\\''") + new_escaped = new_string.replace("'", "'\\''") + + # Use Python one-liner for complex string replacement logic + python_code = ( + f"import sys; " + f"text = open('{file_path}', 'r').read(); " + f"old = '''{old_escaped}'''; " + f"new = '''{new_escaped}'''; " + f"count = text.count(old); " + f"sys.exit(1) if count == 0 else (sys.exit(2) if count > 1 and not {replace_all} else None); " + f"result = text.replace(old, new) if {replace_all} else text.replace(old, new, 1); " + f"open('{file_path}', 'w').write(result); " + f"print(count)" + ) + + cmd = f'python3 -c "{python_code}" 2>&1' + result = self._sandbox.process.exec(cmd) + + exit_code = result.exit_code + output = result.result.strip() + + if exit_code == 1: + return EditResult(error=f"Error: String not found in file: '{old_string}'") + if exit_code == 2: + return EditResult(error=f"Error: String '{old_string}' appears multiple times. Use replace_all=True to replace all occurrences.") + if exit_code != 0: + return EditResult(error=f"Error: File '{file_path}' not found") + + count = int(output) + # External storage - no files_update needed + return EditResult(path=file_path, files_update=None, occurrences=count) + + def grep_raw( + self, + pattern: str, + path: str | None = None, + glob: str | None = None, + ) -> list[GrepMatch] | str: + """Structured search results or error string for invalid input.""" + search_path = path or "/" + + # Build grep command to get structured output + grep_opts = "-rHn" # recursive, with filename, with line number + + # Add glob pattern if specified + glob_pattern = "" + if glob: + glob_pattern = f"--include='{glob}'" + + # Escape pattern for shell + pattern_escaped = pattern.replace("'", "'\\''") + + cmd = f"grep {grep_opts} {glob_pattern} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true" + result = self._sandbox.process.exec(cmd) + + output = result.result.rstrip() + if not output: + return [] + + # Parse grep output into GrepMatch objects + matches: list[GrepMatch] = [] + for line in output.split("\n"): + # Format is: path:line_number:text + parts = line.split(":", 2) + if len(parts) >= 3: + matches.append({ + "path": parts[0], + "line": int(parts[1]), + "text": parts[2], + }) + + return matches + + def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: + """Structured glob matching returning FileInfo dicts.""" + # Escape pattern for shell + pattern_escaped = pattern.replace("'", "'\\''") + + # Use Python's glob module for proper glob pattern matching + python_code = ( + f"import glob; " + f"import os; " + f"os.chdir('{path}'); " + f"results = sorted(glob.glob('{pattern_escaped}', recursive=True)); " + f"print('\\n'.join(results))" + ) + + cmd = f'python3 -c "{python_code}" 2>/dev/null' + result = self._sandbox.process.exec(cmd) + + output = result.result.strip() + if not output: + return [] + + # Convert paths to FileInfo dicts + file_infos: list[FileInfo] = [] + for file_path in output.split("\n"): + file_infos.append({"path": file_path}) + + return file_infos + + +class DaytonaProcess(Process): + """Daytona process implementation.""" + + def __init__(self, sandbox: DaytonaSandboxClient) -> None: + """Initialize the DaytonaProcess with a Daytona sandbox client.""" + self._sandbox = sandbox + + def execute( + self, + command: str, + cwd: str | None = None, + *, + timeout: int = 30 * 60, + ) -> ExecuteResponse: + """Execute a command in the process. + + Args: + command: Command to execute as a string. + cwd: Working directory to execute the command in. + timeout: Maximum execution time in seconds (default: 30 minutes). + """ + response = self._sandbox.process.exec(command, cwd=cwd, timeout=timeout) + return ExecuteResponse( + result=response.result, + exit_code=response.exit_code, + ) + + def get_capabilities(self) -> ProcessCapabilities: + """Get the process capabilities.""" + return { + "supports_exec": True, + } + + +class DaytonaSandbox(Sandbox): + """Daytona sandbox implementation.""" + + def __init__(self, sandbox: DaytonaSandboxClient) -> None: + """Initialize the DaytonaSandbox with a Daytona sandbox client.""" + self._sandbox = sandbox + self._fs = DaytonaFileSystem(sandbox) + self._process = DaytonaProcess(sandbox) + + @property + def fs(self) -> DaytonaFileSystem: + """Filesystem backend.""" + return self._fs + + @property + def process(self) -> Process: + """Process backend.""" + return self._process + + @property + def get_capabilities(self) -> SandboxCapabilities: + """Get the capabilities of the sandbox backend.""" + return { + "fs": { + "can_list_files": True, + "can_read": True, + "can_write": True, + "can_edit": True, + "can_grep": True, + "can_glob": True, + }, + "process": self._process.get_capabilities(), + } + + @property + def id(self) -> str: + """Get the sandbox ID.""" + return self._sandbox.id + + +class DaytonaSandboxProvider(SandboxProvider): + """Daytona sandbox provider implementation.""" + + def __init__( + self, + *, + client: Daytona | None = None, + api_key: str | None = None, + auto_stop_minutes: int | None = None, + auto_delete_minutes: int | None = None, + ) -> None: + """Initialize the DaytonaSandboxProvider with a Daytona client. + + Args: + client: An existing Daytona client instance + api_key: API key for creating a new Daytona client + auto_stop_minutes: Minutes of inactivity before sandbox auto-stops. Defaults to 15. + auto_delete_minutes: Minutes after stopping before sandbox is deleted. Defaults to 0 + (delete immediately on stop). + """ + if client and api_key: + raise ValueError("Provide either daytona_client or api_key, not both.") + + if client is None: + api_key = api_key or os.environ.get("DAYTONA_API_KEY") + if api_key is None: + raise ValueError("Either daytona_client or api_key must be provided.") + config = DaytonaConfig(api_key=api_key) + client = Daytona(config) + + self._client = client + self.auto_stop_interval = auto_stop_minutes + self.auto_delete_interval = auto_delete_minutes + + def get_or_create(self, id: str | None = None, **kwargs) -> Sandbox: + """Get or create a sandbox instance by ID. + + If id is None, creates a new sandbox. + If id is provided, retrieves the existing sandbox. + """ + if id is None: + # Create a new sandbox with TTL parameters + sandbox_client = self._client.create( + params=CreateSandboxFromSnapshotParams( + auto_stop_interval=self.auto_stop_interval, + auto_delete_interval=self.auto_delete_interval, + ) + ) + return DaytonaSandbox(sandbox_client) + # Get existing sandbox + sandbox_client = self._client.get(id) + return DaytonaSandbox(sandbox_client) + + def delete(self, id: str) -> None: + """Delete a sandbox instance by ID. + + Do not raise an error if the sandbox does not exist. + """ + try: + sandbox = self._client.get(id) + self._client.delete(sandbox) + except Exception: + # Silently ignore if sandbox doesn't exist + pass + + def list(self, *, cursor: PaginationCursor | None = None, **kwargs) -> PageResults[SandboxMetadata]: + """List all sandbox IDs. + + Note: Daytona's list() method returns a simple list of IDs, + so we don't support pagination at the API level. + """ + # Daytona's list returns list[str] of sandbox IDs + paginated_sandboxes = self._client.list() + items: SandboxMetadata = [{"id": item.id} for item in paginated_sandboxes.items] + # Convert to SandboxMetadata format + items: list[SandboxMetadata] = items + + # Since Daytona doesn't support pagination, we return all items + return PageResults( + items=items, + cursor=PaginationCursor( + next_cursor=None, + has_more=False, + ), + ) + + def get_capabilities(self) -> SandboxCapabilities: + """Get the capabilities of the sandbox provider.""" + return { + "fs": { + "can_list_files": True, + "can_read": True, + "can_write": True, + "can_edit": True, + "can_grep": True, + "can_glob": True, + }, + "process": { + "supports_exec": True, + }, + } diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 2a057f7c..4b000a5c 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -4,15 +4,20 @@ import os from typing import Optional -from deepagents.backends.protocol import BackendProtocol, WriteResult, EditResult -from deepagents.backends.utils import ( +from runloop_api_client import Runloop + +from deepagents.backends.protocol import ( + BackendProtocol, FileInfo, GrepMatch, + WriteResult, + EditResult, +) +from deepagents.backends.utils import ( check_empty_content, format_content_with_line_numbers, perform_string_replacement, ) -from runloop_api_client import Runloop class RunloopBackend: @@ -27,10 +32,10 @@ class RunloopBackend: # run one or more commands, and then clean up when finished. def __init__( - self, - devbox_id: str, - client: Optional[Runloop] = None, - bearer_token: Optional[str] = None, + self, + devbox_id: str, + client: Optional[Runloop] = None, + bearer_token: Optional[str] = None, ) -> None: """Initialize Runloop backend. @@ -55,7 +60,7 @@ def __init__( def exec(self, command: str) -> tuple[str, int]: """Execute a command in the devbox and return (stdout, exit_status).""" result = self._client.devboxes.execute_and_await_completion( - id=self._devbox_id, + devbox_id=self._devbox_id, command=command, ) # NOTE: could check exit status for error (non-zero) and @@ -99,9 +104,7 @@ def ls_info(self, path: str) -> list[FileInfo]: file_info: FileInfo = { "path": path + "/" if filetype == "d" else path, "is_dir": filetype == "d", - "is_file": filetype == "f", - "is_link": filetype == "l", - "size": size if filetype == "f" else 0, + "size": int(size) if filetype == "f" else 0, "modified_at": modtime, } results.append(file_info) @@ -110,10 +113,10 @@ def ls_info(self, path: str) -> list[FileInfo]: return results def read( - self, - file_path: str, - offset: int = 0, - limit: int = 2000, + self, + file_path: str, + offset: int = 0, + limit: int = 2000, ) -> str: """Read file content with line numbers. @@ -127,13 +130,7 @@ def read( """ # Check if file exists and get content start_line = offset + 1 - cmd = ( - f"if [ ! -f '{file_path}' ]; then " - f"echo 'Error: File not found'; exit 1; " - f"else " - f"tail -n +{start_line} '{file_path}' | head -n {limit}; " - f"fi" - ) + cmd = f"if [ ! -f '{file_path}' ]; then echo 'Error: File not found'; exit 1; else tail -n +{start_line} '{file_path}' | head -n {limit}; fi" stdout, exit_code = self._backend.exec(cmd) if exit_code != 0 or "Error: File not found" in stdout: @@ -146,9 +143,9 @@ def read( return format_content_with_line_numbers(stdout, start_line=start_line) def write( - self, - file_path: str, - content: str, + self, + file_path: str, + content: str, ) -> WriteResult: """Create a new file with content. @@ -168,9 +165,7 @@ def write( stdout, _ = self._backend.exec(check_cmd) if "exists" in stdout: - return WriteResult( - error=f"Cannot write to {file_path} because it already exists. Read and then make an edit, or write to a new path." - ) + return WriteResult(error=f"Cannot write to {file_path} because it already exists. Read and then make an edit, or write to a new path.") # Use the upload_file() method from the Runloop API client. try: @@ -183,14 +178,15 @@ def write( # TODO: catch specific exception return WriteResult(error=f"Error writing file '{file_path}': {e}") - return WriteResult(path=file_path) + # External storage - no files_update needed + return WriteResult(path=file_path, files_update=None) def edit( - self, - file_path: str, - old_string: str, - new_string: str, - replace_all: bool = False, + self, + file_path: str, + old_string: str, + new_string: str, + replace_all: bool = False, ) -> EditResult: """Edit a file by replacing string occurrences. @@ -209,15 +205,10 @@ def edit( try: # fetch the file - response = self._backend._client.devboxes.download_file( - id=self._backend._devbox_id, - path=file_path - ) + response = self._backend._client.devboxes.download_file(id=self._backend._devbox_id, path=file_path) # do the replacements - new_text, occurrences = perform_string_replacement( - response.text(), old_string, new_string, replace_all - ) + new_text, occurrences = perform_string_replacement(response.text(), old_string, new_string, replace_all) # write back self._backend._client.devboxes.upload_file( @@ -225,17 +216,18 @@ def edit( path=file_path, file=new_text.encode("utf-8"), # NOTE: might want a different type? ) - return EditResult(path=file_path, occurrences=occurrences) + # External storage - no files_update needed + return EditResult(path=file_path, files_update=None, occurrences=occurrences) except Exception as e: # TODO: catch specific exception return EditResult(error=f"Error writing file '{file_path}': {e}") def grep_raw( - self, - pattern: str, - path: Optional[str] = None, - glob: Optional[str] = None, + self, + pattern: str, + path: Optional[str] = None, + glob: Optional[str] = None, ) -> list[GrepMatch] | str: """Search for a pattern in files. @@ -308,7 +300,7 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: # Use a more complicated command, to grab stat output from the # matching files. Could be simplified if this isn't needed. python_cmd = ( - f"python3 -c \"" + f'python3 -c "' f"import glob, os, json; " f"os.chdir('{path_escaped}'); " f"matches = glob.glob('{pattern_escaped}', recursive=True); " @@ -316,7 +308,7 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: f" if os.path.isfile(m): " f" s = os.stat(m); " f" print(json.dumps({{'path': m, 'size': s.st_size, 'mtime': s.st_mtime}})); " - f"\" 2>/dev/null" + f'" 2>/dev/null' ) stdout, exit_code = self._backend.exec(python_cmd) @@ -353,4 +345,8 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: continue results.sort(key=lambda x: x.get("path", "")) - return results \ No newline at end of file + return results + + def exec(self, command: str) -> tuple[str, int]: + """Execute a command in the devbox and return (stdout, exit_status).""" + return self._backend.exec(command) diff --git a/pyproject.toml b/pyproject.toml index f8306565..9bc80b18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,9 @@ dependencies = [ "langchain-anthropic>=1.0.0,<2.0.0", "langchain>=1.0.2,<2.0.0", "langchain-core>=1.0.0,<2.0.0", - "wcmatch" + "wcmatch", + "runloop-api-client>=0.66.1", + "daytona>=0.112.2", ] diff --git a/uv.lock b/uv.lock index 7f746e04..e549de72 100644 --- a/uv.lock +++ b/uv.lock @@ -8,6 +8,151 @@ members = [ "deepagents-cli", ] +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, + { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, + { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, + { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, + { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, + { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, + { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload-time = "2025-10-28T20:57:36.415Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload-time = "2025-10-28T20:57:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload-time = "2025-10-28T20:57:40.122Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload-time = "2025-10-28T20:57:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload-time = "2025-10-28T20:57:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload-time = "2025-10-28T20:57:47.216Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload-time = "2025-10-28T20:57:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload-time = "2025-10-28T20:57:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload-time = "2025-10-28T20:57:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload-time = "2025-10-28T20:57:55.617Z" }, + { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload-time = "2025-10-28T20:57:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload-time = "2025-10-28T20:57:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload-time = "2025-10-28T20:58:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload-time = "2025-10-28T20:58:03.972Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload-time = "2025-10-28T20:58:06.189Z" }, + { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload-time = "2025-10-28T20:58:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload-time = "2025-10-28T20:58:11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload-time = "2025-10-28T20:58:13.358Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload-time = "2025-10-28T20:58:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload-time = "2025-10-28T20:58:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload-time = "2025-10-28T20:58:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload-time = "2025-10-28T20:58:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload-time = "2025-10-28T20:58:24.672Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload-time = "2025-10-28T20:58:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload-time = "2025-10-28T20:58:29.787Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload-time = "2025-10-28T20:58:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload-time = "2025-10-28T20:58:34.618Z" }, + { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload-time = "2025-10-28T20:58:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload-time = "2025-10-28T20:58:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload-time = "2025-10-28T20:58:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload-time = "2025-10-28T20:58:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload-time = "2025-10-28T20:58:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload-time = "2025-10-28T20:58:50.642Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload-time = "2025-10-28T20:58:52.782Z" }, +] + +[[package]] +name = "aiohttp-retry" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/61/ebda4d8e3d8cfa1fd3db0fb428db2dd7461d5742cea35178277ad180b033/aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1", size = 13608, upload-time = "2024-11-06T10:44:54.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/99/84ba7273339d0f3dfa57901b846489d2e5c2cd731470167757f1935fffbd/aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54", size = 9981, upload-time = "2024-11-06T10:44:52.917Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -50,6 +195,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, ] +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + [[package]] name = "backports-tarfile" version = "1.2.0" @@ -358,14 +512,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, ] +[[package]] +name = "daytona" +version = "0.112.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "daytona-api-client" }, + { name = "daytona-api-client-async" }, + { name = "deprecated" }, + { name = "environs" }, + { name = "httpx" }, + { name = "multipart" }, + { name = "obstore" }, + { name = "pydantic" }, + { name = "toml" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/43/f87b2dc4348ee557a0adb0e86a761b98a36f00ad5ad6c9d110171a6a9aac/daytona-0.112.2.tar.gz", hash = "sha256:2bd383b3ca133801cb89045bc9653c2e9d039755427735410cfc922c7b48ef46", size = 108253, upload-time = "2025-10-31T19:46:25.207Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/0c/6470e6aed88f0f6b0fa0cab410f3745ec7f1b10768ad5f1500cab0c14186/daytona-0.112.2-py3-none-any.whl", hash = "sha256:4a34f6c52b95b73c363dd342dfb0a2019638b40cf9b1b49e09e460bc362151b8", size = 134540, upload-time = "2025-10-31T19:46:23.611Z" }, +] + +[[package]] +name = "daytona-api-client" +version = "0.112.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/01/d1767e53d10faa1a42c4e3e741f1dcc8cfaf1f7eee26bebe192b2611ac5a/daytona_api_client-0.112.2.tar.gz", hash = "sha256:06b796a3933e095f8df967e85cf28d3b13a7bb8818dcf1c8bf6c54a4ba13deb2", size = 121743, upload-time = "2025-10-31T19:45:28.197Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/09/499c066dbba23ac1556946125eabce9d68979e64134ab30f832971eff660/daytona_api_client-0.112.2-py3-none-any.whl", hash = "sha256:399783bd6408f553560f162003dc4698bc461266f4173122e4c482ee15626b00", size = 363537, upload-time = "2025-10-31T19:45:26.57Z" }, +] + +[[package]] +name = "daytona-api-client-async" +version = "0.112.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aiohttp-retry" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/bd/2a9deb816bc7d7948a363cff2638fbbf55791ad844087fc7ed3f304c53a4/daytona_api_client_async-0.112.2.tar.gz", hash = "sha256:a28a2b2dfa118198a71b40e0c8c0c5b794ffe8b7cf3877336495babd6a823386", size = 122632, upload-time = "2025-10-31T19:45:30.53Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/c9/76aa047792bd8c2bb48a7ac65a70ad0d9edce129200e8e07dac24669c974/daytona_api_client_async-0.112.2-py3-none-any.whl", hash = "sha256:a8f645bbfe84e835c19316fe8624b64179eeb62ac9429e38284653cdb24acb74", size = 373082, upload-time = "2025-10-31T19:45:26.581Z" }, +] + [[package]] name = "deepagents" version = "0.2.5" source = { editable = "." } dependencies = [ + { name = "daytona" }, { name = "langchain" }, { name = "langchain-anthropic" }, { name = "langchain-core" }, + { name = "runloop-api-client" }, { name = "wcmatch" }, ] @@ -385,9 +595,11 @@ test = [ [package.metadata] requires-dist = [ + { name = "daytona", specifier = ">=0.112.2" }, { name = "langchain", specifier = ">=1.0.2,<2.0.0" }, { name = "langchain-anthropic", specifier = ">=1.0.0,<2.0.0" }, { name = "langchain-core", specifier = ">=1.0.0,<2.0.0" }, + { name = "runloop-api-client", specifier = ">=0.66.1" }, { name = "wcmatch" }, ] @@ -480,6 +692,18 @@ test = [ { name = "ruff", specifier = ">=0.9.7" }, ] +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + [[package]] name = "distro" version = "1.9.0" @@ -507,6 +731,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8", size = 632667, upload-time = "2025-09-20T17:55:43.052Z" }, ] +[[package]] +name = "environs" +version = "14.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/75/06801d5beeb398ed3903167af9376bb81c4ac41c44a53d45193065ebb1a8/environs-14.5.0.tar.gz", hash = "sha256:f7b8f6fcf3301bc674bc9c03e39b5986d116126ffb96764efd34c339ed9464ee", size = 35426, upload-time = "2025-11-02T21:30:36.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/f3/6961beb9a1e77d01dee1dd48f00fb3064429c8abcfa26aa863eb7cb2b6dd/environs-14.5.0-py3-none-any.whl", hash = "sha256:1abd3e3a5721fb09797438d6c902bc2f35d4580dfaffe68b8ee588b67b504e13", size = 17202, upload-time = "2025-11-02T21:30:35.186Z" }, +] + [[package]] name = "execnet" version = "2.1.1" @@ -516,6 +753,111 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, ] +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -910,6 +1252,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "marshmallow" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/2c/e40834adb0bb6f21d7372ad90e616eda82116d4f090d93c29ceb2366cdaf/marshmallow-4.1.0.tar.gz", hash = "sha256:daa9862f74e2f7864980d25c29b4ea72944cde48aa17537e3bd5797a4ae62d71", size = 220619, upload-time = "2025-11-01T15:40:37.096Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/df/081ea8c41696d598e7cea4f101e49da718a9b6c9dcaaad4e76dfc11a022c/marshmallow-4.1.0-py3-none-any.whl", hash = "sha256:9901660499be3b880dc92d6b5ee0b9a79e94265b7793f71021f92040c07129f1", size = 48286, upload-time = "2025-11-01T15:40:35.542Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -928,6 +1279,132 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, + { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, + { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, + { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "multipart" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/c9/c6f5ab81bae667d4fe42a58df29f4c2db6ad8377cfd0e9baa729e4fa3ebb/multipart-1.3.0.tar.gz", hash = "sha256:a46bd6b0eb4c1ba865beb88ddd886012a3da709b6e7b86084fc37e99087e5cf1", size = 38816, upload-time = "2025-07-26T15:09:38.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/d6/d547a7004b81fa0b2aafa143b09196f6635e4105cd9d2c641fa8a4051c05/multipart-1.3.0-py3-none-any.whl", hash = "sha256:439bf4b00fd7cb2dbff08ae13f49f4f49798931ecd8d496372c63537fa19f304", size = 14938, upload-time = "2025-07-26T15:09:36.884Z" }, +] + [[package]] name = "mypy" version = "1.18.2" @@ -1008,6 +1485,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/67/d5e07efd38194f52b59b8af25a029b46c0643e9af68204ee263022924c27/nh3-0.3.1-cp38-abi3-win_arm64.whl", hash = "sha256:a3e810a92fb192373204456cac2834694440af73d749565b4348e30235da7f0b", size = 586369, upload-time = "2025-10-07T03:27:57.234Z" }, ] +[[package]] +name = "obstore" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/e4/722ab931b8b2544f1d60bceceaa97d22d810f588f3a26ad64997213c2c4d/obstore-0.7.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65ffe43fd63c9968172bed649fcaf6345b41a124be5d34f46adb94604e9ccef8", size = 3680639, upload-time = "2025-08-01T22:37:05.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/99/7f5efcc0110144f32152b23bd284927ee3f34b28962466b81aa98f8229fb/obstore-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2947609a1fab1f9b808235a8088e7e99814fbaf3b6000833d760fd90f68fa7cd", size = 3397505, upload-time = "2025-08-01T22:37:06.745Z" }, + { url = "https://files.pythonhosted.org/packages/15/84/0b21cb4fdeb1ca8aa256acb8cccabb7f0d450a36c03655512fcda308759e/obstore-0.7.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15409f75acc4e10f924fe118f7018607d6d96a72330ac4cc1663d36b7c6847b1", size = 3554635, upload-time = "2025-08-01T22:37:08.27Z" }, + { url = "https://files.pythonhosted.org/packages/e5/eb/0e9ad4d31e49f8bed3d6f482261fcde5035667330d81348cc3ff041f6ef2/obstore-0.7.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5224d834bbe7a9f2592b130e4ddd86340fa172e5a3a51284e706f6515d95c036", size = 3703695, upload-time = "2025-08-01T22:37:09.51Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/49e854c782076a2877d475089eebf3556a5658df0c0544f6182203af5eab/obstore-0.7.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b1af6c1a33d98db9954f7ceab8eb5e543aea683a79a0ffd72b6c8d176834a9b", size = 3976486, upload-time = "2025-08-01T22:37:10.777Z" }, + { url = "https://files.pythonhosted.org/packages/38/16/518e9e47f0d486821a922e82fe7b1e382f8afb62468cd3129d06937d8403/obstore-0.7.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:708c27c4e5e85799fe7a2d2ae443fbd96c2ad36b561c815a9b01b5333ab536ad", size = 4011834, upload-time = "2025-08-01T22:37:12.136Z" }, + { url = "https://files.pythonhosted.org/packages/99/f8/585e3da7d1fc09965d78763ad68063a2fd7fe114e3953af26399db196e70/obstore-0.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7da327920bef8bbd02445f33947487fe4e94fcb9e084c810108e88be57d0877b", size = 3809352, upload-time = "2025-08-01T22:37:13.681Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a2/4fa492ae67b0354f8415fec8b8ca928e94f4e07f8a12b4d7502a856915ce/obstore-0.7.3-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:8f3b23a40ad374fe7a65fab4678a9978978ec83a597156a2a9d1dbeab433a469", size = 3579895, upload-time = "2025-08-01T22:37:15.243Z" }, + { url = "https://files.pythonhosted.org/packages/b4/35/a8034730b2f7d71268f5685fefc5b37eee589d404e5face79bc38ecd1698/obstore-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9b3e7d0c7e85e4f67e479f7efab5dea26ceaace10897d639d38f77831ef0cdaf", size = 3742907, upload-time = "2025-08-01T22:37:16.429Z" }, + { url = "https://files.pythonhosted.org/packages/96/ca/3174ccc5d3d0f91ff1b6f0f258c795671d5a3bb64f8d72937b44733bdc78/obstore-0.7.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:dfee24c5e9d5b7e0f43e4bbf8cc15069e5c60bfdb86873ce97c0eb487afa5da8", size = 3778325, upload-time = "2025-08-01T22:37:17.669Z" }, + { url = "https://files.pythonhosted.org/packages/53/5c/0cba21607f2a294fc0b6fab3a4ddf5eafe94de384c0212cea6977a4bc3ee/obstore-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:99e187cee4a6e13605886b906b34fec7ae9902dd25b1e9aafae863a9d55c6e47", size = 3784247, upload-time = "2025-08-01T22:37:18.954Z" }, + { url = "https://files.pythonhosted.org/packages/f8/87/886402bf40643163a8042da602cdc8ce3b9355062e7c6930af8e5567f6d3/obstore-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5de3b0859512b9ddbf57ac34db96ad41fb85fc9597e422916044d1bf550427d", size = 3983087, upload-time = "2025-08-01T22:37:20.194Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/95b9bfdbb449795d1a6e312dd6ac4469b31b3df5807d85321d6e6762f264/obstore-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35fdd1cd8856984de1b5a11fced83f6fd6623eb459736e57b9975400ff5baf5a", size = 4038939, upload-time = "2025-08-01T22:37:21.411Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/1ba71bad5aa3cd01b6849490f4e8457b4253c60322b70014c5155bce0549/obstore-0.7.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6cbe5dde68bf6ab5a88f3bb467ca8f123bcce3efc03e22fd8339688559d36199", size = 3676670, upload-time = "2025-08-01T22:37:22.904Z" }, + { url = "https://files.pythonhosted.org/packages/26/5f/abea8b6261c0117ff3f7b1da34185806cc7fb0958dd2eec5f25b43d4134c/obstore-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6db23cbcb3aec10e09a31fd0883950cb9b7f77f4fcf1fb0e8a276e1d1961bf3", size = 3387707, upload-time = "2025-08-01T22:37:24.804Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a7/6fe561c2dab64ce69ed05e76902c6eb9ce82c934bd3b3e6e796a2897dd62/obstore-0.7.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00fde287770bdbdbb06379670d30c257b20e77a4a11b36f1e232b5bc6ef07b7a", size = 3558626, upload-time = "2025-08-01T22:37:26.058Z" }, + { url = "https://files.pythonhosted.org/packages/ed/83/f0c25dcce75e5297cba2a8ecb93198b01f4ff7af699fa1296207e30bf02e/obstore-0.7.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c420036356269666197f0704392c9495f255bb3ff9b667c69fb49bc65bd50dcd", size = 3706975, upload-time = "2025-08-01T22:37:27.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/6d/029a65fa2c51443d27d5a6f57a76becc51793d0a53ea0efac2e4fbce3eda/obstore-0.7.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28482626ca9481569ad16ba0c0c36947ce96e8147c64011dc0af6d58be8ff9c", size = 3973329, upload-time = "2025-08-01T22:37:28.592Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d6/0e49f9d6c5e9d0021722c5e3ad7402d8457ffe2743fe245a1b16fc9caf72/obstore-0.7.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cead20055221337ddf218098afe8138f8624395b0cf2a730da72a4523c11b2f", size = 4021499, upload-time = "2025-08-01T22:37:30.135Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8e/daf5d23477c14cd52525b6e8d5046106e37fbf4f4e62fc0a4c0952c7e229/obstore-0.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71017142a593022848f4af0ac1e39af1a56927981cc2c89542888edb206eb33", size = 3806108, upload-time = "2025-08-01T22:37:31.438Z" }, + { url = "https://files.pythonhosted.org/packages/23/a5/123bcc4b0762e479f9bc443b8a91885c90cc92e844543c2f87d48b1b674e/obstore-0.7.3-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:8aebc2bf796a0d1525318a9ac69608a96d03abc621ca1e6d810e08a70bd695c1", size = 3576246, upload-time = "2025-08-01T22:37:32.698Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/c2fc9ebdb84bddf25a644ee15d5855d8c5e29218dd6ee7877a3378b0094d/obstore-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c0ebf03969b81ee559c377c5ebca9dcdffbef0e6650d43659676aeaeb302a272", size = 3739761, upload-time = "2025-08-01T22:37:33.961Z" }, + { url = "https://files.pythonhosted.org/packages/14/be/a04542e8f37b547fa8720d518c333760f90323cbd392e60bf48d1631e965/obstore-0.7.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e0f5d97064ec35fdef3079f867afe6fa5e76ab2bb3e809855ab34a1aa34c9dcd", size = 3784232, upload-time = "2025-08-01T22:37:35.223Z" }, + { url = "https://files.pythonhosted.org/packages/05/d9/d164f871f9dd91fc5870171a3c60f5986d5f9f98a6e58da4663bbe16a662/obstore-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3a80541671646c5e49493de61361a1851c8c172cf28981b76aa4248a9f02f5b1", size = 3788418, upload-time = "2025-08-01T22:37:36.418Z" }, + { url = "https://files.pythonhosted.org/packages/78/9e/59701156233d94b4654637424890188bb5e1154ea53260a93016084ce423/obstore-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ce6385ad89afad106d05d37296f724ba10f8f4e57ab8ad7f4ecce0aa226d3d", size = 3976968, upload-time = "2025-08-01T22:37:37.702Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fe/d551a770ae10fe2ca5feb5c7256c777219614297c6e45d6714ade9b43fbf/obstore-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:632522ba63a44768977defc0a93fc5dd59ea0455bfd6926cd3121971306da4e5", size = 4050093, upload-time = "2025-08-01T22:37:38.962Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ef/491cf28be51301aa9695d8448c4e6489956c162564dbdf4f21836696e294/obstore-0.7.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:dcb71412dc8d2bd464b340d1f36d8c0ceb7894c01c2ceaaa5f2ac45376503fa2", size = 3676519, upload-time = "2025-08-01T22:37:40.194Z" }, + { url = "https://files.pythonhosted.org/packages/f0/12/41c51cca59784d2b6c60a99a2a010f8e73a089416d288db12d91cbcdbd02/obstore-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6d486bb01438039d686401ce4207d82c02b8b639227baa5bdd578efdab388dea", size = 3387665, upload-time = "2025-08-01T22:37:41.431Z" }, + { url = "https://files.pythonhosted.org/packages/cb/27/9aac5a70c6d4a496a837748bc9368e7825dc58761711d5f65cc8bc9d3765/obstore-0.7.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaaf0c9223b5592658c131ff32a0574be995c7e237f406266f9a68ea2266769", size = 3558354, upload-time = "2025-08-01T22:37:42.678Z" }, + { url = "https://files.pythonhosted.org/packages/f2/04/70e6cf1931d56db2f86a359ea171aa403146c04faf20aeb025eeabe254dd/obstore-0.7.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8ae6cde734df3cc542c14152029170d9ae70ce50b957831ed71073113bd3d60", size = 3706831, upload-time = "2025-08-01T22:37:44.415Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a9/758920c8c7256f0cd366a3b0063247a197d9a1e2e189e2309400022787c5/obstore-0.7.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30da82ae3bfdf24fa80af38967e323ae8da0bb7c36cce01f0dda7689faaf1272", size = 3973250, upload-time = "2025-08-01T22:37:45.631Z" }, + { url = "https://files.pythonhosted.org/packages/59/f8/5a6a831d7328a4351caab13ba7faf47cb1bdcb5afba2e46535386ccf1170/obstore-0.7.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5daa9f912eac8cdf218161d34e13f38cbb594e934eaaf8a7c09dca5a394b231", size = 4030160, upload-time = "2025-08-01T22:37:47.208Z" }, + { url = "https://files.pythonhosted.org/packages/67/7d/698e4851049999b4a8ff9622ece0cba86e64c4242fa981e21f9832bdd378/obstore-0.7.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef06cad4e8978d672357b328b4f61c48827b2b79d7eaf58b68ee31ac0e652b8", size = 3805594, upload-time = "2025-08-01T22:37:48.699Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a6/4a9290cac8aaa16a7ce9aec6e8a001ed0d0ed42d1e49570c6770d31f693c/obstore-0.7.3-cp313-cp313-manylinux_2_24_aarch64.whl", hash = "sha256:d34920539a94da2b87195787b80004960638dfd0aa2f4369fc9239e0a41470a8", size = 3575482, upload-time = "2025-08-01T22:37:50.216Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c9/87f7c88daf07a52b5d86a9de0664574ee0dea2f5e6cd26a91ad4688b53fb/obstore-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcdaa779f376745ff493cce7f19cbbe8d75f68304bf1062e757ab60bd62de1", size = 3739411, upload-time = "2025-08-01T22:37:51.483Z" }, + { url = "https://files.pythonhosted.org/packages/69/58/1163bcb48e80e220ef6010130880d24a75239025fde1092356ce71b6efee/obstore-0.7.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ae095f679e4796b8f6ef80ed3813ddd14a477ae219a0c059c23cf294f9288ded", size = 3783914, upload-time = "2025-08-01T22:37:52.857Z" }, + { url = "https://files.pythonhosted.org/packages/75/a2/f5b68265a6ea248adbd4e2f9db2dae7d727ab6ac53a63dfebcf28f1aacea/obstore-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6def59e79c19b8804743fec6407f542b387dc1630c2254412ae8bd3a0b98e7e4", size = 3787905, upload-time = "2025-08-01T22:37:54.414Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2c/23b671c7eaf37097fe9c3c2cc925c466135d4866e2009444daf91f180fed/obstore-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f97797c42476ab19853ef4a161b903eaf96c2363a23b9e0187d66b0daee350cb", size = 3976888, upload-time = "2025-08-01T22:37:55.681Z" }, + { url = "https://files.pythonhosted.org/packages/42/10/5f352e6dd1388f5c8931261357e111a6923121d937a1ebad09f4cf391418/obstore-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:8f0ecc01b1444bc08ff98e368b80ea2c085a7783621075298e86d3aba96f8e27", size = 4050018, upload-time = "2025-08-01T22:37:57.285Z" }, +] + [[package]] name = "openai" version = "2.6.1" @@ -1181,6 +1707,105 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + [[package]] name = "pycparser" version = "2.23" @@ -1408,6 +2033,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -1662,6 +2299,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/7e/61c42657f6e4614a4258f1c3b0c5b93adc4d1f8575f5229d1906b483099b/ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093", size = 12256762, upload-time = "2025-09-04T16:50:15.737Z" }, ] +[[package]] +name = "runloop-api-client" +version = "0.66.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/8b/cfb603162a59a744eb3fce4d2fe33b27bfb64797872bd88bd548db3077f1/runloop_api_client-0.66.1.tar.gz", hash = "sha256:ba4bec099c630735a97bc81d1fd2f79f8461fe2251713ab6ce38739d13439586", size = 305391, upload-time = "2025-10-23T22:14:06.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/6e/97bcf51e0f14b96b32c22f1f7fab6d6aa9a855f74dc78dcb4fe2a4d8114b/runloop_api_client-0.66.1-py3-none-any.whl", hash = "sha256:6d77b538b1d91b80e8ae87acfe3ae0c83c3d816f19b08935d141c643dddd6321", size = 223927, upload-time = "2025-10-23T22:14:04.787Z" }, +] + [[package]] name = "secretstorage" version = "3.4.0" @@ -1675,6 +2330,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1761,6 +2425,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, ] +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + [[package]] name = "tomli" version = "2.3.0" @@ -1872,6 +2545,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] +[[package]] +name = "uuid-utils" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/ef/b6c1fd4fee3b2854bf9d602530ab8b6624882e2691c15a9c4d22ea8c03eb/uuid_utils-0.11.1.tar.gz", hash = "sha256:7ef455547c2ccb712840b106b5ab006383a9bfe4125ba1c5ab92e47bcbf79b46", size = 19933, upload-time = "2025-10-02T13:32:09.526Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/f5/254d7ce4b3aa4a1a3a4f279e0cc74eec8b4d3a61641d8ffc6e983907f2ca/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4bc8cf73c375b9ea11baf70caacc2c4bf7ce9bfd804623aa0541e5656f3dbeaf", size = 581019, upload-time = "2025-10-02T13:31:32.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/e6/f7d14c4e1988d8beb3ac9bd773f370376c704925bdfb07380f5476bb2986/uuid_utils-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0d2cb3bcc6f5862d08a0ee868b18233bc63ba9ea0e85ea9f3f8e703983558eba", size = 294377, upload-time = "2025-10-02T13:31:34.01Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/847a9a0258e7a2a14b015afdaa06ee4754a2680db7b74bac159d594eeb18/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463400604f623969f198aba9133ebfd717636f5e34257340302b1c3ff685dc0f", size = 328070, upload-time = "2025-10-02T13:31:35.619Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/c5d342d31860c9b4f481ef31a4056825961f9b462d216555e76dcee580ea/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aef66b935342b268c6ffc1796267a1d9e73135740a10fe7e4098e1891cbcc476", size = 333610, upload-time = "2025-10-02T13:31:37.058Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4b/52edc023ffcb9ab9a4042a58974a79c39ba7a565e683f1fd9814b504cf13/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd65c41b81b762278997de0d027161f27f9cc4058fa57bbc0a1aaa63a63d6d1a", size = 475669, upload-time = "2025-10-02T13:31:38.38Z" }, + { url = "https://files.pythonhosted.org/packages/59/81/ee55ee63264531bb1c97b5b6033ad6ec81b5cd77f89174e9aef3af3d8889/uuid_utils-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccfac9d5d7522d61accabb8c68448ead6407933415e67e62123ed6ed11f86510", size = 331946, upload-time = "2025-10-02T13:31:39.66Z" }, + { url = "https://files.pythonhosted.org/packages/cf/07/5d4be27af0e9648afa512f0d11bb6d96cb841dd6d29b57baa3fbf55fd62e/uuid_utils-0.11.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:003f48f05c01692d0c1f7e413d194e7299a1a364e0047a4eb904d3478b84eca1", size = 352920, upload-time = "2025-10-02T13:31:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/5b/48/a69dddd9727512b0583b87bfff97d82a8813b28fb534a183c9e37033cfef/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5c936042120bdc30d62f539165beaa4a6ba7e817a89e5409a6f06dc62c677a9", size = 509413, upload-time = "2025-10-02T13:31:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/66/0d/1b529a3870c2354dd838d5f133a1cba75220242b0061f04a904ca245a131/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:2e16dcdbdf4cd34ffb31ead6236960adb50e6c962c9f4554a6ecfdfa044c6259", size = 529454, upload-time = "2025-10-02T13:31:44.338Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f2/04a3f77c85585aac09d546edaf871a4012052fb8ace6dbddd153b4d50f02/uuid_utils-0.11.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f8b21fed11b23134502153d652c77c3a37fa841a9aa15a4e6186d440a22f1a0e", size = 498084, upload-time = "2025-10-02T13:31:45.601Z" }, + { url = "https://files.pythonhosted.org/packages/89/08/538b380b4c4b220f3222c970930fe459cc37f1dfc6c8dc912568d027f17d/uuid_utils-0.11.1-cp39-abi3-win32.whl", hash = "sha256:72abab5ab27c1b914e3f3f40f910532ae242df1b5f0ae43f1df2ef2f610b2a8c", size = 174314, upload-time = "2025-10-02T13:31:47.269Z" }, + { url = "https://files.pythonhosted.org/packages/00/66/971ec830094ac1c7d46381678f7138c1805015399805e7dd7769c893c9c8/uuid_utils-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:5ed9962f8993ef2fd418205f92830c29344102f86871d99b57cef053abf227d9", size = 179214, upload-time = "2025-10-02T13:31:48.344Z" }, +] + [[package]] name = "wcmatch" version = "10.1" @@ -1893,6 +2586,129 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "wrapt" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/19/5e5bcd855d808892fe02d49219f97a50f64cd6d8313d75df3494ee97b1a3/wrapt-2.0.0.tar.gz", hash = "sha256:35a542cc7a962331d0279735c30995b024e852cf40481e384fd63caaa391cbb9", size = 81722, upload-time = "2025-10-19T23:47:54.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/8f/8e4c8b6da60b4205191d588cbac448fb9ff4f5ed89f4e555dc4813ab30cf/wrapt-2.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b7e221abb6c5387819db9323dac3c875b459695057449634f1111955d753c621", size = 77433, upload-time = "2025-10-19T23:45:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/22/9a/01a29ccb029aa8e78241f8b53cb89ae8826c240129abbbb6ebba3416eff9/wrapt-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1147a84c8fc852426580af8b6e33138461ddbc65aa459a25ea539374d32069fa", size = 60641, upload-time = "2025-10-19T23:45:43.866Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ec/e058997971428b7665b5c3665a55b18bb251ea7e08d002925e3ca017c020/wrapt-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d6691d4a711504a0bc10de789842ad6ac627bed22937b10f37a1211a8ab7bb3", size = 61526, upload-time = "2025-10-19T23:45:44.839Z" }, + { url = "https://files.pythonhosted.org/packages/70/c3/c82263503f554715aa1847e85dc75a69631a54e9d7ab0f1a55e34a22d44a/wrapt-2.0.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f460e1eb8e75a17c3918c8e35ba57625721eef2439ef0bcf05304ac278a65e1d", size = 114069, upload-time = "2025-10-19T23:45:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/dc/97/d95e88a3a1bc2890a1aa47880c2762cf0eb6d231b5a64048e351cec6f071/wrapt-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12c37784b77bf043bf65cc96c7195a5db474b8e54173208af076bdbb61df7b3e", size = 116109, upload-time = "2025-10-19T23:45:48.252Z" }, + { url = "https://files.pythonhosted.org/packages/dc/36/cba0bf954f2303897b80fa5342499b43f8c5201110dddf0d578d6841b149/wrapt-2.0.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75e5c049eb583835f7a0e0e311d9dde9bfbaac723a6dd89d052540f9b2809977", size = 112500, upload-time = "2025-10-19T23:45:45.838Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/8cb88e63bec989f641d208acb3fd198bfdbbb4ef7dfb71f0cac3c90b07a9/wrapt-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e50bcbd5b65dac21b82319fcf18486e6ac439947e9305034b00704eb7405f553", size = 115356, upload-time = "2025-10-19T23:45:49.249Z" }, + { url = "https://files.pythonhosted.org/packages/bb/60/a6d5fb94648cd430648705bef9f4241bd22ead123ead552b6d2873ad5240/wrapt-2.0.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:06b78cb6b9320f57737a52fede882640d93cface98332d1a3df0c5696ec9ae9f", size = 111754, upload-time = "2025-10-19T23:45:51.21Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/1963854edf0592ae806307899dc7bf891e76cec19e598f55845c94603a65/wrapt-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c8349ebfc3cd98bc9105e0112dd8c8ac1f3c7cb5601f9d02248cae83a63f748", size = 113789, upload-time = "2025-10-19T23:45:52.473Z" }, + { url = "https://files.pythonhosted.org/packages/62/ec/4b1d76cb6d96ac511aaaa92efc57f528e57f06082a595b8b2663fcdb0f20/wrapt-2.0.0-cp311-cp311-win32.whl", hash = "sha256:028f19ec29e204fe725139d4a8b09f77ecfb64f8f02b7ab5ee822c85e330b68b", size = 57954, upload-time = "2025-10-19T23:45:57.03Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cf/df8ff9bd64d4a75f9a9f6c1c93480a51904d0c9bd71c11994301c47d8a33/wrapt-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:c6961f05e58d919153ba311b397b7b904b907132b7b8344dde47865d4bb5ec89", size = 60308, upload-time = "2025-10-19T23:45:54.314Z" }, + { url = "https://files.pythonhosted.org/packages/69/d8/61e245fe387d58d84b3f913d5da9d909c4f239b887db692a05105aaf2a1b/wrapt-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:be7e316c2accd5a31dbcc230de19e2a846a325f8967fdea72704d00e38e6af06", size = 58822, upload-time = "2025-10-19T23:45:55.772Z" }, + { url = "https://files.pythonhosted.org/packages/3c/28/7f266b5bf50c3ad0c99c524d99faa0f7d6eecb045d950e7d2c9e1f0e1338/wrapt-2.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73c6f734aecb1a030d9a265c13a425897e1ea821b73249bb14471445467ca71c", size = 78078, upload-time = "2025-10-19T23:45:58.855Z" }, + { url = "https://files.pythonhosted.org/packages/06/0c/bbdcad7eb535fae9d6b0fcfa3995c364797cd8e2b423bba5559ab2d88dcf/wrapt-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b4a7f8023b8ce8a36370154733c747f8d65c8697cb977d8b6efeb89291fff23e", size = 61158, upload-time = "2025-10-19T23:46:00.096Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/bba3e7a4ebf4d1624103ee59d97b78a1fbb08fb5753ff5d1b69f5ef5e863/wrapt-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1cb62f686c50e9dab5983c68f6c8e9cbf14a6007935e683662898a7d892fa69", size = 61646, upload-time = "2025-10-19T23:46:01.279Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0c/0f565294897a72493dbafe7b46229b5f09f3776795a894d6b737e98387de/wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:43dc0550ae15e33e6bb45a82a5e1b5495be2587fbaa996244b509921810ee49f", size = 121442, upload-time = "2025-10-19T23:46:04.287Z" }, + { url = "https://files.pythonhosted.org/packages/da/80/7f03501a8a078ad79b19b1a888f9192a9494e62ddf8985267902766a4f30/wrapt-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39c5b45b056d630545e40674d1f5e1b51864b3546f25ab6a4a331943de96262e", size = 123018, upload-time = "2025-10-19T23:46:06.052Z" }, + { url = "https://files.pythonhosted.org/packages/37/6b/ad0e1ff98359f13b4b0c2c52848e792841146fe79ac5f56899b9a028fc0d/wrapt-2.0.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:804e88f824b76240a1b670330637ccfd2d18b9efa3bb4f02eb20b2f64880b324", size = 117369, upload-time = "2025-10-19T23:46:02.53Z" }, + { url = "https://files.pythonhosted.org/packages/ac/6c/a90437bba8cb1ce2ed639af979515e09784678c2a7f4ffc79f2cf7de809e/wrapt-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c2c476aa3fc2b9899c3f7b20963fac4f952e7edb74a31fc92f7745389a2e3618", size = 121453, upload-time = "2025-10-19T23:46:07.747Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a9/b3982f9bd15bd45857a23c48b7c36e47d05db4a4dcc5061c31f169238845/wrapt-2.0.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8d851e526891216f89fcb7a1820dad9bd503ba3468fb9635ee28e93c781aa98e", size = 116250, upload-time = "2025-10-19T23:46:09.385Z" }, + { url = "https://files.pythonhosted.org/packages/73/e2/b7a8b1afac9f791d8f5eac0d9726559f1d7ec4a2b5a6b4e67ac145b007a5/wrapt-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b95733c2360c4a8656ee93c7af78e84c0bd617da04a236d7a456c8faa34e7a2d", size = 120575, upload-time = "2025-10-19T23:46:11.882Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/37920eeea96094f450ae35505d39f1135df951a2cdee0d4e01d4f843396a/wrapt-2.0.0-cp312-cp312-win32.whl", hash = "sha256:ea56817176834edf143df1109ae8fdaa087be82fdad3492648de0baa8ae82bf2", size = 58175, upload-time = "2025-10-19T23:46:15.678Z" }, + { url = "https://files.pythonhosted.org/packages/f0/db/b395f3b0c7f2c60d9219afacc54ceb699801ccf2d3d969ba556dc6d3af20/wrapt-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:3c7d3bee7be7a2665286103f4d1f15405c8074e6e1f89dac5774f9357c9a3809", size = 60415, upload-time = "2025-10-19T23:46:12.913Z" }, + { url = "https://files.pythonhosted.org/packages/86/22/33d660214548af47fc59d9eec8c0e0693bcedc5b3a0b52e8cbdd61f3b646/wrapt-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:680f707e1d26acbc60926659799b15659f077df5897a6791c7c598a5d4a211c4", size = 58911, upload-time = "2025-10-19T23:46:13.889Z" }, + { url = "https://files.pythonhosted.org/packages/18/0a/dd88abfe756b1aa79f0777e5ee4ce9e4b5dc4999bd805e9b04b52efc7b18/wrapt-2.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2ea096db28d5eb64d381af0e93464621ace38a7003a364b6b5ffb7dd713aabe", size = 78083, upload-time = "2025-10-19T23:46:16.937Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/8afebc1655a863bb2178b23c2d699b8743f3a7dab466904adc6155f3c858/wrapt-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c92b5a82d28491e3f14f037e1aae99a27a5e6e0bb161e65f52c0445a3fa7c940", size = 61156, upload-time = "2025-10-19T23:46:17.927Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8b/f710a6528ccc52e21943f42c8cf64814cde90f9adbd3bcd58c7c274b4f75/wrapt-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:81d234718aabe632d179fac52c7f69f0f99fbaac4d4bcd670e62462bbcbfcad7", size = 61641, upload-time = "2025-10-19T23:46:19.229Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5f/e4eabd0cc6684c5b208c2abc5c3459449c4d15be1694a9bbcf51e0e135fd/wrapt-2.0.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db2eea83c43f84e4e41dbbb4c1de371a53166e55f900a6b130c3ef51c6345c1a", size = 121454, upload-time = "2025-10-19T23:46:21.808Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c4/ec31ee17cc7866960d323609ba7402be786d211a6d713a59f776c4270bb3/wrapt-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65f50e356c425c061e1e17fe687ff30e294fed9bf3441dc1f13ef73859c2a817", size = 123063, upload-time = "2025-10-19T23:46:23.545Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2b/a4b10c3c0022e40aeae9bec009bafb049f440493f0575ebb27ecf61c32f8/wrapt-2.0.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:887f2a667e3cbfb19e204032d42ad7dedaa43972e4861dc7a3d51ae951d9b578", size = 117401, upload-time = "2025-10-19T23:46:20.433Z" }, + { url = "https://files.pythonhosted.org/packages/2a/4a/ade23a76967e1f148e461076a4d0e24a7950a5f18b394c9107fe60224ae2/wrapt-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9054829da4be461e3ad3192e4b6bbf1fc18af64c9975ce613aec191924e004dc", size = 121485, upload-time = "2025-10-19T23:46:24.85Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ba/33b5f3e2edede4e1cfd259f0d9c203cf370f259bb9b215dd58fc6cbb94e9/wrapt-2.0.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b952ffd77133a5a2798ee3feb18e51b0a299d2f440961e5bb7737dbb02e57289", size = 116276, upload-time = "2025-10-19T23:46:27.006Z" }, + { url = "https://files.pythonhosted.org/packages/eb/bf/b7f95bb4529a35ca11eb95d48f9d1a563b495471f7cf404c644566fb4293/wrapt-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e25fde03c480061b8234d8ee4863eb5f40a9be4fb258ce105b364de38fc6bcf9", size = 120578, upload-time = "2025-10-19T23:46:28.679Z" }, + { url = "https://files.pythonhosted.org/packages/f8/71/984849df6f052592474a44aafd6b847e1cffad39b0debc5390a04aa46331/wrapt-2.0.0-cp313-cp313-win32.whl", hash = "sha256:49e982b7860d325094978292a49e0418833fc7fc42c0dc7cd0b7524d7d06ee74", size = 58178, upload-time = "2025-10-19T23:46:32.372Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3b/4e1fc0f2e1355fbc55ab248311bf4c958dbbd96bd9183b9e96882cc16213/wrapt-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e5c86389d9964050ce50babe247d172a5e3911d59a64023b90db2b4fa00ae7c", size = 60423, upload-time = "2025-10-19T23:46:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/20/0a/9384e0551f56fe361f41bb8f209a13bb9ef689c3a18264225b249849b12c/wrapt-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:b96fdaa4611e05c7231937930567d3c16782be9dbcf03eb9f60d83e57dd2f129", size = 58918, upload-time = "2025-10-19T23:46:31.056Z" }, + { url = "https://files.pythonhosted.org/packages/68/70/37b90d3ee5bf0d0dc4859306383da08b685c9a51abff6fd6b0a7c052e117/wrapt-2.0.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f2c7b7fead096dbf1dcc455b7f59facb05de3f5bfb04f60a69f98cdfe6049e5f", size = 81980, upload-time = "2025-10-19T23:46:33.368Z" }, + { url = "https://files.pythonhosted.org/packages/95/23/0ce69cc90806b90b3ee4cfd9ad8d2ee9becc3a1aab7df3c3bfc7d0904cb6/wrapt-2.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:04c7c8393f25b11c0faa5d907dd9eb462e87e4e7ba55e308a046d7ed37f4bbe2", size = 62900, upload-time = "2025-10-19T23:46:34.415Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/03ec08170c02f38f3be3646977920976b968e0b704a0693a98f95d02f4d2/wrapt-2.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a93e0f8b376c0735b2f4daf58018b4823614d2b896cb72b6641c4d3dbdca1d75", size = 63636, upload-time = "2025-10-19T23:46:35.643Z" }, + { url = "https://files.pythonhosted.org/packages/75/c1/04ce0511e504cdcd84cdb6980bc7d4efa38ac358e8103d6dd0cd278bfc6d/wrapt-2.0.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b42d13603da4416c43c430dbc6313c8d7ff745c40942f146ed4f6dd02c7d2547", size = 152650, upload-time = "2025-10-19T23:46:38.717Z" }, + { url = "https://files.pythonhosted.org/packages/17/06/cd2e32b5f744701189c954f9ab5eee449c86695b13f414bb8ea7a83f6d48/wrapt-2.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8bbd2472abf8c33480ad2314b1f8fac45d592aba6cc093e8839a7b2045660e6", size = 158811, upload-time = "2025-10-19T23:46:40.875Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a2/a6d920695cca62563c1b969064e5cd2051344a6e330c184b6f80383d87e4/wrapt-2.0.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e64a3a1fd9a308ab9b815a2ad7a65b679730629dbf85f8fc3f7f970d634ee5df", size = 146033, upload-time = "2025-10-19T23:46:37.351Z" }, + { url = "https://files.pythonhosted.org/packages/c6/90/7fd2abe4ec646bc43cb6b0d05086be6fcf15e64f06f51fc4198804396d68/wrapt-2.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d61214525eaf88e0d0edf3d1ad5b5889863c6f88e588c6cdc6aa4ee5d1f10a4a", size = 155673, upload-time = "2025-10-19T23:46:42.582Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8d/6cce7f8c41633e677ac8aa34e84b53a22a645ec2a680deb991785ca2798d/wrapt-2.0.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:04f7a5f92c5f7324a1735043cc467b1295a1c5b4e0c1395472b7c44706e3dc61", size = 144364, upload-time = "2025-10-19T23:46:44.381Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/9570349e03afa9d83daf7f33ffb17e8cdc62d7e84c0d09005d0f51912efa/wrapt-2.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2356f76cb99b3de5b4e5b8210367fbbb81c7309fe39b622f5d199dd88eb7f765", size = 150275, upload-time = "2025-10-19T23:46:45.662Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d8/448728e6fe030e5c4f1022c82cd3af1de1c672fa53d2d5b36b32a55ce7bf/wrapt-2.0.0-cp313-cp313t-win32.whl", hash = "sha256:0a921b657a224e40e4bc161b5d33934583b34f0c9c5bdda4e6ac66f9d2fcb849", size = 59867, upload-time = "2025-10-19T23:46:49.593Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b1/ad812b1fe1cd85f6498dc3a3c9809a1e880d6108283b1735119bec217041/wrapt-2.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c16f6d4eea98080f6659a8a7fc559d4a0a337ee66960659265cad2c8a40f7c0f", size = 63170, upload-time = "2025-10-19T23:46:46.87Z" }, + { url = "https://files.pythonhosted.org/packages/7f/29/c105b1e76650c82823c491952a7a8eafe09b78944f7a43f22d37ed860229/wrapt-2.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:52878edc13dc151c58a9966621d67163a80654bc6cff4b2e1c79fa62d0352b26", size = 60339, upload-time = "2025-10-19T23:46:47.862Z" }, + { url = "https://files.pythonhosted.org/packages/f8/38/0dd39f83163fd28326afba84e3e416656938df07e60a924ac4d992b30220/wrapt-2.0.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:79a53d86c2aff7b32cc77267e3a308365d1fcb881e74bc9cbe26f63ee90e37f0", size = 78242, upload-time = "2025-10-19T23:46:51.096Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/fa7a5c1d73f8690c712f9d2e4615700c6809942536dd3f441b9ba650a310/wrapt-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d731a4f22ed6ffa4cb551b4d2b0c24ff940c27a88edaf8e3490a5ee3a05aef71", size = 61207, upload-time = "2025-10-19T23:46:52.558Z" }, + { url = "https://files.pythonhosted.org/packages/23/d9/67cb93da492eb0a1cb17b7ed18220d059e58f00467ce6728b674d3441b3d/wrapt-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3e02ab8c0ac766a5a6e81cd3b6cc39200c69051826243182175555872522bd5a", size = 61748, upload-time = "2025-10-19T23:46:54.468Z" }, + { url = "https://files.pythonhosted.org/packages/e5/be/912bbd70cc614f491b526a1d7fe85695b283deed19287b9f32460178c54d/wrapt-2.0.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:895870602d65d7338edb3b6a717d856632ad9f14f7ff566214e4fb11f0816649", size = 120424, upload-time = "2025-10-19T23:46:57.575Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e1/10df8937e7da2aa9bc3662a4b623e51a323c68f42cad7b13f0e61a700ce2/wrapt-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b9ad4fab76a0086dc364c4f17f39ad289600e73ef5c6e9ab529aff22cac1ac3", size = 122804, upload-time = "2025-10-19T23:46:59.308Z" }, + { url = "https://files.pythonhosted.org/packages/f3/60/576751b1919adab9f63168e3b5fd46c0d1565871b1cc4c2569503ccf4be6/wrapt-2.0.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7ca0562606d7bad2736b2c18f61295d61f50cd3f4bfc51753df13614dbcce1b", size = 117398, upload-time = "2025-10-19T23:46:55.814Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/243411f360cc27bae5f8e21c16f1a8d87674c5534f4558e8a97c1e0d1c6f/wrapt-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe089d9f5a4a3dea0108a8ae34bced114d0c4cca417bada1c5e8f42d98af9050", size = 121230, upload-time = "2025-10-19T23:47:01.347Z" }, + { url = "https://files.pythonhosted.org/packages/d6/23/2f21f692c3b3f0857cb82708ce0c341fbac55a489d4025ae4e3fd5d5de8c/wrapt-2.0.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e761f2d2f8dbc80384af3d547b522a80e67db3e319c7b02e7fd97aded0a8a678", size = 116296, upload-time = "2025-10-19T23:47:02.659Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ed/678957fad212cfb1b65b2359d62f5619f5087d1d1cf296c6a996be45171c/wrapt-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:17ba1bdc52d0c783481850996aa26cea5237720769197335abea2ae6b4c23bc0", size = 119602, upload-time = "2025-10-19T23:47:03.775Z" }, + { url = "https://files.pythonhosted.org/packages/dc/e3/aeb4c3b052d3eed95e61babc20dcb1a512651e098cca4b84a6896585c06a/wrapt-2.0.0-cp314-cp314-win32.whl", hash = "sha256:f73318741b141223a4674ba96992aa2291b1b3f7a5e85cb3c2c964f86171eb45", size = 58649, upload-time = "2025-10-19T23:47:07.382Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2a/a71c51cb211798405b59172c7df5789a5b934b18317223cf22e0c6f852de/wrapt-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8e08d4edb13cafe7b3260f31d4de033f73d3205774540cf583bffaa4bec97db9", size = 60897, upload-time = "2025-10-19T23:47:04.862Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a5/acc5628035d06f69e9144cca543ca54c33b42a5a23b6f1e8fa131026db89/wrapt-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:af01695c2b7bbd8d67b869d8e3de2b123a7bfbee0185bdd138c2775f75373b83", size = 59306, upload-time = "2025-10-19T23:47:05.883Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e6/1318ca07d7fcee57e4592a78dacd9d5493b8ddd971c553a62904fb2c0cf2/wrapt-2.0.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:057f02c13cce7b26c79624c06a3e1c2353e6dc9708525232232f6768118042ca", size = 81987, upload-time = "2025-10-19T23:47:08.7Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bf/ffac358ddf61c3923d94a8b0e7620f2af1cd1b637a0fe4963a3919aa62b7/wrapt-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:79bdd84570267f3f43d609c892ae2d30b91ee4b8614c2cbfd311a2965f1c9bdb", size = 62902, upload-time = "2025-10-19T23:47:10.248Z" }, + { url = "https://files.pythonhosted.org/packages/b5/af/387c51f9e7b544fe95d852fc94f9f3866e3f7d7d39c2ee65041752f90bc2/wrapt-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:93c8b4f4d54fd401a817abbfc9bf482aa72fd447f8adf19ce81d035b3f5c762c", size = 63635, upload-time = "2025-10-19T23:47:11.746Z" }, + { url = "https://files.pythonhosted.org/packages/7c/99/d38d8c80b9cc352531d4d539a17e3674169a5cc25a7e6e5e3c27bc29893e/wrapt-2.0.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5e09ffd31001dce71c2c2a4fc201bdba9a2f9f62b23700cf24af42266e784741", size = 152659, upload-time = "2025-10-19T23:47:15.344Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2a/e154432f274e22ecf2465583386c5ceffa5e0bab3947c1c5b26cc8e7b275/wrapt-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d87c285ff04e26083c4b03546e7b74df7ba4f1f32f1dcb92e9ac13c2dbb4c379", size = 158818, upload-time = "2025-10-19T23:47:17.569Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7a/3a40c453300e2898e99c27495b8109ff7cd526997d12cfb8ebd1843199a4/wrapt-2.0.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e52e50ea0a72ea48d1291cf8b8aaedcc99072d9dc5baba6b820486dcf4c67da8", size = 146113, upload-time = "2025-10-19T23:47:13.026Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e2/3116a9eade8bea2bf5eedba3fa420e3c7d193d4b047440330d8eaf1098de/wrapt-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fd4c95536975895f32571073446e614d5e2810b666b64955586dcddfd438fd3", size = 155689, upload-time = "2025-10-19T23:47:19.397Z" }, + { url = "https://files.pythonhosted.org/packages/43/1c/277d3fbe9d177830ab9e54fe9253f38455b75a22d639a4bd9fa092d55ae5/wrapt-2.0.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d6ebfe9283209220ed9de80a3e9442aab8fc2be5a9bbf8491b99e02ca9349a89", size = 144403, upload-time = "2025-10-19T23:47:20.779Z" }, + { url = "https://files.pythonhosted.org/packages/d8/37/ab6ddaf182248aac5ed925725ef4c69a510594764665ecbd95bdd4481f16/wrapt-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5d3ebd784804f146b7ea55359beb138e23cc18e5a5cc2cf26ad438723c00ce3a", size = 150307, upload-time = "2025-10-19T23:47:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d7/df9e2d8040a3af618ff9496261cf90ca4f886fd226af0f4a69ac0c020c3b/wrapt-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:9b15940ae9debc8b40b15dc57e1ce4433f7fb9d3f8761c7fab1ddd94cb999d99", size = 60557, upload-time = "2025-10-19T23:47:26.73Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c2/502bd4557a3a9199ea73cc5932cf83354bd362682162f0b14164d2e90216/wrapt-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7a0efbbc06d3e2077476a04f55859819d23206600b4c33f791359a8e6fa3c362", size = 63988, upload-time = "2025-10-19T23:47:23.826Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/632b13942f45db7af709f346ff38b8992c8c21b004e61ab320b0dec525fe/wrapt-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7fec8a9455c029c8cf4ff143a53b6e7c463268d42be6c17efa847ebd2f809965", size = 60584, upload-time = "2025-10-19T23:47:25.396Z" }, + { url = "https://files.pythonhosted.org/packages/00/5c/c34575f96a0a038579683c7f10fca943c15c7946037d1d254ab9db1536ec/wrapt-2.0.0-py3-none-any.whl", hash = "sha256:02482fb0df89857e35427dfb844319417e14fae05878f295ee43fa3bf3b15502", size = 43998, upload-time = "2025-10-19T23:47:52.858Z" }, +] + [[package]] name = "xxhash" version = "3.6.0" @@ -1996,6 +2812,116 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, ] +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + [[package]] name = "zipp" version = "3.23.0" From 07f961bf7c051fcf5520e143bf37b81877d6ac6f Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 09:40:36 -0500 Subject: [PATCH 11/27] x --- libs/deepagents/integrations/runloop.py | 37 ++++++++----------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 4b000a5c..a9c08823 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -20,24 +20,20 @@ ) -class RunloopBackend: +class RunloopProtocol(BackendProtocol): """Backend that operates on files in a Runloop devbox. This implementation uses the Runloop API client to execute commands and manipulate files within a remote devbox environment. """ - # NOTE: As an example, this currently uses a pre-allocated devbox. - # For the real version we would want to create a devbox as needed, - # run one or more commands, and then clean up when finished. - def __init__( self, devbox_id: str, client: Optional[Runloop] = None, bearer_token: Optional[str] = None, ) -> None: - """Initialize Runloop backend. + """Initialize Runloop protocol. Args: devbox_id: ID of the Runloop devbox to operate on. @@ -67,11 +63,6 @@ def exec(self, command: str) -> tuple[str, int]: # return stderr here instead / in addition to stdout. return (result.stdout or "", result.exit_status) - -class RunloopProtocol(BackendProtocol): - def __init__(self, backend): - self._backend = backend - def ls_info(self, path: str) -> list[FileInfo]: """List files and directories in the specified directory (non-recursive). @@ -84,7 +75,7 @@ def ls_info(self, path: str) -> list[FileInfo]: """ # Use find to list only direct children cmd = f"find '{path}' -maxdepth 1 -mindepth 1 -printf '%p %s %T@ %y %Y\\n' 2>/dev/null" - stdout, exit_code = self._backend.exec(cmd) + stdout, exit_code = self.exec(cmd) if exit_code != 0 or not stdout.strip(): # NOTE: this silently ignores errors; not sure what error @@ -131,7 +122,7 @@ def read( # Check if file exists and get content start_line = offset + 1 cmd = f"if [ ! -f '{file_path}' ]; then echo 'Error: File not found'; exit 1; else tail -n +{start_line} '{file_path}' | head -n {limit}; fi" - stdout, exit_code = self._backend.exec(cmd) + stdout, exit_code = self.exec(cmd) if exit_code != 0 or "Error: File not found" in stdout: return f"Error: File '{file_path}' not found" @@ -162,15 +153,15 @@ def write( # Check if file already exists check_cmd = f"test -e '{file_path}' && echo 'exists' || echo 'ok'" - stdout, _ = self._backend.exec(check_cmd) + stdout, _ = self.exec(check_cmd) if "exists" in stdout: return WriteResult(error=f"Cannot write to {file_path} because it already exists. Read and then make an edit, or write to a new path.") # Use the upload_file() method from the Runloop API client. try: - self._backend._client.devboxes.upload_file( - id=self._backend._devbox_id, + self._client.devboxes.upload_file( + id=self._devbox_id, path=file_path, file=content.encode("utf-8"), # NOTE: might want a different type? ) @@ -205,14 +196,14 @@ def edit( try: # fetch the file - response = self._backend._client.devboxes.download_file(id=self._backend._devbox_id, path=file_path) + response = self._client.devboxes.download_file(id=self._devbox_id, path=file_path) # do the replacements new_text, occurrences = perform_string_replacement(response.text(), old_string, new_string, replace_all) # write back - self._backend._client.devboxes.upload_file( - id=self._backend._devbox_id, + self._client.devboxes.upload_file( + id=self._devbox_id, path=file_path, file=new_text.encode("utf-8"), # NOTE: might want a different type? ) @@ -254,7 +245,7 @@ def grep_raw( pattern_escaped = pattern.replace("'", "\\'") cmd = f"grep {grep_opts} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true" - stdout, _ = self._backend.exec(cmd) + stdout, _ = self.exec(cmd) if not stdout.strip(): return [] @@ -311,7 +302,7 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: f'" 2>/dev/null' ) - stdout, exit_code = self._backend.exec(python_cmd) + stdout, exit_code = self.exec(python_cmd) if exit_code != 0 or not stdout.strip(): return [] @@ -346,7 +337,3 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: results.sort(key=lambda x: x.get("path", "")) return results - - def exec(self, command: str) -> tuple[str, int]: - """Execute a command in the devbox and return (stdout, exit_status).""" - return self._backend.exec(command) From d16cd962f40d7ce574a29257a69f2a6c37b73a8c Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 10:00:28 -0500 Subject: [PATCH 12/27] x --- libs/deepagents/integrations/runloop.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index a9c08823..3cfd0e5c 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -20,6 +20,18 @@ ) +# Python command template for glob operations +_GLOB_COMMAND_TEMPLATE = '''python3 -c " +import glob, os, json +os.chdir('{path_escaped}') +matches = glob.glob('{pattern_escaped}', recursive=True) +for m in matches: + if os.path.isfile(m): + s = os.stat(m) + print(json.dumps({{'path': m, 'size': s.st_size, 'mtime': s.st_mtime}})) +" 2>/dev/null''' + + class RunloopProtocol(BackendProtocol): """Backend that operates on files in a Runloop devbox. @@ -290,16 +302,9 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: # Use a more complicated command, to grab stat output from the # matching files. Could be simplified if this isn't needed. - python_cmd = ( - f'python3 -c "' - f"import glob, os, json; " - f"os.chdir('{path_escaped}'); " - f"matches = glob.glob('{pattern_escaped}', recursive=True); " - f"for m in matches: " - f" if os.path.isfile(m): " - f" s = os.stat(m); " - f" print(json.dumps({{'path': m, 'size': s.st_size, 'mtime': s.st_mtime}})); " - f'" 2>/dev/null' + python_cmd = _GLOB_COMMAND_TEMPLATE.format( + path_escaped=path_escaped, + pattern_escaped=pattern_escaped ) stdout, exit_code = self.exec(python_cmd) From 76524274d52a399a99dd6f3b64676de0c5f7636f Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 10:02:44 -0500 Subject: [PATCH 13/27] x --- libs/deepagents/backends/pagination.py | 2 +- libs/deepagents/integrations/daytona.py | 25 ++++++++++++------------- libs/deepagents/integrations/runloop.py | 21 ++++++++------------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/libs/deepagents/backends/pagination.py b/libs/deepagents/backends/pagination.py index b7689a7b..b24a68af 100644 --- a/libs/deepagents/backends/pagination.py +++ b/libs/deepagents/backends/pagination.py @@ -23,4 +23,4 @@ class PageResults(TypedDict, Generic[T]): items: list[T] """List of sandbox IDs.""" cursor: PaginationCursor - """Pagination cursor.""" \ No newline at end of file + """Pagination cursor.""" diff --git a/libs/deepagents/integrations/daytona.py b/libs/deepagents/integrations/daytona.py index 2927b762..fcdc2d09 100644 --- a/libs/deepagents/integrations/daytona.py +++ b/libs/deepagents/integrations/daytona.py @@ -5,13 +5,12 @@ import os from typing import TYPE_CHECKING -from deepagents.backends.protocol import FileInfo, GrepMatch, WriteResult, EditResult -from deepagents.backends.process import ExecuteResponse, Process, ProcessCapabilities - from daytona import CreateSandboxFromSnapshotParams, Daytona, DaytonaConfig + from deepagents.backends.pagination import PageResults, PaginationCursor -from deepagents.backends.sandbox import Sandbox, SandboxCapabilities, SandboxMetadata, \ - SandboxProvider +from deepagents.backends.process import ExecuteResponse, Process, ProcessCapabilities +from deepagents.backends.protocol import EditResult, FileInfo, GrepMatch, WriteResult +from deepagents.backends.sandbox import Sandbox, SandboxCapabilities, SandboxMetadata, SandboxProvider if TYPE_CHECKING: from daytona import Sandbox as DaytonaSandboxClient @@ -92,9 +91,7 @@ def write( # Write the file using Python python_code = ( - f"import os; " - f"os.makedirs(os.path.dirname('{file_path}') or '.', exist_ok=True); " - f"open('{file_path}', 'w').write('''{content_escaped}''')" + f"import os; os.makedirs(os.path.dirname('{file_path}') or '.', exist_ok=True); open('{file_path}', 'w').write('''{content_escaped}''')" ) cmd = f'python3 -c "{python_code}" 2>&1' @@ -181,11 +178,13 @@ def grep_raw( # Format is: path:line_number:text parts = line.split(":", 2) if len(parts) >= 3: - matches.append({ - "path": parts[0], - "line": int(parts[1]), - "text": parts[2], - }) + matches.append( + { + "path": parts[0], + "line": int(parts[1]), + "text": parts[2], + } + ) return matches diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 3cfd0e5c..566cbff3 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -2,16 +2,15 @@ import datetime import os -from typing import Optional from runloop_api_client import Runloop from deepagents.backends.protocol import ( BackendProtocol, + EditResult, FileInfo, GrepMatch, WriteResult, - EditResult, ) from deepagents.backends.utils import ( check_empty_content, @@ -19,9 +18,8 @@ perform_string_replacement, ) - # Python command template for glob operations -_GLOB_COMMAND_TEMPLATE = '''python3 -c " +_GLOB_COMMAND_TEMPLATE = """python3 -c " import glob, os, json os.chdir('{path_escaped}') matches = glob.glob('{pattern_escaped}', recursive=True) @@ -29,7 +27,7 @@ if os.path.isfile(m): s = os.stat(m) print(json.dumps({{'path': m, 'size': s.st_size, 'mtime': s.st_mtime}})) -" 2>/dev/null''' +" 2>/dev/null""" class RunloopProtocol(BackendProtocol): @@ -42,8 +40,8 @@ class RunloopProtocol(BackendProtocol): def __init__( self, devbox_id: str, - client: Optional[Runloop] = None, - bearer_token: Optional[str] = None, + client: Runloop | None = None, + bearer_token: str | None = None, ) -> None: """Initialize Runloop protocol. @@ -229,8 +227,8 @@ def edit( def grep_raw( self, pattern: str, - path: Optional[str] = None, - glob: Optional[str] = None, + path: str | None = None, + glob: str | None = None, ) -> list[GrepMatch] | str: """Search for a pattern in files. @@ -302,10 +300,7 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: # Use a more complicated command, to grab stat output from the # matching files. Could be simplified if this isn't needed. - python_cmd = _GLOB_COMMAND_TEMPLATE.format( - path_escaped=path_escaped, - pattern_escaped=pattern_escaped - ) + python_cmd = _GLOB_COMMAND_TEMPLATE.format(path_escaped=path_escaped, pattern_escaped=pattern_escaped) stdout, exit_code = self.exec(python_cmd) From 2d4bfdca44a6a1731b848a74f9e0a4d01f9f1c58 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 11:10:08 -0500 Subject: [PATCH 14/27] x --- test_runloop_backend.ipynb | 675 +++++++++++++++++++++++++++++++++++++ 1 file changed, 675 insertions(+) create mode 100644 test_runloop_backend.ipynb diff --git a/test_runloop_backend.ipynb b/test_runloop_backend.ipynb new file mode 100644 index 00000000..88eb4823 --- /dev/null +++ b/test_runloop_backend.ipynb @@ -0,0 +1,675 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test Runloop Backend Implementation\n", + "\n", + "This notebook tests the RunloopProtocol backend implementation by creating a devbox and testing all methods." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup: Create Runloop Client and Devbox" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from deepagents.integrations.runloop import RunloopProtocol\n", + "from runloop_api_client import Runloop" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Devbox created with ID: dbx_31WKMQDRNkMz7eu3kidKr\n" + ] + } + ], + "source": [ + "# Create Runloop client (API Key is automatically loaded from \"RUNLOOP_API_KEY\" environment variable)\n", + "client = Runloop()\n", + "\n", + "# Create a simple devbox without code mounts\n", + "devbox = client.devboxes.create()\n", + "print(f\"Devbox created with ID: {devbox.id}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RunloopProtocol backend instantiated successfully\n" + ] + } + ], + "source": [ + "# Instantiate the RunloopProtocol backend\n", + "backend = RunloopProtocol(devbox_id=devbox.id, client=client)\n", + "print(\"RunloopProtocol backend instantiated successfully\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 1: exec() - Execute Commands" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exit code: 0\n", + "Output: Hello from Runloop!\n", + "\n" + ] + } + ], + "source": [ + "# Test basic command execution\n", + "stdout, exit_code = backend.exec(\"echo 'Hello from Runloop!'\")\n", + "print(f\"Exit code: {exit_code}\")\n", + "print(f\"Output: {stdout}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current directory: /home/user\n", + "\n", + "Directory listing:\n", + "total 24\n", + "drwx------ 3 user user 4096 Nov 5 14:59 .\n", + "drwxr-xr-x 3 root root 4096 Sep 25 23:13 ..\n", + "-rw-r--r-- 1 user user 220 Sep 25 23:13 .bash_logout\n", + "-rw-r--r-- 1 user user 3526 Sep 25 23:13 .bashrc\n", + "-rw-r--r-- 1 user user 807 Sep 25 23:13 .profile\n", + "drwx------ 2 user user 4096 Nov 5 14:59 .ssh\n", + "-rw-r--r-- 1 user user 0 Nov 5 14:59 .sudo_as_admin_successful\n", + "\n" + ] + } + ], + "source": [ + "# Test pwd and ls\n", + "stdout, exit_code = backend.exec(\"pwd\")\n", + "print(f\"Current directory: {stdout}\")\n", + "\n", + "stdout, exit_code = backend.exec(\"ls -la\")\n", + "print(f\"Directory listing:\\n{stdout}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 2: write() - Create Files" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Write result: WriteResult(error=None, path='/tmp/test_backend/hello.txt', files_update=None)\n" + ] + } + ], + "source": [ + "# Create a test directory\n", + "backend.exec(\"mkdir -p /tmp/test_backend\")\n", + "\n", + "# Write a simple text file\n", + "result = backend.write(file_path=\"/tmp/test_backend/hello.txt\", content=\"Hello World!\\nThis is a test file.\\nLine 3.\")\n", + "print(f\"Write result: {result}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Write result: WriteResult(error=None, path='/tmp/test_backend/test.py', files_update=None)\n" + ] + } + ], + "source": [ + "# Write a Python file\n", + "result = backend.write(\n", + " file_path=\"/tmp/test_backend/test.py\",\n", + " content=\"\"\"def greet(name):\n", + " return f'Hello, {name}!'\n", + "\n", + "if __name__ == '__main__':\n", + " print(greet('World'))\n", + "\"\"\",\n", + ")\n", + "print(f\"Write result: {result}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Expected error: WriteResult(error='Cannot write to /tmp/test_backend/hello.txt because it already exists. Read and then make an edit, or write to a new path.', path=None, files_update=None)\n" + ] + } + ], + "source": [ + "# Test writing to existing file (should fail)\n", + "result = backend.write(file_path=\"/tmp/test_backend/hello.txt\", content=\"This should fail\")\n", + "print(f\"Expected error: {result}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 3: read() - Read Files" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "File content:\n", + " 1\tHello World!\n", + " 2\tThis is a test file.\n", + " 3\tLine 3.\n" + ] + } + ], + "source": [ + "# Read the text file\n", + "content = backend.read(\"/tmp/test_backend/hello.txt\")\n", + "print(\"File content:\")\n", + "print(content)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "File content with offset=1, limit=1:\n", + " 2\tThis is a test file.\n" + ] + } + ], + "source": [ + "# Read with offset\n", + "content = backend.read(\"/tmp/test_backend/hello.txt\", offset=1, limit=1)\n", + "print(\"File content with offset=1, limit=1:\")\n", + "print(content)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Python file content:\n", + " 1\tdef greet(name):\n", + " 2\t return f'Hello, {name}!'\n", + " 3\t\n", + " 4\tif __name__ == '__main__':\n", + " 5\t print(greet('World'))\n" + ] + } + ], + "source": [ + "# Read the Python file\n", + "content = backend.read(\"/tmp/test_backend/test.py\")\n", + "print(\"Python file content:\")\n", + "print(content)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Expected error: Error: File '/tmp/test_backend/nonexistent.txt' not found\n" + ] + } + ], + "source": [ + "# Read non-existent file\n", + "content = backend.read(\"/tmp/test_backend/nonexistent.txt\")\n", + "print(f\"Expected error: {content}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 4: edit() - Edit Files" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Edit result: EditResult(error=None, path='/tmp/test_backend/hello.txt', files_update=None, occurrences=1)\n", + "\n", + "Updated content:\n", + " 1\tGreetings Universe!\n", + " 2\tThis is a test file.\n", + " 3\tLine 3.\n" + ] + } + ], + "source": [ + "# Edit the hello.txt file - single replacement\n", + "result = backend.edit(file_path=\"/tmp/test_backend/hello.txt\", old_string=\"Hello World!\", new_string=\"Greetings Universe!\", replace_all=False)\n", + "print(f\"Edit result: {result}\")\n", + "\n", + "# Read to verify\n", + "content = backend.read(\"/tmp/test_backend/hello.txt\")\n", + "print(\"\\nUpdated content:\")\n", + "print(content)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Edit result: EditResult(error=None, path='/tmp/test_backend/test.py', files_update=None, occurrences=1)\n", + "\n", + "Updated content:\n", + " 1\tdef greet(name):\n", + " 2\t return f'Hello, {name}!'\n", + " 3\t\n", + " 4\tif __name__ == '__main__':\n", + " 5\t print(greet('Runloop'))\n" + ] + } + ], + "source": [ + "# Edit with replace_all\n", + "result = backend.edit(file_path=\"/tmp/test_backend/test.py\", old_string=\"World\", new_string=\"Runloop\", replace_all=True)\n", + "print(f\"Edit result: {result}\")\n", + "\n", + "# Read to verify\n", + "content = backend.read(\"/tmp/test_backend/test.py\")\n", + "print(\"\\nUpdated content:\")\n", + "print(content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 5: ls_info() - List Directory Contents" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found 2 items:\n", + " {'path': '/tmp/test_backend/hello.txt', 'is_dir': False, 'size': 48, 'modified_at': '2025-11-05T10:00:08.928000'}\n", + " {'path': '/tmp/test_backend/test.py', 'is_dir': False, 'size': 102, 'modified_at': '2025-11-05T10:00:09.276000'}\n" + ] + } + ], + "source": [ + "# List files in test directory\n", + "files = backend.ls_info(\"/tmp/test_backend\")\n", + "print(f\"Found {len(files)} items:\")\n", + "for file_info in files:\n", + " print(f\" {file_info}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "After adding subdirectory - Found 3 items:\n", + " Path: /tmp/test_backend/hello.txt, Is Dir: False, Size: 48\n", + " Path: /tmp/test_backend/subdir/, Is Dir: True, Size: 0\n", + " Path: /tmp/test_backend/test.py, Is Dir: False, Size: 102\n" + ] + } + ], + "source": [ + "# Create subdirectories and more files\n", + "backend.exec(\"mkdir -p /tmp/test_backend/subdir\")\n", + "backend.write(\"/tmp/test_backend/subdir/nested.txt\", \"Nested file content\")\n", + "\n", + "# List again\n", + "files = backend.ls_info(\"/tmp/test_backend\")\n", + "print(f\"\\nAfter adding subdirectory - Found {len(files)} items:\")\n", + "for file_info in files:\n", + " print(f\" Path: {file_info['path']}, Is Dir: {file_info['is_dir']}, Size: {file_info['size']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 6: grep_raw() - Search Files" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "WriteResult(error=None, path='/tmp/test_backend/data.json', files_update=None)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create more test files for grepping\n", + "backend.write(\"/tmp/test_backend/file1.txt\", \"The quick brown fox\")\n", + "backend.write(\"/tmp/test_backend/file2.txt\", \"The lazy dog\\nA quick cat\")\n", + "backend.write(\"/tmp/test_backend/data.json\", '{\"key\": \"value\", \"number\": 42}')" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found 2 matches for 'quick':\n", + " /tmp/test_backend/file2.txt:2 - A quick cat\n", + " /tmp/test_backend/file1.txt:1 - The quick brown fox\n" + ] + } + ], + "source": [ + "# Search for pattern\n", + "matches = backend.grep_raw(pattern=\"quick\", path=\"/tmp/test_backend\")\n", + "print(f\"Found {len(matches)} matches for 'quick':\")\n", + "for match in matches:\n", + " print(f\" {match['path']}:{match['line']} - {match['text']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Found 2 matches for 'quick' in *.txt files:\n", + " /tmp/test_backend/file2.txt:2 - A quick cat\n", + " /tmp/test_backend/file1.txt:1 - The quick brown fox\n" + ] + } + ], + "source": [ + "# Search with glob filter\n", + "matches = backend.grep_raw(pattern=\"quick\", path=\"/tmp/test_backend\", glob=\"*.txt\")\n", + "print(f\"\\nFound {len(matches)} matches for 'quick' in *.txt files:\")\n", + "for match in matches:\n", + " print(f\" {match['path']}:{match['line']} - {match['text']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Found 0 matches for numbers:\n" + ] + } + ], + "source": [ + "# Search with regex pattern\n", + "matches = backend.grep_raw(pattern=\"[0-9]+\", path=\"/tmp/test_backend\")\n", + "print(f\"\\nFound {len(matches)} matches for numbers:\")\n", + "for match in matches:\n", + " print(f\" {match['path']}:{match['line']} - {match['text']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test 7: glob_info() - Find Files by Pattern" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found 3 .txt files:\n", + " /tmp/test_backend/file1.txt (19 bytes)\n", + " /tmp/test_backend/file2.txt (24 bytes)\n", + " /tmp/test_backend/hello.txt (48 bytes)\n" + ] + } + ], + "source": [ + "# Find all .txt files\n", + "files = backend.glob_info(pattern=\"*.txt\", path=\"/tmp/test_backend\")\n", + "print(f\"Found {len(files)} .txt files:\")\n", + "for file_info in files:\n", + " print(f\" {file_info['path']} ({file_info['size']} bytes)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Found 1 .py files:\n", + " /tmp/test_backend/test.py (102 bytes)\n" + ] + } + ], + "source": [ + "# Find all .py files\n", + "files = backend.glob_info(pattern=\"*.py\", path=\"/tmp/test_backend\")\n", + "print(f\"\\nFound {len(files)} .py files:\")\n", + "for file_info in files:\n", + " print(f\" {file_info['path']} ({file_info['size']} bytes)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Found 4 .txt files (recursive):\n", + " /tmp/test_backend/file1.txt\n", + " /tmp/test_backend/file2.txt\n", + " /tmp/test_backend/hello.txt\n", + " /tmp/test_backend/subdir/nested.txt\n" + ] + } + ], + "source": [ + "# Recursive search with **\n", + "files = backend.glob_info(pattern=\"**/*.txt\", path=\"/tmp/test_backend\")\n", + "print(f\"\\nFound {len(files)} .txt files (recursive):\")\n", + "for file_info in files:\n", + " print(f\" {file_info['path']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cleanup (Optional)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Clean up test files\n", + "stdout, exit_code = backend.exec(\"rm -rf /tmp/test_backend\")\n", + "print(f\"Cleanup completed with exit code: {exit_code}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Optionally shutdown the devbox\n", + "# client.devboxes.shutdown(id=devbox.id)\n", + "# print(f\"Devbox {devbox.id} shutdown\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "This notebook tested all methods of the RunloopProtocol backend:\n", + "\n", + "1. ✓ `exec()` - Execute shell commands\n", + "2. ✓ `write()` - Create new files\n", + "3. ✓ `read()` - Read file contents with line numbers\n", + "4. ✓ `edit()` - Edit files with string replacement\n", + "5. ✓ `ls_info()` - List directory contents\n", + "6. ✓ `grep_raw()` - Search for patterns in files\n", + "7. ✓ `glob_info()` - Find files matching glob patterns" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 1bdd02f42e3a9126f9ce36cd276791a6861792f3 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 11:39:55 -0500 Subject: [PATCH 15/27] x --- libs/deepagents/integrations/runloop.py | 32 ++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 566cbff3..91ee1820 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -6,6 +6,7 @@ from runloop_api_client import Runloop from deepagents.backends.protocol import ( + BackendFactory, BackendProtocol, EditResult, FileInfo, @@ -30,7 +31,7 @@ " 2>/dev/null""" -class RunloopProtocol(BackendProtocol): +class RunloopBackend(BackendProtocol): """Backend that operates on files in a Runloop devbox. This implementation uses the Runloop API client to execute commands @@ -337,3 +338,32 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: results.sort(key=lambda x: x.get("path", "")) return results + +class NotGiven: + """Sentinel type to indicate non specified parameters.""" + + +class RunloopProvider: + def __init__(self, *, client: Runloop | None, api_key: str | None = None, create_params: dict | NotGiven = NotGiven) -> None: + if client and api_key: + raise ValueError("Provide either client or api_key, not both.") + if not client and not api_key: + raise ValueError("Either client or api_key must be provided.") + + if not client: + api_key = api_key or os.environ.get("RUNLOOP_API_KEY", None) + if api_key is None: + raise ValueError("Either client or api_key must be provided.") + client = Runloop(bearer_token=api_key) + + self.client = client + self.create_params = {} + + def backend_factory(self, runtime: "ToolRuntime") -> RunloopBackend: + """Return a RunloopBackendFactory using the stored client.""" + kwargs = {} if self.create_params is NotGiven else self.create_params + devbox_resource = self.client.devboxes.create(**kwargs) + return RunloopBackend( + devbox_id=devbox_resource.id, + client=self.client, + ) From e714f28cedaedf14b0e8a64dd81054102931eb98 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 11:40:19 -0500 Subject: [PATCH 16/27] x --- libs/deepagents/integrations/runloop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 91ee1820..93137320 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -6,7 +6,6 @@ from runloop_api_client import Runloop from deepagents.backends.protocol import ( - BackendFactory, BackendProtocol, EditResult, FileInfo, @@ -339,6 +338,7 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: results.sort(key=lambda x: x.get("path", "")) return results + class NotGiven: """Sentinel type to indicate non specified parameters.""" @@ -357,7 +357,7 @@ def __init__(self, *, client: Runloop | None, api_key: str | None = None, create client = Runloop(bearer_token=api_key) self.client = client - self.create_params = {} + self.create_params = create_params def backend_factory(self, runtime: "ToolRuntime") -> RunloopBackend: """Return a RunloopBackendFactory using the stored client.""" From 2470bc26a5d7b723be4bb6671f3bd1bb4c5a5b4f Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 11:49:22 -0500 Subject: [PATCH 17/27] x --- libs/deepagents/integrations/runloop.py | 2 +- test_runloop_backend.ipynb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 93137320..20bc3e16 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -344,7 +344,7 @@ class NotGiven: class RunloopProvider: - def __init__(self, *, client: Runloop | None, api_key: str | None = None, create_params: dict | NotGiven = NotGiven) -> None: + def __init__(self, *, client: Runloop | None = None, api_key: str | None = None, create_params: dict | NotGiven = NotGiven) -> None: if client and api_key: raise ValueError("Provide either client or api_key, not both.") if not client and not api_key: diff --git a/test_runloop_backend.ipynb b/test_runloop_backend.ipynb index 88eb4823..8207a1f1 100644 --- a/test_runloop_backend.ipynb +++ b/test_runloop_backend.ipynb @@ -22,7 +22,7 @@ "metadata": {}, "outputs": [], "source": [ - "from deepagents.integrations.runloop import RunloopProtocol\n", + "from deepagents.integrations.runloop import RunloopBackend\n", "from runloop_api_client import Runloop" ] }, @@ -63,7 +63,7 @@ ], "source": [ "# Instantiate the RunloopProtocol backend\n", - "backend = RunloopProtocol(devbox_id=devbox.id, client=client)\n", + "backend = RunloopBackend(devbox_id=devbox.id, client=client)\n", "print(\"RunloopProtocol backend instantiated successfully\")" ] }, From 195464514c6645052c59970dc5cb6a6dcc2764cf Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 11:49:26 -0500 Subject: [PATCH 18/27] x --- agent_with_runloop_backend.ipynb | 270 +++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 agent_with_runloop_backend.ipynb diff --git a/agent_with_runloop_backend.ipynb b/agent_with_runloop_backend.ipynb new file mode 100644 index 00000000..2c476020 --- /dev/null +++ b/agent_with_runloop_backend.ipynb @@ -0,0 +1,270 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "metadata": {}, + "source": [ + "# Deep Agent with Runloop Backend\n", + "\n", + "This notebook demonstrates how to create a deep agent that operates on files in a Runloop devbox." + ] + }, + { + "cell_type": "markdown", + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "metadata": {}, + "source": [ + "## Setup: Import dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from runloop_api_client import Runloop\n", + "from deepagents.graph import create_deep_agent\n", + "from deepagents.integrations.runloop import RunloopProvider" + ] + }, + { + "cell_type": "markdown", + "id": "d4e5f6a7-b8c9-0123-def1-234567890123", + "metadata": {}, + "source": [ + "## Option 1: Using RunloopProvider (Recommended)\n", + "\n", + "The `RunloopProvider` will automatically create a devbox when the agent is initialized." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e5f6a7b8-c9d0-1234-ef12-345678901234", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Agent created with Runloop backend!\n" + ] + } + ], + "source": [ + "# Create the Runloop provider with optional devbox creation parameters\n", + "provider = RunloopProvider(\n", + " api_key=os.environ.get(\"RUNLOOP_API_KEY\"),\n", + " create_params={\n", + " # Optional: Add code mounts, environment variables, etc.\n", + " # \"code_mounts\": [\n", + " # {\n", + " # \"repo_name\": \"your-repo\",\n", + " # \"repo_owner\": \"your-username\",\n", + " # \"token\": os.environ.get(\"GITHUB_TOKEN\"),\n", + " # }\n", + " # ],\n", + " # \"environment_variables\": {\"MY_VAR\": \"value\"},\n", + " }\n", + ")\n", + "\n", + "# The None Here doesn't make much sense\n", + "backend = provider.backend_factory(None)\n", + "\n", + "# Create the deep agent with the Runloop backend\n", + "agent = create_deep_agent(\n", + " backend=backend,\n", + " system_prompt=\"You are a helpful coding assistant that operates on files in a remote devbox.\",\n", + ")\n", + "\n", + "print(\"Agent created with Runloop backend!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d799bce6-4a67-40ce-858e-7ab32ddcb688", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'messages': [HumanMessage(content='could you list the files you have access to (not using the shell)', additional_kwargs={}, response_metadata={}, id='229d5971-55b2-44d2-9533-84a661ea4da4'),\n", + " AIMessage(content=[{'text': \"I'll list the files in the root directory for you.\", 'type': 'text'}, {'id': 'toolu_01Ugvitaew9uS1ozKmrQari3', 'input': {'path': '/'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_013MoxLmzkUc6SVWxUeAD7aa', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 5776}, 'cache_creation_input_tokens': 5776, 'cache_read_input_tokens': 0, 'input_tokens': 3, 'output_tokens': 64, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--9b436cd0-b482-4211-ad71-6774481babed-0', tool_calls=[{'name': 'ls', 'args': {'path': '/'}, 'id': 'toolu_01Ugvitaew9uS1ozKmrQari3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5779, 'output_tokens': 64, 'total_tokens': 5843, 'input_token_details': {'cache_read': 0, 'cache_creation': 5776, 'ephemeral_5m_input_tokens': 5776, 'ephemeral_1h_input_tokens': 0}}),\n", + " ToolMessage(content='[\"/bin\", \"/boot/\", \"/dev/\", \"/etc/\", \"/home/\", \"/lib\", \"/lost+found/\", \"/media/\", \"/mnt/\", \"/opt/\", \"/proc/\", \"/root/\", \"/run/\", \"/runloop/\", \"/sbin\", \"/srv/\", \"/sys/\", \"/tmp/\", \"/usr/\", \"/var/\"]', name='ls', id='dec83681-a9e7-4412-8e27-da46619bbc19', tool_call_id='toolu_01Ugvitaew9uS1ozKmrQari3'),\n", + " AIMessage(content=[{'text': 'Let me check some common directories where user files might be located:', 'type': 'text'}, {'id': 'toolu_01JLshf23tBFBRGSJibeJcRP', 'input': {'path': '/home'}, 'name': 'ls', 'type': 'tool_use'}, {'id': 'toolu_01EPLqGgmjJTGKfJ8BkEYDoK', 'input': {'path': '/root'}, 'name': 'ls', 'type': 'tool_use'}, {'id': 'toolu_01X5zRT1fZizz4RWgif6ehdX', 'input': {'path': '/tmp'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01HbqxTvYvpSSVjSgtyXB2k9', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 155}, 'cache_creation_input_tokens': 155, 'cache_read_input_tokens': 5776, 'input_tokens': 6, 'output_tokens': 136, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--5d5afda0-2250-4591-a313-74accddf2647-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home'}, 'id': 'toolu_01JLshf23tBFBRGSJibeJcRP', 'type': 'tool_call'}, {'name': 'ls', 'args': {'path': '/root'}, 'id': 'toolu_01EPLqGgmjJTGKfJ8BkEYDoK', 'type': 'tool_call'}, {'name': 'ls', 'args': {'path': '/tmp'}, 'id': 'toolu_01X5zRT1fZizz4RWgif6ehdX', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5937, 'output_tokens': 136, 'total_tokens': 6073, 'input_token_details': {'cache_read': 5776, 'cache_creation': 155, 'ephemeral_5m_input_tokens': 155, 'ephemeral_1h_input_tokens': 0}}),\n", + " ToolMessage(content='[\"/home/user/\"]', name='ls', id='409b9122-14b9-4e4c-ab4b-6573ee69a954', tool_call_id='toolu_01JLshf23tBFBRGSJibeJcRP'),\n", + " ToolMessage(content=[], name='ls', id='1b8ec4fb-6907-4a45-b8b6-33a1454ecd32', tool_call_id='toolu_01EPLqGgmjJTGKfJ8BkEYDoK'),\n", + " ToolMessage(content='[\"/tmp/node-compile-cache/\", \"/tmp/v8-compile-cache-0/\"]', name='ls', id='3c83e0f2-e20b-48aa-b610-4c8d37cf74fb', tool_call_id='toolu_01X5zRT1fZizz4RWgif6ehdX'),\n", + " AIMessage(content=[{'id': 'toolu_01CtGHxb64aJC83rGVQ49dM5', 'input': {'path': '/home/user'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01Rzfmz3Az8FoAUsynorkCZq', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 252}, 'cache_creation_input_tokens': 252, 'cache_read_input_tokens': 5931, 'input_tokens': 7, 'output_tokens': 54, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--05826f64-cfe8-48e9-8d81-2b7b257e2844-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home/user'}, 'id': 'toolu_01CtGHxb64aJC83rGVQ49dM5', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6190, 'output_tokens': 54, 'total_tokens': 6244, 'input_token_details': {'cache_read': 5931, 'cache_creation': 252, 'ephemeral_5m_input_tokens': 252, 'ephemeral_1h_input_tokens': 0}}),\n", + " ToolMessage(content='[\"/home/user/.bash_logout\", \"/home/user/.bashrc\", \"/home/user/.profile\", \"/home/user/.ssh/\", \"/home/user/.sudo_as_admin_successful\"]', name='ls', id='63915218-d062-49c3-8fec-3e6db446792b', tool_call_id='toolu_01CtGHxb64aJC83rGVQ49dM5'),\n", + " AIMessage(content=\"Based on my exploration, here's what I found:\\n\\n**Root directory (/):** Contains standard Linux system directories like `/bin`, `/etc`, `/home`, `/usr`, `/var`, etc.\\n\\n**User home directory (/home/user/):** Contains:\\n- `.bash_logout`\\n- `.bashrc`\\n- `.profile`\\n- `.ssh/` (directory)\\n- `.sudo_as_admin_successful`\\n\\n**Other directories:**\\n- `/root/` - Empty or no accessible files\\n- `/tmp/` - Contains some Node.js compilation cache directories\\n\\nThe filesystem appears to be a relatively clean Linux environment with standard configuration files. There don't appear to be any project files or user documents currently present. Would you like me to explore any specific directory further or help you create some files?\", additional_kwargs={}, response_metadata={'id': 'msg_01NsE8SH6gjKfZN5FkoPkm7M', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 112}, 'cache_creation_input_tokens': 112, 'cache_read_input_tokens': 6183, 'input_tokens': 6, 'output_tokens': 176, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--a5de9966-5074-43fe-bf12-1bb82567649a-0', usage_metadata={'input_tokens': 6301, 'output_tokens': 176, 'total_tokens': 6477, 'input_token_details': {'cache_read': 6183, 'cache_creation': 112, 'ephemeral_5m_input_tokens': 112, 'ephemeral_1h_input_tokens': 0}})]}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent.invoke({\n", + " \"messages\": [\n", + " { \n", + " \"role\": \"user\",\n", + " \"content\": \"could you list the files you have access to (not using the shell)\"\n", + " }\n", + " ]}\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "c9d0e1f2-a3b4-5678-3456-789012345678", + "metadata": {}, + "source": [ + "## Option 3: With GitHub code mounts\n", + "\n", + "Create an agent that operates on a cloned GitHub repository." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d0e1f2a3-b4c5-6789-4567-890123456789", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Agent created with GitHub repository mounted!\n" + ] + } + ], + "source": [ + "# Create provider with GitHub code mount\n", + "provider_with_repo = RunloopProvider(\n", + " api_key=os.environ.get(\"RUNLOOP_API_KEY\"),\n", + " create_params={\n", + " \"code_mounts\": [\n", + " {\n", + " \"repo_name\": \"personal-site-generator\", # Replace with your repo\n", + " \"repo_owner\": \"eyurtsev\", # Replace with your username\n", + " \"token\": os.environ.get(\"GITHUB_TOKEN\"),\n", + " }\n", + " ],\n", + " }\n", + ")\n", + "\n", + "# The None Here doesn't make much sense\n", + "backend = provider.backend_factory(None)\n", + "\n", + "# Create agent\n", + "agent_with_repo = create_deep_agent(\n", + " backend=backend,\n", + " system_prompt=\"You are a helpful coding assistant. The repository is mounted in the devbox.\",\n", + ")\n", + "\n", + "print(\"Agent created with GitHub repository mounted!\")" + ] + }, + { + "cell_type": "markdown", + "id": "e1f2a3b4-c5d6-7890-5678-901234567890", + "metadata": {}, + "source": [ + "## Running the agent\n", + "\n", + "Now you can interact with your agent and it will operate on files in the Runloop devbox." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2a3b4c5-d6e7-8901-6789-012345678901", + "metadata": {}, + "outputs": [], + "source": [ + "# Example: Ask the agent to create a file\n", + "result = agent.invoke({\n", + " \"messages\": [{\n", + " \"role\": \"user\",\n", + " \"content\": \"Create a hello.py file that prints 'Hello from Runloop!'\"\n", + " }]\n", + "})\n", + "\n", + "# Print the agent's response\n", + "print(result[\"messages\"][-1].content)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3b4c5d6-e7f8-9012-7890-123456789012", + "metadata": {}, + "outputs": [], + "source": [ + "# Example: Ask the agent to list files\n", + "result = agent.invoke({\n", + " \"messages\": [{\n", + " \"role\": \"user\",\n", + " \"content\": \"List all Python files in the current directory\"\n", + " }]\n", + "})\n", + "\n", + "print(result[\"messages\"][-1].content)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b4c5d6e7-f8a9-0123-8901-234567890123", + "metadata": {}, + "outputs": [], + "source": [ + "# Example: Ask the agent to read and modify a file\n", + "result = agent.invoke({\n", + " \"messages\": [{\n", + " \"role\": \"user\",\n", + " \"content\": \"Read hello.py and add a function that says goodbye\"\n", + " }]\n", + "})\n", + "\n", + "print(result[\"messages\"][-1].content)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fc94653c-25f1-4b1d-9b7d-b4fe295b4f38", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 369f1a46ed1825f463872869f8ea038d16a484a5 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 11:53:13 -0500 Subject: [PATCH 19/27] x --- agent_with_runloop_backend.ipynb | 56 ++++++++++---------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/agent_with_runloop_backend.ipynb b/agent_with_runloop_backend.ipynb index 2c476020..71c7e064 100644 --- a/agent_with_runloop_backend.ipynb +++ b/agent_with_runloop_backend.ipynb @@ -126,14 +126,14 @@ "id": "c9d0e1f2-a3b4-5678-3456-789012345678", "metadata": {}, "source": [ - "## Option 3: With GitHub code mounts\n", + "## With GitHub code mounts\n", "\n", "Create an agent that operates on a cloned GitHub repository." ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 3, "id": "d0e1f2a3-b4c5-6789-4567-890123456789", "metadata": {}, "outputs": [ @@ -164,7 +164,7 @@ "backend = provider.backend_factory(None)\n", "\n", "# Create agent\n", - "agent_with_repo = create_deep_agent(\n", + "agent = create_deep_agent(\n", " backend=backend,\n", " system_prompt=\"You are a helpful coding assistant. The repository is mounted in the devbox.\",\n", ")\n", @@ -184,66 +184,46 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "f2a3b4c5-d6e7-8901-6789-012345678901", + "execution_count": 4, + "id": "ad5cc8dd-3710-4bc0-93b5-b06cb21fe18a", "metadata": {}, "outputs": [], "source": [ - "# Example: Ask the agent to create a file\n", - "result = agent.invoke({\n", - " \"messages\": [{\n", - " \"role\": \"user\",\n", - " \"content\": \"Create a hello.py file that prints 'Hello from Runloop!'\"\n", - " }]\n", - "})\n", - "\n", - "# Print the agent's response\n", - "print(result[\"messages\"][-1].content)" + "import uuid" ] }, { "cell_type": "code", - "execution_count": null, - "id": "a3b4c5d6-e7f8-9012-7890-123456789012", + "execution_count": 5, + "id": "f79f6476-5fcd-4198-abaa-053240f62b0a", "metadata": {}, "outputs": [], "source": [ - "# Example: Ask the agent to list files\n", - "result = agent.invoke({\n", - " \"messages\": [{\n", - " \"role\": \"user\",\n", - " \"content\": \"List all Python files in the current directory\"\n", - " }]\n", - "})\n", - "\n", - "print(result[\"messages\"][-1].content)" + "config = {\n", + " \"configurable\": {\n", + " \"thread_id\": str(uuid.uuid4())\n", + " }\n", + "}" ] }, { "cell_type": "code", "execution_count": null, - "id": "b4c5d6e7-f8a9-0123-8901-234567890123", + "id": "f2a3b4c5-d6e7-8901-6789-012345678901", "metadata": {}, "outputs": [], "source": [ - "# Example: Ask the agent to read and modify a file\n", + "# Example: Ask the agent to create a file\n", "result = agent.invoke({\n", " \"messages\": [{\n", " \"role\": \"user\",\n", - " \"content\": \"Read hello.py and add a function that says goodbye\"\n", + " \"content\": \"list the mounted git repo'\"\n", " }]\n", - "})\n", + "}, config=config)\n", "\n", + "# Print the agent's response\n", "print(result[\"messages\"][-1].content)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fc94653c-25f1-4b1d-9b7d-b4fe295b4f38", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From f4dc2d6b7b428b069d68951ed81330871366f83e Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 14:23:17 -0500 Subject: [PATCH 20/27] x --- agent_with_runloop_backend.ipynb | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/agent_with_runloop_backend.ipynb b/agent_with_runloop_backend.ipynb index 71c7e064..2ad6c84c 100644 --- a/agent_with_runloop_backend.ipynb +++ b/agent_with_runloop_backend.ipynb @@ -118,7 +118,7 @@ " \"content\": \"could you list the files you have access to (not using the shell)\"\n", " }\n", " ]}\n", - " )" + ")" ] }, { @@ -194,8 +194,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "f79f6476-5fcd-4198-abaa-053240f62b0a", + "execution_count": null, + "id": "af66ae08-bb44-4a1e-9f97-ca43a589185a", "metadata": {}, "outputs": [], "source": [ @@ -203,16 +203,8 @@ " \"configurable\": {\n", " \"thread_id\": str(uuid.uuid4())\n", " }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f2a3b4c5-d6e7-8901-6789-012345678901", - "metadata": {}, - "outputs": [], - "source": [ + "}\n", + "\n", "# Example: Ask the agent to create a file\n", "result = agent.invoke({\n", " \"messages\": [{\n", @@ -222,7 +214,7 @@ "}, config=config)\n", "\n", "# Print the agent's response\n", - "print(result[\"messages\"][-1].content)" + "print(result[\"messages\"][-1].content_blocks)" ] } ], From 6b74263906d62962a23804462c20d8424a1d18fb Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 14:43:23 -0500 Subject: [PATCH 21/27] x --- libs/deepagents/backends/process.py | 44 +++++++++++++++++++++++++ libs/deepagents/integrations/daytona.py | 4 +-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 libs/deepagents/backends/process.py diff --git a/libs/deepagents/backends/process.py b/libs/deepagents/backends/process.py new file mode 100644 index 00000000..5b847323 --- /dev/null +++ b/libs/deepagents/backends/process.py @@ -0,0 +1,44 @@ +"""Abstraction for modeling a process.""" + +import abc +from typing import NotRequired, TypedDict + + +class ProcessCapabilities(TypedDict): + """Capabilities of the process backend.""" + + supports_exec: bool + + +class ExecuteResponse(TypedDict): + """Result of code execution.""" + + result: str + """The output of the executed code. + + This will usually be the standard output of the command executed. + """ + exit_code: NotRequired[int] + """The exit code of the executed code, if applicable.""" + + +class Process(abc.ABC): + @abc.abstractmethod + def execute( + self, + command: str, + cwd: str | None = None, + *, + timeout: int = 30 * 60, + ) -> ExecuteResponse: + """Execute a command in the process. + + Args: + command: Command to execute as a string. + cwd: Working directory to execute the command in. + timeout: Maximum execution time in seconds (default: 30 minutes). + """ + ... + + @abc.abstractmethod + def get_capabilities(self) -> ProcessCapabilities: ... diff --git a/libs/deepagents/integrations/daytona.py b/libs/deepagents/integrations/daytona.py index fcdc2d09..9ea20b1c 100644 --- a/libs/deepagents/integrations/daytona.py +++ b/libs/deepagents/integrations/daytona.py @@ -9,14 +9,14 @@ from deepagents.backends.pagination import PageResults, PaginationCursor from deepagents.backends.process import ExecuteResponse, Process, ProcessCapabilities -from deepagents.backends.protocol import EditResult, FileInfo, GrepMatch, WriteResult +from deepagents.backends.protocol import EditResult, FileInfo, GrepMatch, WriteResult, BackendProtocol from deepagents.backends.sandbox import Sandbox, SandboxCapabilities, SandboxMetadata, SandboxProvider if TYPE_CHECKING: from daytona import Sandbox as DaytonaSandboxClient -class DaytonaFileSystem: +class DaytonaFileSystem(BackendProtocol): """Daytona filesystem implementation conforming to BackendProtocol.""" def __init__(self, sandbox: DaytonaSandboxClient) -> None: From 2985365697a5ae6e64d74d6faadfded9d13d224c Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 14:53:32 -0500 Subject: [PATCH 22/27] x --- libs/deepagents/integrations/runloop.py | 37 +++++++++++++------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 20bc3e16..8207b71c 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -5,6 +5,7 @@ from runloop_api_client import Runloop +from deepagents.backends.process import ExecuteResponse from deepagents.backends.protocol import ( BackendProtocol, EditResult, @@ -63,15 +64,15 @@ def __init__( self._client = client self._devbox_id = devbox_id - def exec(self, command: str) -> tuple[str, int]: - """Execute a command in the devbox and return (stdout, exit_status).""" + def exec(self, command: str) -> ExecuteResponse: + """Execute a command in the devbox and return ExecuteResponse.""" result = self._client.devboxes.execute_and_await_completion( devbox_id=self._devbox_id, command=command, ) # NOTE: could check exit status for error (non-zero) and # return stderr here instead / in addition to stdout. - return (result.stdout or "", result.exit_status) + return ExecuteResponse(result=result.stdout or "", exit_code=result.exit_status) def ls_info(self, path: str) -> list[FileInfo]: """List files and directories in the specified directory (non-recursive). @@ -85,16 +86,16 @@ def ls_info(self, path: str) -> list[FileInfo]: """ # Use find to list only direct children cmd = f"find '{path}' -maxdepth 1 -mindepth 1 -printf '%p %s %T@ %y %Y\\n' 2>/dev/null" - stdout, exit_code = self.exec(cmd) + response = self.exec(cmd) - if exit_code != 0 or not stdout.strip(): + if response.get("exit_code", 0) != 0 or not response["result"].strip(): # NOTE: this silently ignores errors; not sure what error # handling semantics are needed here, but presumably not # this. :) return [] results: list[FileInfo] = [] - for line in stdout.strip().split("\n"): + for line in response["result"].strip().split("\n"): if not line: continue @@ -132,16 +133,16 @@ def read( # Check if file exists and get content start_line = offset + 1 cmd = f"if [ ! -f '{file_path}' ]; then echo 'Error: File not found'; exit 1; else tail -n +{start_line} '{file_path}' | head -n {limit}; fi" - stdout, exit_code = self.exec(cmd) + response = self.exec(cmd) - if exit_code != 0 or "Error: File not found" in stdout: + if response.get("exit_code", 0) != 0 or "Error: File not found" in response["result"]: return f"Error: File '{file_path}' not found" - empty_msg = check_empty_content(stdout) + empty_msg = check_empty_content(response["result"]) if empty_msg: return empty_msg - return format_content_with_line_numbers(stdout, start_line=start_line) + return format_content_with_line_numbers(response["result"], start_line=start_line) def write( self, @@ -163,9 +164,9 @@ def write( # Check if file already exists check_cmd = f"test -e '{file_path}' && echo 'exists' || echo 'ok'" - stdout, _ = self.exec(check_cmd) + response = self.exec(check_cmd) - if "exists" in stdout: + if "exists" in response["result"]: return WriteResult(error=f"Cannot write to {file_path} because it already exists. Read and then make an edit, or write to a new path.") # Use the upload_file() method from the Runloop API client. @@ -255,14 +256,14 @@ def grep_raw( pattern_escaped = pattern.replace("'", "\\'") cmd = f"grep {grep_opts} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true" - stdout, _ = self.exec(cmd) + response = self.exec(cmd) - if not stdout.strip(): + if not response["result"].strip(): return [] # Parse grep output: path:line_number:content matches: list[GrepMatch] = [] - for line in stdout.strip().split("\n"): + for line in response["result"].strip().split("\n"): if not line: continue @@ -302,13 +303,13 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: # matching files. Could be simplified if this isn't needed. python_cmd = _GLOB_COMMAND_TEMPLATE.format(path_escaped=path_escaped, pattern_escaped=pattern_escaped) - stdout, exit_code = self.exec(python_cmd) + response = self.exec(python_cmd) - if exit_code != 0 or not stdout.strip(): + if response.get("exit_code", 0) != 0 or not response["result"].strip(): return [] results: list[FileInfo] = [] - for line in stdout.strip().split("\n"): + for line in response["result"].strip().split("\n"): if not line: continue From 495982ea083508c9158c4c89f4fd7bf0acc93da2 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 15:22:16 -0500 Subject: [PATCH 23/27] add process execution data type --- libs/deepagents/backends/process.py | 38 ++++++++++++++++++------- libs/deepagents/integrations/daytona.py | 19 ++++++++++--- libs/deepagents/integrations/runloop.py | 34 +++++++++++++--------- 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/libs/deepagents/backends/process.py b/libs/deepagents/backends/process.py index 5b847323..7090a400 100644 --- a/libs/deepagents/backends/process.py +++ b/libs/deepagents/backends/process.py @@ -1,7 +1,8 @@ """Abstraction for modeling a process.""" import abc -from typing import NotRequired, TypedDict +from dataclasses import dataclass +from typing import TypedDict class ProcessCapabilities(TypedDict): @@ -10,16 +11,24 @@ class ProcessCapabilities(TypedDict): supports_exec: bool -class ExecuteResponse(TypedDict): - """Result of code execution.""" +@dataclass +class ExecuteResponse: + """Result of code execution. - result: str - """The output of the executed code. - - This will usually be the standard output of the command executed. + Simplified schema optimized for LLM consumption. """ - exit_code: NotRequired[int] - """The exit code of the executed code, if applicable.""" + + output: str + """Combined stdout and stderr output of the executed command.""" + + exit_code: int | None = None + """The process exit code. 0 indicates success, non-zero indicates failure.""" + + signal: str | None = None + """The signal that terminated the process (e.g., 'SIGTERM', 'SIGKILL'), if applicable.""" + + truncated: bool = False + """Whether the output was truncated due to backend limitations.""" class Process(abc.ABC): @@ -28,15 +37,22 @@ def execute( self, command: str, cwd: str | None = None, + env: dict[str, str] | None = None, *, timeout: int = 30 * 60, ) -> ExecuteResponse: """Execute a command in the process. + Simplified interface optimized for LLM consumption. + Args: - command: Command to execute as a string. - cwd: Working directory to execute the command in. + command: Full shell command string to execute. + cwd: Working directory to execute the command in (absolute path). + env: Environment variables for the command (dict of name -> value). timeout: Maximum execution time in seconds (default: 30 minutes). + + Returns: + ExecuteResponse with combined output, exit code, optional signal, and truncation flag. """ ... diff --git a/libs/deepagents/integrations/daytona.py b/libs/deepagents/integrations/daytona.py index 9ea20b1c..6f0fb6f4 100644 --- a/libs/deepagents/integrations/daytona.py +++ b/libs/deepagents/integrations/daytona.py @@ -9,7 +9,7 @@ from deepagents.backends.pagination import PageResults, PaginationCursor from deepagents.backends.process import ExecuteResponse, Process, ProcessCapabilities -from deepagents.backends.protocol import EditResult, FileInfo, GrepMatch, WriteResult, BackendProtocol +from deepagents.backends.protocol import BackendProtocol, EditResult, FileInfo, GrepMatch, WriteResult from deepagents.backends.sandbox import Sandbox, SandboxCapabilities, SandboxMetadata, SandboxProvider if TYPE_CHECKING: @@ -228,20 +228,31 @@ def execute( self, command: str, cwd: str | None = None, + env: dict[str, str] | None = None, *, timeout: int = 30 * 60, ) -> ExecuteResponse: """Execute a command in the process. + Simplified interface optimized for LLM consumption. + Args: - command: Command to execute as a string. - cwd: Working directory to execute the command in. + command: Full shell command string to execute. + cwd: Working directory to execute the command in (absolute path). + env: Environment variables for the command. Note: Daytona may not support custom env vars. timeout: Maximum execution time in seconds (default: 30 minutes). + + Returns: + ExecuteResponse with combined output, exit code, optional signal, and truncation flag. """ + # Note: Daytona SDK doesn't support custom env variables response = self._sandbox.process.exec(command, cwd=cwd, timeout=timeout) + return ExecuteResponse( - result=response.result, + output=response.result, # Daytona combines stdout/stderr exit_code=response.exit_code, + signal=None, # Daytona SDK doesn't provide signal information + truncated=False, # Daytona doesn't have output limits ) def get_capabilities(self) -> ProcessCapabilities: diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 8207b71c..815fd3af 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -70,9 +70,17 @@ def exec(self, command: str) -> ExecuteResponse: devbox_id=self._devbox_id, command=command, ) - # NOTE: could check exit status for error (non-zero) and - # return stderr here instead / in addition to stdout. - return ExecuteResponse(result=result.stdout or "", exit_code=result.exit_status) + # Combine stdout and stderr + output = result.stdout or "" + if result.stderr: + output += "\n" + result.stderr if output else result.stderr + + return ExecuteResponse( + output=output, + exit_code=result.exit_status, + signal=None, # Runloop API doesn't provide signal information + truncated=False, # Runloop doesn't provide truncation info + ) def ls_info(self, path: str) -> list[FileInfo]: """List files and directories in the specified directory (non-recursive). @@ -88,14 +96,14 @@ def ls_info(self, path: str) -> list[FileInfo]: cmd = f"find '{path}' -maxdepth 1 -mindepth 1 -printf '%p %s %T@ %y %Y\\n' 2>/dev/null" response = self.exec(cmd) - if response.get("exit_code", 0) != 0 or not response["result"].strip(): + if (response.exit_code or 0) != 0 or not response.output.strip(): # NOTE: this silently ignores errors; not sure what error # handling semantics are needed here, but presumably not # this. :) return [] results: list[FileInfo] = [] - for line in response["result"].strip().split("\n"): + for line in response.output.strip().split("\n"): if not line: continue @@ -135,14 +143,14 @@ def read( cmd = f"if [ ! -f '{file_path}' ]; then echo 'Error: File not found'; exit 1; else tail -n +{start_line} '{file_path}' | head -n {limit}; fi" response = self.exec(cmd) - if response.get("exit_code", 0) != 0 or "Error: File not found" in response["result"]: + if (response.exit_code or 0) != 0 or "Error: File not found" in response.output: return f"Error: File '{file_path}' not found" - empty_msg = check_empty_content(response["result"]) + empty_msg = check_empty_content(response.output) if empty_msg: return empty_msg - return format_content_with_line_numbers(response["result"], start_line=start_line) + return format_content_with_line_numbers(response.output, start_line=start_line) def write( self, @@ -166,7 +174,7 @@ def write( check_cmd = f"test -e '{file_path}' && echo 'exists' || echo 'ok'" response = self.exec(check_cmd) - if "exists" in response["result"]: + if "exists" in response.output: return WriteResult(error=f"Cannot write to {file_path} because it already exists. Read and then make an edit, or write to a new path.") # Use the upload_file() method from the Runloop API client. @@ -258,12 +266,12 @@ def grep_raw( cmd = f"grep {grep_opts} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true" response = self.exec(cmd) - if not response["result"].strip(): + if not response.output.strip(): return [] # Parse grep output: path:line_number:content matches: list[GrepMatch] = [] - for line in response["result"].strip().split("\n"): + for line in response.output.strip().split("\n"): if not line: continue @@ -305,11 +313,11 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: response = self.exec(python_cmd) - if response.get("exit_code", 0) != 0 or not response["result"].strip(): + if (response.exit_code or 0) != 0 or not response.output.strip(): return [] results: list[FileInfo] = [] - for line in response["result"].strip().split("\n"): + for line in response.output.strip().split("\n"): if not line: continue From 6577f7a63f2b6881f8faf6856f5ba51d0a8466af Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 15:29:37 -0500 Subject: [PATCH 24/27] x --- libs/deepagents/integrations/runloop.py | 31 +++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 815fd3af..56b47ae7 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -64,8 +64,25 @@ def __init__( self._client = client self._devbox_id = devbox_id - def exec(self, command: str) -> ExecuteResponse: - """Execute a command in the devbox and return ExecuteResponse.""" + def execute( + self, + command: str, + cwd: str | None = None, + env: dict[str, str] | None = None, + *, + timeout: int = 30 * 60, + ) -> ExecuteResponse: + """Execute a command in the devbox and return ExecuteResponse. + + Args: + command: Full shell command string to execute. + cwd: Working directory to execute the command in (absolute path). + env: Environment variables for the command (dict of name -> value). + timeout: Maximum execution time in seconds (default: 30 minutes). + + Returns: + ExecuteResponse with combined output, exit code, optional signal, and truncation flag. + """ result = self._client.devboxes.execute_and_await_completion( devbox_id=self._devbox_id, command=command, @@ -94,7 +111,7 @@ def ls_info(self, path: str) -> list[FileInfo]: """ # Use find to list only direct children cmd = f"find '{path}' -maxdepth 1 -mindepth 1 -printf '%p %s %T@ %y %Y\\n' 2>/dev/null" - response = self.exec(cmd) + response = self.execute(cmd) if (response.exit_code or 0) != 0 or not response.output.strip(): # NOTE: this silently ignores errors; not sure what error @@ -141,7 +158,7 @@ def read( # Check if file exists and get content start_line = offset + 1 cmd = f"if [ ! -f '{file_path}' ]; then echo 'Error: File not found'; exit 1; else tail -n +{start_line} '{file_path}' | head -n {limit}; fi" - response = self.exec(cmd) + response = self.execute(cmd) if (response.exit_code or 0) != 0 or "Error: File not found" in response.output: return f"Error: File '{file_path}' not found" @@ -172,7 +189,7 @@ def write( # Check if file already exists check_cmd = f"test -e '{file_path}' && echo 'exists' || echo 'ok'" - response = self.exec(check_cmd) + response = self.execute(check_cmd) if "exists" in response.output: return WriteResult(error=f"Cannot write to {file_path} because it already exists. Read and then make an edit, or write to a new path.") @@ -264,7 +281,7 @@ def grep_raw( pattern_escaped = pattern.replace("'", "\\'") cmd = f"grep {grep_opts} -e '{pattern_escaped}' '{search_path}' 2>/dev/null || true" - response = self.exec(cmd) + response = self.execute(cmd) if not response.output.strip(): return [] @@ -311,7 +328,7 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: # matching files. Could be simplified if this isn't needed. python_cmd = _GLOB_COMMAND_TEMPLATE.format(path_escaped=path_escaped, pattern_escaped=pattern_escaped) - response = self.exec(python_cmd) + response = self.execute(python_cmd) if (response.exit_code or 0) != 0 or not response.output.strip(): return [] From abdc0be84cb75334fcb03f00ebe40d32919a83c8 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 5 Nov 2025 15:44:13 -0500 Subject: [PATCH 25/27] x --- libs/deepagents/integrations/runloop.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 56b47ae7..1a528995 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -86,6 +86,7 @@ def execute( result = self._client.devboxes.execute_and_await_completion( devbox_id=self._devbox_id, command=command, + timeout=timeout, ) # Combine stdout and stderr output = result.stdout or "" From fdeab91e166a8a12aac7e9bfa8e3cc79c3dbbf8e Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 12:10:16 -0500 Subject: [PATCH 26/27] x --- libs/deepagents/integrations/runloop.py | 36 ++----------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/libs/deepagents/integrations/runloop.py b/libs/deepagents/integrations/runloop.py index 1a528995..8c6486d5 100644 --- a/libs/deepagents/integrations/runloop.py +++ b/libs/deepagents/integrations/runloop.py @@ -7,7 +7,7 @@ from deepagents.backends.process import ExecuteResponse from deepagents.backends.protocol import ( - BackendProtocol, + SandboxBackendProtocol, EditResult, FileInfo, GrepMatch, @@ -31,7 +31,7 @@ " 2>/dev/null""" -class RunloopBackend(BackendProtocol): +class RunloopBackend(SandboxBackendProtocol): """Backend that operates on files in a Runloop devbox. This implementation uses the Runloop API client to execute commands @@ -67,8 +67,6 @@ def __init__( def execute( self, command: str, - cwd: str | None = None, - env: dict[str, str] | None = None, *, timeout: int = 30 * 60, ) -> ExecuteResponse: @@ -364,33 +362,3 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: results.sort(key=lambda x: x.get("path", "")) return results - - -class NotGiven: - """Sentinel type to indicate non specified parameters.""" - - -class RunloopProvider: - def __init__(self, *, client: Runloop | None = None, api_key: str | None = None, create_params: dict | NotGiven = NotGiven) -> None: - if client and api_key: - raise ValueError("Provide either client or api_key, not both.") - if not client and not api_key: - raise ValueError("Either client or api_key must be provided.") - - if not client: - api_key = api_key or os.environ.get("RUNLOOP_API_KEY", None) - if api_key is None: - raise ValueError("Either client or api_key must be provided.") - client = Runloop(bearer_token=api_key) - - self.client = client - self.create_params = create_params - - def backend_factory(self, runtime: "ToolRuntime") -> RunloopBackend: - """Return a RunloopBackendFactory using the stored client.""" - kwargs = {} if self.create_params is NotGiven else self.create_params - devbox_resource = self.client.devboxes.create(**kwargs) - return RunloopBackend( - devbox_id=devbox_resource.id, - client=self.client, - ) From 7defe36cfb785682a4af89c8eda21b3036fa2fa5 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Thu, 6 Nov 2025 13:04:11 -0500 Subject: [PATCH 27/27] x --- agent_with_runloop_backend.ipynb | 187 +++++++++++++++++-------------- 1 file changed, 104 insertions(+), 83 deletions(-) diff --git a/agent_with_runloop_backend.ipynb b/agent_with_runloop_backend.ipynb index 2ad6c84c..5d5aabf2 100644 --- a/agent_with_runloop_backend.ipynb +++ b/agent_with_runloop_backend.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "metadata": {}, "outputs": [], @@ -28,22 +28,49 @@ "import os\n", "from runloop_api_client import Runloop\n", "from deepagents.graph import create_deep_agent\n", - "from deepagents.integrations.runloop import RunloopProvider" + "from deepagents.integrations.runloop import RunloopBackend" ] }, { - "cell_type": "markdown", - "id": "d4e5f6a7-b8c9-0123-def1-234567890123", + "cell_type": "code", + "execution_count": 3, + "id": "22f0b297-52c9-45b0-a96d-95063f08ae4a", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dbx_31Wy37EKz7tur7WyBxD0L\n" + ] + } + ], "source": [ - "## Option 1: Using RunloopProvider (Recommended)\n", + "import os\n", + "\n", + "from runloop_api_client import Runloop\n", "\n", - "The `RunloopProvider` will automatically create a devbox when the agent is initialized." + "client = Runloop(\n", + " bearer_token=os.environ.get(\"RUNLOOP_API_KEY\"), # This is the default and can be omitted\n", + ")\n", + "\n", + "devbox_view = client.devboxes.create()\n", + "print(devbox_view.id)" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, + "id": "40135f20-a1ad-45f9-9475-79669d4e05c5", + "metadata": {}, + "outputs": [], + "source": [ + "backend = RunloopBackend(devbox_id=devbox_view.id, client=client)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "id": "e5f6a7b8-c9d0-1234-ef12-345678901234", "metadata": {}, "outputs": [ @@ -56,25 +83,6 @@ } ], "source": [ - "# Create the Runloop provider with optional devbox creation parameters\n", - "provider = RunloopProvider(\n", - " api_key=os.environ.get(\"RUNLOOP_API_KEY\"),\n", - " create_params={\n", - " # Optional: Add code mounts, environment variables, etc.\n", - " # \"code_mounts\": [\n", - " # {\n", - " # \"repo_name\": \"your-repo\",\n", - " # \"repo_owner\": \"your-username\",\n", - " # \"token\": os.environ.get(\"GITHUB_TOKEN\"),\n", - " # }\n", - " # ],\n", - " # \"environment_variables\": {\"MY_VAR\": \"value\"},\n", - " }\n", - ")\n", - "\n", - "# The None Here doesn't make much sense\n", - "backend = provider.backend_factory(None)\n", - "\n", "# Create the deep agent with the Runloop backend\n", "agent = create_deep_agent(\n", " backend=backend,\n", @@ -86,26 +94,24 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "d799bce6-4a67-40ce-858e-7ab32ddcb688", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'messages': [HumanMessage(content='could you list the files you have access to (not using the shell)', additional_kwargs={}, response_metadata={}, id='229d5971-55b2-44d2-9533-84a661ea4da4'),\n", - " AIMessage(content=[{'text': \"I'll list the files in the root directory for you.\", 'type': 'text'}, {'id': 'toolu_01Ugvitaew9uS1ozKmrQari3', 'input': {'path': '/'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_013MoxLmzkUc6SVWxUeAD7aa', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 5776}, 'cache_creation_input_tokens': 5776, 'cache_read_input_tokens': 0, 'input_tokens': 3, 'output_tokens': 64, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--9b436cd0-b482-4211-ad71-6774481babed-0', tool_calls=[{'name': 'ls', 'args': {'path': '/'}, 'id': 'toolu_01Ugvitaew9uS1ozKmrQari3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5779, 'output_tokens': 64, 'total_tokens': 5843, 'input_token_details': {'cache_read': 0, 'cache_creation': 5776, 'ephemeral_5m_input_tokens': 5776, 'ephemeral_1h_input_tokens': 0}}),\n", - " ToolMessage(content='[\"/bin\", \"/boot/\", \"/dev/\", \"/etc/\", \"/home/\", \"/lib\", \"/lost+found/\", \"/media/\", \"/mnt/\", \"/opt/\", \"/proc/\", \"/root/\", \"/run/\", \"/runloop/\", \"/sbin\", \"/srv/\", \"/sys/\", \"/tmp/\", \"/usr/\", \"/var/\"]', name='ls', id='dec83681-a9e7-4412-8e27-da46619bbc19', tool_call_id='toolu_01Ugvitaew9uS1ozKmrQari3'),\n", - " AIMessage(content=[{'text': 'Let me check some common directories where user files might be located:', 'type': 'text'}, {'id': 'toolu_01JLshf23tBFBRGSJibeJcRP', 'input': {'path': '/home'}, 'name': 'ls', 'type': 'tool_use'}, {'id': 'toolu_01EPLqGgmjJTGKfJ8BkEYDoK', 'input': {'path': '/root'}, 'name': 'ls', 'type': 'tool_use'}, {'id': 'toolu_01X5zRT1fZizz4RWgif6ehdX', 'input': {'path': '/tmp'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01HbqxTvYvpSSVjSgtyXB2k9', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 155}, 'cache_creation_input_tokens': 155, 'cache_read_input_tokens': 5776, 'input_tokens': 6, 'output_tokens': 136, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--5d5afda0-2250-4591-a313-74accddf2647-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home'}, 'id': 'toolu_01JLshf23tBFBRGSJibeJcRP', 'type': 'tool_call'}, {'name': 'ls', 'args': {'path': '/root'}, 'id': 'toolu_01EPLqGgmjJTGKfJ8BkEYDoK', 'type': 'tool_call'}, {'name': 'ls', 'args': {'path': '/tmp'}, 'id': 'toolu_01X5zRT1fZizz4RWgif6ehdX', 'type': 'tool_call'}], usage_metadata={'input_tokens': 5937, 'output_tokens': 136, 'total_tokens': 6073, 'input_token_details': {'cache_read': 5776, 'cache_creation': 155, 'ephemeral_5m_input_tokens': 155, 'ephemeral_1h_input_tokens': 0}}),\n", - " ToolMessage(content='[\"/home/user/\"]', name='ls', id='409b9122-14b9-4e4c-ab4b-6573ee69a954', tool_call_id='toolu_01JLshf23tBFBRGSJibeJcRP'),\n", - " ToolMessage(content=[], name='ls', id='1b8ec4fb-6907-4a45-b8b6-33a1454ecd32', tool_call_id='toolu_01EPLqGgmjJTGKfJ8BkEYDoK'),\n", - " ToolMessage(content='[\"/tmp/node-compile-cache/\", \"/tmp/v8-compile-cache-0/\"]', name='ls', id='3c83e0f2-e20b-48aa-b610-4c8d37cf74fb', tool_call_id='toolu_01X5zRT1fZizz4RWgif6ehdX'),\n", - " AIMessage(content=[{'id': 'toolu_01CtGHxb64aJC83rGVQ49dM5', 'input': {'path': '/home/user'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01Rzfmz3Az8FoAUsynorkCZq', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 252}, 'cache_creation_input_tokens': 252, 'cache_read_input_tokens': 5931, 'input_tokens': 7, 'output_tokens': 54, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--05826f64-cfe8-48e9-8d81-2b7b257e2844-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home/user'}, 'id': 'toolu_01CtGHxb64aJC83rGVQ49dM5', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6190, 'output_tokens': 54, 'total_tokens': 6244, 'input_token_details': {'cache_read': 5931, 'cache_creation': 252, 'ephemeral_5m_input_tokens': 252, 'ephemeral_1h_input_tokens': 0}}),\n", - " ToolMessage(content='[\"/home/user/.bash_logout\", \"/home/user/.bashrc\", \"/home/user/.profile\", \"/home/user/.ssh/\", \"/home/user/.sudo_as_admin_successful\"]', name='ls', id='63915218-d062-49c3-8fec-3e6db446792b', tool_call_id='toolu_01CtGHxb64aJC83rGVQ49dM5'),\n", - " AIMessage(content=\"Based on my exploration, here's what I found:\\n\\n**Root directory (/):** Contains standard Linux system directories like `/bin`, `/etc`, `/home`, `/usr`, `/var`, etc.\\n\\n**User home directory (/home/user/):** Contains:\\n- `.bash_logout`\\n- `.bashrc`\\n- `.profile`\\n- `.ssh/` (directory)\\n- `.sudo_as_admin_successful`\\n\\n**Other directories:**\\n- `/root/` - Empty or no accessible files\\n- `/tmp/` - Contains some Node.js compilation cache directories\\n\\nThe filesystem appears to be a relatively clean Linux environment with standard configuration files. There don't appear to be any project files or user documents currently present. Would you like me to explore any specific directory further or help you create some files?\", additional_kwargs={}, response_metadata={'id': 'msg_01NsE8SH6gjKfZN5FkoPkm7M', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 112}, 'cache_creation_input_tokens': 112, 'cache_read_input_tokens': 6183, 'input_tokens': 6, 'output_tokens': 176, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--a5de9966-5074-43fe-bf12-1bb82567649a-0', usage_metadata={'input_tokens': 6301, 'output_tokens': 176, 'total_tokens': 6477, 'input_token_details': {'cache_read': 6183, 'cache_creation': 112, 'ephemeral_5m_input_tokens': 112, 'ephemeral_1h_input_tokens': 0}})]}" + "{'messages': [HumanMessage(content='could you list the files you have access to (not using the shell)', additional_kwargs={}, response_metadata={}, id='af9024cc-2f43-4189-adb4-df4002ba16e8'),\n", + " AIMessage(content=[{'text': \"I'll list the files in the root directory for you using the `ls` tool.\", 'type': 'text'}, {'id': 'toolu_01F5JG2vEa3MfUkkVJDECfar', 'input': {'path': '/'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01AJgwWnMfcuFRsowUgKymfd', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 6021}, 'cache_creation_input_tokens': 6021, 'cache_read_input_tokens': 0, 'input_tokens': 3, 'output_tokens': 70, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--40e0b169-0f0e-4454-8d27-54ad0af8202b-0', tool_calls=[{'name': 'ls', 'args': {'path': '/'}, 'id': 'toolu_01F5JG2vEa3MfUkkVJDECfar', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6024, 'output_tokens': 70, 'total_tokens': 6094, 'input_token_details': {'cache_read': 0, 'cache_creation': 6021, 'ephemeral_5m_input_tokens': 6021, 'ephemeral_1h_input_tokens': 0}}),\n", + " ToolMessage(content='[\"/bin\", \"/boot/\", \"/dev/\", \"/etc/\", \"/home/\", \"/lib\", \"/lost+found/\", \"/media/\", \"/mnt/\", \"/opt/\", \"/proc/\", \"/root/\", \"/run/\", \"/runloop/\", \"/sbin\", \"/srv/\", \"/sys/\", \"/tmp/\", \"/usr/\", \"/var/\"]', name='ls', id='0e7977b3-29be-4c7f-9732-99df7717a52a', tool_call_id='toolu_01F5JG2vEa3MfUkkVJDECfar'),\n", + " AIMessage(content=[{'text': \"These are the top-level directories. Would you like me to explore a specific directory? Common places where user files might be located include:\\n- `/home/` - typically contains user home directories\\n- `/root/` - root user's home directory\\n- `/tmp/` - temporary files\\n\\nLet me check the home directory to see if there are any user files:\", 'type': 'text'}, {'id': 'toolu_01VbsrkDwfDCqwDAx8wxcvwv', 'input': {'path': '/home'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_014z33y8Tjf3JUTDygCPZ1ow', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 161}, 'cache_creation_input_tokens': 161, 'cache_read_input_tokens': 6021, 'input_tokens': 6, 'output_tokens': 130, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--37884afe-18dd-49c4-8b42-4c1b3352f354-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home'}, 'id': 'toolu_01VbsrkDwfDCqwDAx8wxcvwv', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6188, 'output_tokens': 130, 'total_tokens': 6318, 'input_token_details': {'cache_read': 6021, 'cache_creation': 161, 'ephemeral_5m_input_tokens': 161, 'ephemeral_1h_input_tokens': 0}}),\n", + " ToolMessage(content='[\"/home/user/\"]', name='ls', id='9c9ce343-4d71-43c1-81e1-4e6ffb713bb9', tool_call_id='toolu_01VbsrkDwfDCqwDAx8wxcvwv'),\n", + " AIMessage(content=[{'id': 'toolu_01YYB5BrUsHCERQENFEchwDH', 'input': {'path': '/home/user'}, 'name': 'ls', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01MEYsD2CkCVZnfMAchsoWyG', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 148}, 'cache_creation_input_tokens': 148, 'cache_read_input_tokens': 6182, 'input_tokens': 6, 'output_tokens': 54, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--31594999-5a24-4bc8-92bd-7710ff24833e-0', tool_calls=[{'name': 'ls', 'args': {'path': '/home/user'}, 'id': 'toolu_01YYB5BrUsHCERQENFEchwDH', 'type': 'tool_call'}], usage_metadata={'input_tokens': 6336, 'output_tokens': 54, 'total_tokens': 6390, 'input_token_details': {'cache_read': 6182, 'cache_creation': 148, 'ephemeral_5m_input_tokens': 148, 'ephemeral_1h_input_tokens': 0}}),\n", + " ToolMessage(content='[\"/home/user/.bash_logout\", \"/home/user/.bashrc\", \"/home/user/.profile\", \"/home/user/.ssh/\", \"/home/user/.sudo_as_admin_successful\"]', name='ls', id='989c232e-847d-4bf5-b83a-d5f6170453dd', tool_call_id='toolu_01YYB5BrUsHCERQENFEchwDH'),\n", + " AIMessage(content='The `/home/user` directory contains mostly configuration files. Would you like me to explore any other directories or search for specific types of files?', additional_kwargs={}, response_metadata={'id': 'msg_01GBSvugbTUcF13j4s7veT3n', 'model': 'claude-sonnet-4-5-20250929', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation': {'ephemeral_1h_input_tokens': 0, 'ephemeral_5m_input_tokens': 111}, 'cache_creation_input_tokens': 111, 'cache_read_input_tokens': 6330, 'input_tokens': 6, 'output_tokens': 32, 'server_tool_use': None, 'service_tier': 'standard'}, 'model_name': 'claude-sonnet-4-5-20250929', 'model_provider': 'anthropic'}, id='lc_run--f8ac1432-1219-4afc-869b-63b3b9df7093-0', usage_metadata={'input_tokens': 6447, 'output_tokens': 32, 'total_tokens': 6479, 'input_token_details': {'cache_read': 6330, 'cache_creation': 111, 'ephemeral_5m_input_tokens': 111, 'ephemeral_1h_input_tokens': 0}})]}" ] }, - "execution_count": 4, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -121,6 +127,66 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": 8, + "id": "77c3cee4-fb80-4488-adfc-2044c33afbaa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'type': 'text', 'text': \"Perfect! I've created the file `foo.md` in `/home/user/` with a short 3 sentence poem about nature. The poem describes the morning sun, a gentle breeze, and nature's peaceful symphony.\"}]\n" + ] + } + ], + "source": [ + "import uuid\n", + "\n", + "config = {\n", + " \"configurable\": {\n", + " \"thread_id\": str(uuid.uuid4())\n", + " }\n", + "}\n", + "\n", + "# Example: Ask the agent to create a file\n", + "result = agent.invoke({\n", + " \"messages\": [{\n", + " \"role\": \"user\",\n", + " \"content\": \"create a file called foo.md with a short 3 sentence poem'\"\n", + " }]\n", + "}, config=config)\n", + "\n", + "# Print the agent's response\n", + "print(result[\"messages\"][-1].content_blocks)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c6d439fe-1aa8-45a4-a534-b62e8a2e6498", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total 4.0K\n", + "4.0K -rw-r--r-- 1 user user 152 Nov 6 17:58 foo.md\n", + "\n", + "The morning sun breaks through the trees.\n", + "A gentle breeze whispers secrets to the leaves.\n", + "Nature's symphony plays on, bringing peace to all who listen.\n", + "\n" + ] + } + ], + "source": [ + "print(backend.execute('ls -lsh').output)\n", + "print(backend.execute('cat foo.md').output)" + ] + }, { "cell_type": "markdown", "id": "c9d0e1f2-a3b4-5678-3456-789012345678", @@ -171,51 +237,6 @@ "\n", "print(\"Agent created with GitHub repository mounted!\")" ] - }, - { - "cell_type": "markdown", - "id": "e1f2a3b4-c5d6-7890-5678-901234567890", - "metadata": {}, - "source": [ - "## Running the agent\n", - "\n", - "Now you can interact with your agent and it will operate on files in the Runloop devbox." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "ad5cc8dd-3710-4bc0-93b5-b06cb21fe18a", - "metadata": {}, - "outputs": [], - "source": [ - "import uuid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "af66ae08-bb44-4a1e-9f97-ca43a589185a", - "metadata": {}, - "outputs": [], - "source": [ - "config = {\n", - " \"configurable\": {\n", - " \"thread_id\": str(uuid.uuid4())\n", - " }\n", - "}\n", - "\n", - "# Example: Ask the agent to create a file\n", - "result = agent.invoke({\n", - " \"messages\": [{\n", - " \"role\": \"user\",\n", - " \"content\": \"list the mounted git repo'\"\n", - " }]\n", - "}, config=config)\n", - "\n", - "# Print the agent's response\n", - "print(result[\"messages\"][-1].content_blocks)" - ] } ], "metadata": {