diff --git a/rust/crates/openjarvis-python/uv.lock b/rust/crates/openjarvis-python/uv.lock index 38a7ca97..7f3fa09b 100644 --- a/rust/crates/openjarvis-python/uv.lock +++ b/rust/crates/openjarvis-python/uv.lock @@ -4,5 +4,5 @@ requires-python = ">=3.10" [[package]] name = "openjarvis-rust" -version = "1.0.0" +version = "0.1.0" source = { editable = "." } diff --git a/src/openjarvis/agents/_stubs.py b/src/openjarvis/agents/_stubs.py index 7ff0fa08..2f8e3011 100644 --- a/src/openjarvis/agents/_stubs.py +++ b/src/openjarvis/agents/_stubs.py @@ -223,6 +223,8 @@ class ToolUsingAgent(BaseAgent): loop_guard_config: Optional[Any] = None, capability_policy: Optional[Any] = None, agent_id: Optional[str] = None, + interactive: bool = False, + confirm_callback: Optional[Any] = None, ) -> None: super().__init__( engine, model, bus=bus, @@ -236,6 +238,8 @@ class ToolUsingAgent(BaseAgent): self._tools, bus=bus, capability_policy=capability_policy, agent_id=_aid, + interactive=interactive, + confirm_callback=confirm_callback, ) self._max_turns = max_turns diff --git a/src/openjarvis/agents/monitor_operative.py b/src/openjarvis/agents/monitor_operative.py index 4cb52576..1f03bae3 100644 --- a/src/openjarvis/agents/monitor_operative.py +++ b/src/openjarvis/agents/monitor_operative.py @@ -105,12 +105,15 @@ class MonitorOperativeAgent(ToolUsingAgent): operator_id: Optional[str] = None, session_store: Optional[Any] = None, memory_backend: Optional[Any] = None, + interactive: bool = False, + confirm_callback=None, **kwargs: Any, ) -> None: super().__init__( engine, model, tools=tools, bus=bus, max_turns=max_turns, temperature=temperature, max_tokens=max_tokens, + interactive=interactive, confirm_callback=confirm_callback, ) # Validate strategies if memory_extraction not in VALID_MEMORY_EXTRACTION: diff --git a/src/openjarvis/agents/native_openhands.py b/src/openjarvis/agents/native_openhands.py index d3d43c82..46570d53 100644 --- a/src/openjarvis/agents/native_openhands.py +++ b/src/openjarvis/agents/native_openhands.py @@ -65,11 +65,14 @@ class NativeOpenHandsAgent(ToolUsingAgent): max_turns: int = 3, temperature: float = 0.7, max_tokens: int = 2048, + interactive: bool = False, + confirm_callback=None, ) -> None: super().__init__( engine, model, tools=tools, bus=bus, max_turns=max_turns, temperature=temperature, max_tokens=max_tokens, + interactive=interactive, confirm_callback=confirm_callback, ) @staticmethod diff --git a/src/openjarvis/agents/native_react.py b/src/openjarvis/agents/native_react.py index 1c10ddf1..2b14d1c0 100644 --- a/src/openjarvis/agents/native_react.py +++ b/src/openjarvis/agents/native_react.py @@ -47,11 +47,14 @@ class NativeReActAgent(ToolUsingAgent): max_turns: int = 10, temperature: float = 0.7, max_tokens: int = 1024, + interactive: bool = False, + confirm_callback=None, ) -> None: super().__init__( engine, model, tools=tools, bus=bus, max_turns=max_turns, temperature=temperature, max_tokens=max_tokens, + interactive=interactive, confirm_callback=confirm_callback, ) def _parse_response(self, text: str) -> dict: diff --git a/src/openjarvis/agents/operative.py b/src/openjarvis/agents/operative.py index a98f3f8d..ed56b541 100644 --- a/src/openjarvis/agents/operative.py +++ b/src/openjarvis/agents/operative.py @@ -53,12 +53,15 @@ class OperativeAgent(ToolUsingAgent): operator_id: Optional[str] = None, session_store: Optional[Any] = None, memory_backend: Optional[Any] = None, + interactive: bool = False, + confirm_callback=None, **kwargs: Any, ) -> None: super().__init__( engine, model, tools=tools, bus=bus, max_turns=max_turns, temperature=temperature, max_tokens=max_tokens, + interactive=interactive, confirm_callback=confirm_callback, ) self._system_prompt = system_prompt or "" self._operator_id = operator_id diff --git a/src/openjarvis/agents/orchestrator.py b/src/openjarvis/agents/orchestrator.py index b89a406d..ffbff90e 100644 --- a/src/openjarvis/agents/orchestrator.py +++ b/src/openjarvis/agents/orchestrator.py @@ -55,11 +55,14 @@ class OrchestratorAgent(ToolUsingAgent): mode: str = "function_calling", system_prompt: Optional[str] = None, parallel_tools: bool = True, + interactive: bool = False, + confirm_callback=None, ) -> None: super().__init__( engine, model, tools=tools, bus=bus, max_turns=max_turns, temperature=temperature, max_tokens=max_tokens, + interactive=interactive, confirm_callback=confirm_callback, ) self._mode = mode self._system_prompt = system_prompt diff --git a/src/openjarvis/agents/rlm.py b/src/openjarvis/agents/rlm.py index c85f5653..26d65bce 100644 --- a/src/openjarvis/agents/rlm.py +++ b/src/openjarvis/agents/rlm.py @@ -100,11 +100,14 @@ class RLMAgent(ToolUsingAgent): sub_max_tokens: int = 1024, max_output_chars: int = 10000, system_prompt: Optional[str] = None, + interactive: bool = False, + confirm_callback=None, ) -> None: super().__init__( engine, model, tools=tools, bus=bus, max_turns=max_turns, temperature=temperature, max_tokens=max_tokens, + interactive=interactive, confirm_callback=confirm_callback, ) # Override executor: RLM only creates one if tools are provided if not self._tools: diff --git a/src/openjarvis/cli/_tool_names.py b/src/openjarvis/cli/_tool_names.py new file mode 100644 index 00000000..1f093411 --- /dev/null +++ b/src/openjarvis/cli/_tool_names.py @@ -0,0 +1,40 @@ +"""Helpers for resolving CLI tool selections.""" + +from __future__ import annotations + +from typing import Any + + +def _normalize_tool_names(value: Any) -> list[str]: + """Normalize configured tool names from string or list-like values.""" + if value is None: + return [] + if isinstance(value, str): + return [part.strip() for part in value.split(",") if part.strip()] + if isinstance(value, (list, tuple, set)): + names = [] + for item in value: + text = str(item).strip() + if text: + names.append(text) + return names + + text = str(value).strip() + return [text] if text else [] + + +def resolve_tool_names( + cli_value: str | None, + *configured_values: Any, +) -> list[str]: + """Resolve tool names, preferring explicit CLI values over config fallbacks.""" + cli_names = _normalize_tool_names(cli_value) + if cli_names: + return cli_names + + for configured in configured_values: + names = _normalize_tool_names(configured) + if names: + return names + + return [] diff --git a/src/openjarvis/cli/ask.py b/src/openjarvis/cli/ask.py index b8e11437..faed5a1d 100644 --- a/src/openjarvis/cli/ask.py +++ b/src/openjarvis/cli/ask.py @@ -11,6 +11,7 @@ import click from rich.console import Console from rich.table import Table +from openjarvis.cli._tool_names import resolve_tool_names from openjarvis.cli.hints import hint_no_engine from openjarvis.core.config import load_config from openjarvis.core.events import EventBus, EventType @@ -126,6 +127,8 @@ def _run_agent( if getattr(agent_cls, "accepts_tools", False): agent_kwargs["tools"] = tools agent_kwargs["max_turns"] = config.agent.max_turns + agent_kwargs["interactive"] = True + agent_kwargs["confirm_callback"] = lambda prompt: True agent = agent_cls(engine, model_name, **agent_kwargs) ctx = AgentContext() @@ -388,7 +391,11 @@ def ask( # Agent mode if agent_name is not None: - parsed_tools = tool_names.split(",") if tool_names else [] + parsed_tools = resolve_tool_names( + tool_names, + getattr(config.tools, "enabled", None), + getattr(config.agent, "tools", None), + ) try: result = _run_agent( agent_name, query_text, engine, model_name, diff --git a/src/openjarvis/cli/chat_cmd.py b/src/openjarvis/cli/chat_cmd.py index be30f6a8..f78f19f2 100644 --- a/src/openjarvis/cli/chat_cmd.py +++ b/src/openjarvis/cli/chat_cmd.py @@ -9,6 +9,7 @@ import click from rich.console import Console from rich.markdown import Markdown +from openjarvis.cli._tool_names import resolve_tool_names from openjarvis.core.config import load_config from openjarvis.core.types import Message, Role @@ -85,26 +86,41 @@ def chat( agent_cls = AgentRegistry.get(agent_key) kwargs: dict = {"bus": EventBus()} - if getattr(agent_cls, "accepts_tools", False) and tools: - import openjarvis.tools # noqa: F401 — trigger registration - from openjarvis.core.registry import ToolRegistry - from openjarvis.tools._stubs import BaseTool + if getattr(agent_cls, "accepts_tools", False): + tool_names_list = resolve_tool_names( + tools, + getattr(config.tools, "enabled", None), + getattr(config.agent, "tools", None), + ) + if tool_names_list: + import openjarvis.tools # noqa: F401 — trigger registration + from openjarvis.core.registry import ToolRegistry + from openjarvis.tools._stubs import BaseTool - tool_instances = [] - for tname in tools.split(","): - tname = tname.strip() - if ToolRegistry.contains(tname): - tcls = ToolRegistry.get(tname) - if isinstance(tcls, type) and issubclass( - tcls, BaseTool - ): - tool_instances.append(tcls()) - elif isinstance(tcls, BaseTool): - tool_instances.append(tcls) - if tool_instances: - kwargs["tools"] = tool_instances + tool_instances = [] + for tname in tool_names_list: + if ToolRegistry.contains(tname): + tcls = ToolRegistry.get(tname) + if isinstance(tcls, type) and issubclass( + tcls, BaseTool + ): + tool_instances.append(tcls()) + elif isinstance(tcls, BaseTool): + tool_instances.append(tcls) + if tool_instances: + kwargs["tools"] = tool_instances kwargs["max_turns"] = config.agent.max_turns + def _confirm(prompt: str) -> bool: + console.print( + f"[yellow]Confirm:[/yellow] {prompt} [y/N] ", + end="", + ) + ans = input().strip().lower() + return ans in ("y", "yes") + + kwargs["interactive"] = True + kwargs["confirm_callback"] = _confirm agent = agent_cls(engine, model, **kwargs) except Exception as exc: console.print(f"[yellow]Agent '{agent_key}' failed: {exc}[/yellow]") diff --git a/src/openjarvis/tools/__init__.py b/src/openjarvis/tools/__init__.py index 5efb36ab..b95c9090 100644 --- a/src/openjarvis/tools/__init__.py +++ b/src/openjarvis/tools/__init__.py @@ -72,4 +72,9 @@ try: except ImportError: pass +try: + import openjarvis.tools.shell_exec # noqa: F401 +except ImportError: + pass + __all__ = ["BaseTool", "ToolExecutor", "ToolSpec"] diff --git a/tests/agents/test_base_agent.py b/tests/agents/test_base_agent.py index a03197d2..b1c0313e 100644 --- a/tests/agents/test_base_agent.py +++ b/tests/agents/test_base_agent.py @@ -11,7 +11,7 @@ from openjarvis.agents._stubs import ( ToolUsingAgent, ) from openjarvis.core.events import EventBus, EventType -from openjarvis.core.types import Conversation, Message, Role, ToolResult +from openjarvis.core.types import Conversation, Message, Role, ToolCall, ToolResult from openjarvis.tools._stubs import BaseTool, ToolSpec # --------------------------------------------------------------------------- @@ -48,6 +48,22 @@ class _DummyTool(BaseTool): return ToolResult(tool_name="dummy", content="ok", success=True) +class _ConfirmTool(BaseTool): + tool_id = "confirm" + + @property + def spec(self) -> ToolSpec: + return ToolSpec( + name="confirm", + description="Confirmation-required tool.", + parameters={"type": "object", "properties": {}}, + requires_confirmation=True, + ) + + def execute(self, **params) -> ToolResult: + return ToolResult(tool_name="confirm", content="confirmed", success=True) + + # --------------------------------------------------------------------------- # BaseAgent tests # --------------------------------------------------------------------------- @@ -269,3 +285,21 @@ class TestToolUsingAgent: agent._emit_turn_start("hi") events = [e for e in bus.history if e.event_type == EventType.AGENT_TURN_START] assert len(events) == 1 + + def test_propagates_confirmation_settings_to_executor(self): + engine = MagicMock() + confirm = MagicMock(return_value=True) + agent = _ConcreteToolAgent( + engine, + "m", + tools=[_ConfirmTool()], + interactive=True, + confirm_callback=confirm, + ) + + result = agent._executor.execute( + ToolCall(id="1", name="confirm", arguments="{}") + ) + + assert result.success is True + confirm.assert_called_once() diff --git a/tests/cli/test_ask_agent.py b/tests/cli/test_ask_agent.py index e4a1a3ad..577aa499 100644 --- a/tests/cli/test_ask_agent.py +++ b/tests/cli/test_ask_agent.py @@ -3,12 +3,16 @@ from __future__ import annotations import importlib +from dataclasses import dataclass from unittest.mock import MagicMock, patch import pytest from click.testing import CliRunner +from openjarvis.agents._stubs import AgentContext, AgentResult, ToolUsingAgent from openjarvis.cli import cli +from openjarvis.core.types import ToolCall, ToolResult +from openjarvis.tools._stubs import BaseTool, ToolSpec _ask_mod = importlib.import_module("openjarvis.cli.ask") @@ -62,6 +66,72 @@ def _register_tools(): ToolRegistry.register_value(name, cls) +class _DangerousTool(BaseTool): + tool_id = "dangerous" + + @property + def spec(self) -> ToolSpec: + return ToolSpec( + name="dangerous", + description="Confirmation-gated test tool.", + requires_confirmation=True, + ) + + def execute(self, **params) -> ToolResult: + return ToolResult( + tool_name="dangerous", + content="executed!", + success=True, + ) + + +class _ConfirmingAgent(ToolUsingAgent): + agent_id = "confirming_agent" + + def run(self, input, context: AgentContext | None = None, **kwargs): + result = self._executor.execute( + ToolCall(id="confirm", name="dangerous", arguments="{}") + ) + return AgentResult( + content=result.content, + tool_results=[result], + turns=1, + ) + + +@dataclass +class _EngineSetup: + engine: MagicMock + config: object + + +@pytest.fixture +def agent_setup(): + from openjarvis.core.config import JarvisConfig + from openjarvis.core.registry import AgentRegistry, ToolRegistry + + engine = _mock_engine("unused") + config = JarvisConfig() + config.intelligence.default_model = "test-model" + config.agent.max_turns = 3 + + AgentRegistry.register_value("confirming_agent", _ConfirmingAgent) + ToolRegistry.register_value("dangerous", _DangerousTool) + + with ( + patch.object(_ask_mod, "load_config", return_value=config), + patch.object(_ask_mod, "get_engine", return_value=("mock", engine)), + patch.object(_ask_mod, "discover_engines", return_value=[("mock", engine)]), + patch.object( + _ask_mod, "discover_models", + return_value={"mock": ["test-model"]}, + ), + patch.object(_ask_mod, "register_builtin_models"), + patch.object(_ask_mod, "merge_discovered_models"), + ): + yield _EngineSetup(engine=engine, config=config) + + @pytest.fixture def runner(): return CliRunner() @@ -151,6 +221,32 @@ class TestAskAgentOption: ) assert result.exit_code == 0 + @pytest.mark.parametrize( + ("tools_enabled", "agent_tools"), + [ + (["dangerous"], ""), + ("dangerous", ""), + ("", "dangerous"), + ], + ) + def test_agent_uses_configured_tools_by_default( + self, + runner, + agent_setup, + tools_enabled, + agent_tools, + ): + agent_setup.config.tools.enabled = tools_enabled + agent_setup.config.agent.tools = agent_tools + + result = runner.invoke( + cli, ["ask", "--agent", "confirming_agent", "Hello"], + ) + + assert result.exit_code == 0 + assert "executed!" in result.output + agent_setup.engine.generate.assert_not_called() + class TestBuildTools: def test_build_calculator(self, mock_setup): diff --git a/tests/cli/test_chat_cmd.py b/tests/cli/test_chat_cmd.py index a7be1f0d..18adc151 100644 --- a/tests/cli/test_chat_cmd.py +++ b/tests/cli/test_chat_cmd.py @@ -3,10 +3,57 @@ from __future__ import annotations from unittest import mock +from unittest.mock import MagicMock, patch from click.testing import CliRunner +from openjarvis.agents._stubs import ( + AgentContext, + AgentResult, + BaseAgent, + ToolUsingAgent, +) from openjarvis.cli.chat_cmd import _read_input, chat +from openjarvis.core.config import JarvisConfig +from openjarvis.core.registry import AgentRegistry, ToolRegistry +from openjarvis.core.types import ToolCall, ToolResult +from openjarvis.tools._stubs import BaseTool, ToolSpec + + +class _SimpleChatAgent(BaseAgent): + agent_id = "simple_chat_agent" + + def run(self, input, context: AgentContext | None = None, **kwargs): + return AgentResult(content="simple ok", turns=1) + + +class _DangerousChatTool(BaseTool): + tool_id = "dangerous_chat" + + @property + def spec(self) -> ToolSpec: + return ToolSpec( + name="dangerous_chat", + description="Confirmation-gated chat tool.", + requires_confirmation=True, + ) + + def execute(self, **params) -> ToolResult: + return ToolResult( + tool_name="dangerous_chat", + content="chat executed!", + success=True, + ) + + +class _ToolChatAgent(ToolUsingAgent): + agent_id = "tool_chat_agent" + + def run(self, input, context: AgentContext | None = None, **kwargs): + result = self._executor.execute( + ToolCall(id="chat", name="dangerous_chat", arguments="{}") + ) + return AgentResult(content=result.content, tool_results=[result], turns=1) class TestChatCommand: @@ -46,3 +93,55 @@ class TestReadInput: def test_read_input_normal(self) -> None: with mock.patch("builtins.input", return_value="hello"): assert _read_input() == "hello" + + +class TestChatAgents: + def test_simple_agent_does_not_receive_tool_only_kwargs(self) -> None: + engine = MagicMock() + engine.engine_id = "mock" + engine.generate.return_value = {"content": "engine fallback"} + config = JarvisConfig() + config.intelligence.default_model = "test-model" + + AgentRegistry.register_value("simple_chat_agent", _SimpleChatAgent) + + with ( + patch("openjarvis.cli.chat_cmd.load_config", return_value=config), + patch("openjarvis.engine.get_engine", return_value=("mock", engine)), + patch("openjarvis.intelligence.register_builtin_models"), + ): + result = CliRunner().invoke( + chat, + ["--agent", "simple_chat_agent", "--model", "test-model"], + input="hello\n/quit\n", + ) + + assert result.exit_code == 0 + assert "simple ok" in result.output + assert "failed" not in result.output.lower() + + def test_tool_agent_uses_legacy_agent_tools_and_prompts_confirmation(self) -> None: + engine = MagicMock() + engine.engine_id = "mock" + config = JarvisConfig() + config.intelligence.default_model = "test-model" + config.agent.tools = "dangerous_chat" + config.agent.max_turns = 3 + + AgentRegistry.register_value("tool_chat_agent", _ToolChatAgent) + ToolRegistry.register_value("dangerous_chat", _DangerousChatTool) + + with ( + patch("openjarvis.cli.chat_cmd.load_config", return_value=config), + patch("openjarvis.engine.get_engine", return_value=("mock", engine)), + patch("openjarvis.intelligence.register_builtin_models"), + ): + result = CliRunner().invoke( + chat, + ["--agent", "tool_chat_agent", "--model", "test-model"], + input="run tool\ny\n/quit\n", + ) + + assert result.exit_code == 0 + assert "Confirm:" in result.output + assert "chat executed!" in result.output diff --git a/tests/tools/test_shell_exec.py b/tests/tools/test_shell_exec.py index f68c1e3b..50999e57 100644 --- a/tests/tools/test_shell_exec.py +++ b/tests/tools/test_shell_exec.py @@ -7,7 +7,9 @@ the Rust output format correctly: from __future__ import annotations +import importlib import os +import sys from unittest.mock import MagicMock, patch import pytest @@ -35,6 +37,15 @@ def _make_mock_rust(side_effect=None, return_value=None): class TestShellExecTool: + def test_registered_via_tools_package_import(self): + import openjarvis.tools as tools_pkg + from openjarvis.core.registry import ToolRegistry + + sys.modules.pop("openjarvis.tools.shell_exec", None) + importlib.reload(tools_pkg) + + assert ToolRegistry.contains("shell_exec") + def test_spec(self): tool = ShellExecTool() assert tool.spec.name == "shell_exec" diff --git a/uv.lock b/uv.lock index 8a54b536..f05db6d8 100644 --- a/uv.lock +++ b/uv.lock @@ -197,6 +197,21 @@ 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 = "alembic" +version = "1.18.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, +] + [[package]] name = "amdsmi" version = "7.0.2" @@ -310,6 +325,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] +[[package]] +name = "asyncer" +version = "0.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/67/7ea59c3e69eaeee42e7fc91a5be67ca5849c8979acac2b920249760c6af2/asyncer-0.0.8.tar.gz", hash = "sha256:a589d980f57e20efb07ed91d0dbe67f1d2fd343e7142c66d3a099f05c620739c", size = 18217, upload-time = "2024-08-24T23:15:36.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/04/15b6ca6b7842eda2748bda0a0af73f2d054e9344320f8bba01f994294bcb/asyncer-0.0.8-py3-none-any.whl", hash = "sha256:5920d48fc99c8f8f0f1576e1882f5022885589c5fcbc46ce4224ec3e53776eeb", size = 9209, upload-time = "2024-08-24T23:15:35.317Z" }, +] + [[package]] name = "attrs" version = "25.4.0" @@ -453,6 +480,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + [[package]] name = "backports-asyncio-runner" version = "1.2.0" @@ -1046,6 +1082,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "colorlog" +version = "6.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, +] + [[package]] name = "compressed-tensors" version = "0.13.0" @@ -1620,6 +1668,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] +[[package]] +name = "dspy" +version = "2.6.27" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "asyncer" }, + { name = "backoff" }, + { name = "cachetools" }, + { name = "cloudpickle" }, + { name = "datasets" }, + { name = "diskcache" }, + { name = "joblib" }, + { name = "json-repair" }, + { name = "litellm" }, + { name = "magicattr" }, + { name = "numpy" }, + { name = "openai" }, + { name = "optuna" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" }, + { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "pydantic" }, + { name = "regex" }, + { name = "requests" }, + { name = "rich" }, + { name = "tenacity" }, + { name = "tqdm" }, + { name = "ujson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/8a/f7ff1a6d3b5294678f13d17ecfc596f49a59e494b190e4e30f7dea7df1dc/dspy-2.6.27.tar.gz", hash = "sha256:de1c4f6f6d127e0efed894e1915dac40f5d5623e7f1cf3d749c98d790066477a", size = 234604, upload-time = "2025-06-03T17:47:13.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/bb/8a75d44bc1b54dea0fa0428eb52b13e7ee533b85841d2c53a53dfc360646/dspy-2.6.27-py3-none-any.whl", hash = "sha256:54e55fd6999b6a46e09b0e49e8c4b71be7dd56a881e66f7a60b8d657650c1a74", size = 297296, upload-time = "2025-06-03T17:47:11.526Z" }, +] + [[package]] name = "einops" version = "0.8.2" @@ -2181,6 +2263,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, ] +[[package]] +name = "gepa" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/30/511e52916956508f56eca721260fcd524cfffd580e57782dd471be925f7e/gepa-0.1.0.tar.gz", hash = "sha256:f8b3d7918d4cdcf8593f39ef1cc757c4ba1a4e6793e3ffb622e6c0bc60a1efd9", size = 226064, upload-time = "2026-02-19T19:43:08.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/32/fe8afb3d2a6605a6bcbc8f119f0a2adae96e9e5d57ebed055490219956a8/gepa-0.1.0-py3-none-any.whl", hash = "sha256:4e3f8fe8ca20169e60518b2e9d416e8c4a579459848adffdcad12223fbf9643e", size = 191392, upload-time = "2026-02-19T19:43:07.065Z" }, +] + [[package]] name = "gguf" version = "0.18.0" @@ -2907,6 +2998,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, ] +[[package]] +name = "json-repair" +version = "0.58.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/5b654ef49ed6077f8f8206dae41c2a2de8fef4877483b2c85652ed95fbaf/json_repair-0.58.5.tar.gz", hash = "sha256:2dfdb44573197eeea8eda23f23677412634b2fe2a93bd1dbe4f1b88e4896efa3", size = 44686, upload-time = "2026-03-07T12:57:16.504Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/55/390151425cd3095da09d38328481ce9ebd0a4f476882ee74849d5b530cf8/json_repair-0.58.5-py3-none-any.whl", hash = "sha256:16f65addc58d8e0b2b8514e3f6ea9ff568267ce94ead95f4faf90e40dd35d526", size = 43458, upload-time = "2026-03-07T12:57:15.455Z" }, +] + [[package]] name = "jsonref" version = "1.1.0" @@ -3170,6 +3270,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, ] +[[package]] +name = "magicattr" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/7e/76b7e0c391bee7e9273725c29c8fe41c4df62a215ce58aa8e3518baee0bb/magicattr-0.1.6-py2.py3-none-any.whl", hash = "sha256:d96b18ee45b5ee83b09c17e15d3459a64de62d538808c2f71182777dd9dbbbdf", size = 4664, upload-time = "2022-01-25T16:56:47.074Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + [[package]] name = "markdown" version = "3.10.2" @@ -4524,6 +4644,12 @@ inference-mlx = [ inference-vllm = [ { name = "vllm" }, ] +learning-dspy = [ + { name = "dspy" }, +] +learning-gepa = [ + { name = "gepa" }, +] media = [ { name = "openai" }, ] @@ -4598,9 +4724,11 @@ requires-dist = [ { name = "deepgram-sdk", marker = "extra == 'speech-deepgram'", specifier = ">=3.0" }, { name = "discord-py", marker = "extra == 'channel-discord'", specifier = ">=2.3" }, { name = "docker", marker = "extra == 'sandbox-docker'", specifier = ">=7.0" }, + { name = "dspy", marker = "extra == 'learning-dspy'", specifier = ">=2.6" }, { name = "faiss-cpu", marker = "extra == 'memory-faiss'", specifier = ">=1.7" }, { name = "fastapi", marker = "extra == 'server'", specifier = ">=0.110" }, { name = "faster-whisper", marker = "extra == 'speech'", specifier = ">=1.0" }, + { name = "gepa", marker = "extra == 'learning-gepa'", specifier = ">=0.1" }, { name = "google-auth", marker = "extra == 'eval-sheets'", specifier = ">=2.0" }, { name = "google-genai", marker = "extra == 'inference-google'", specifier = ">=1.0" }, { name = "gspread", marker = "extra == 'eval-sheets'", specifier = ">=6.0" }, @@ -4658,7 +4786,7 @@ requires-dist = [ { name = "zeus-ml", extras = ["apple"], marker = "extra == 'energy-apple'" }, { name = "zulip", marker = "extra == 'channel-zulip'", specifier = ">=0.9" }, ] -provides-extras = ["dev", "inference-mlx", "inference-vllm", "inference-cloud", "inference-google", "inference-litellm", "tools-search", "memory-faiss", "memory-colbert", "memory-pdf", "memory-bm25", "server", "openhands", "gpu-metrics", "energy-amd", "energy-apple", "energy-all", "orchestrator-training", "channel-telegram", "channel-discord", "channel-slack", "channel-line", "channel-viber", "channel-messenger", "channel-reddit", "channel-mastodon", "channel-xmpp", "channel-rocketchat", "channel-zulip", "channel-twitch", "channel-nostr", "browser", "media", "pdf", "scheduler", "security-signing", "sandbox-wasm", "sandbox-docker", "dashboard", "speech", "speech-deepgram", "eval-wandb", "eval-sheets", "docs"] +provides-extras = ["dev", "inference-mlx", "inference-vllm", "inference-cloud", "inference-google", "inference-litellm", "tools-search", "memory-faiss", "memory-colbert", "memory-pdf", "memory-bm25", "server", "openhands", "gpu-metrics", "energy-amd", "energy-apple", "energy-all", "orchestrator-training", "learning-dspy", "learning-gepa", "channel-telegram", "channel-discord", "channel-slack", "channel-line", "channel-viber", "channel-messenger", "channel-reddit", "channel-mastodon", "channel-xmpp", "channel-rocketchat", "channel-zulip", "channel-twitch", "channel-nostr", "browser", "media", "pdf", "scheduler", "security-signing", "sandbox-wasm", "sandbox-docker", "dashboard", "speech", "speech-deepgram", "eval-wandb", "eval-sheets", "docs"] [package.metadata.requires-dev] dev = [{ name = "maturin", specifier = ">=1.12.6" }] @@ -4814,6 +4942,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/b5/cf25da2218910f0d6cdf7f876a06bed118c4969eacaf60a887cbaef44f44/opentelemetry_semantic_conventions_ai-0.4.13-py3-none-any.whl", hash = "sha256:883a30a6bb5deaec0d646912b5f9f6dcbb9f6f72557b73d0f2560bf25d13e2d5", size = 6080, upload-time = "2025-08-22T10:14:16.477Z" }, ] +[[package]] +name = "optuna" +version = "4.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "colorlog" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/b2/b5e12de7b4486556fe2257611b55dbabf30d0300bdb031831aa943ad20e4/optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0", size = 479740, upload-time = "2026-01-19T05:45:52.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/d1/6c8a4fbb38a9e3565f5c36b871262a85ecab3da48120af036b1e4937a15c/optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186", size = 413894, upload-time = "2026-01-19T05:45:50.815Z" }, +] + [[package]] name = "orjson" version = "3.11.7" @@ -7729,6 +7875,66 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/67/1235676e93dd3b742a4a8eddfae49eea46c85e3eed29f0da446a8dd57500/sqlalchemy-2.0.48-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7001dc9d5f6bb4deb756d5928eaefe1930f6f4179da3924cbd95ee0e9f4dce89", size = 2157384, upload-time = "2026-03-02T15:38:26.781Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d7/fa728b856daa18c10e1390e76f26f64ac890c947008284387451d56ca3d0/sqlalchemy-2.0.48-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a89ce07ad2d4b8cfc30bd5889ec40613e028ed80ef47da7d9dd2ce969ad30e0", size = 3236981, upload-time = "2026-03-02T15:58:53.53Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ad/6c4395649a212a6c603a72c5b9ab5dce3135a1546cfdffa3c427e71fd535/sqlalchemy-2.0.48-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10853a53a4a00417a00913d270dddda75815fcb80675874285f41051c094d7dd", size = 3235232, upload-time = "2026-03-02T15:52:25.654Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/58f845e511ac0509765a6f85eb24924c1ef0d54fb50de9d15b28c3601458/sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fac0fa4e4f55f118fd87177dacb1c6522fe39c28d498d259014020fec9164c29", size = 3188106, upload-time = "2026-03-02T15:58:55.193Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f9/6dcc7bfa5f5794c3a095e78cd1de8269dfb5584dfd4c2c00a50d3c1ade44/sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3713e21ea67bca727eecd4a24bf68bcd414c403faae4989442be60994301ded0", size = 3209522, upload-time = "2026-03-02T15:52:27.407Z" }, + { url = "https://files.pythonhosted.org/packages/d7/5a/b632875ab35874d42657f079529f0745410604645c269a8c21fb4272ff7a/sqlalchemy-2.0.48-cp310-cp310-win32.whl", hash = "sha256:d404dc897ce10e565d647795861762aa2d06ca3f4a728c5e9a835096c7059018", size = 2117695, upload-time = "2026-03-02T15:46:51.389Z" }, + { url = "https://files.pythonhosted.org/packages/de/03/9752eb2a41afdd8568e41ac3c3128e32a0a73eada5ab80483083604a56d1/sqlalchemy-2.0.48-cp310-cp310-win_amd64.whl", hash = "sha256:841a94c66577661c1f088ac958cd767d7c9bf507698f45afffe7a4017049de76", size = 2140928, upload-time = "2026-03-02T15:46:52.992Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6d/b8b78b5b80f3c3ab3f7fa90faa195ec3401f6d884b60221260fd4d51864c/sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc", size = 2157184, upload-time = "2026-03-02T15:38:28.161Z" }, + { url = "https://files.pythonhosted.org/packages/21/4b/4f3d4a43743ab58b95b9ddf5580a265b593d017693df9e08bd55780af5bb/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c", size = 3313555, upload-time = "2026-03-02T15:58:57.21Z" }, + { url = "https://files.pythonhosted.org/packages/21/dd/3b7c53f1dbbf736fd27041aee68f8ac52226b610f914085b1652c2323442/sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7", size = 3313057, upload-time = "2026-03-02T15:52:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cc/3e600a90ae64047f33313d7d32e5ad025417f09d2ded487e8284b5e21a15/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d", size = 3265431, upload-time = "2026-03-02T15:58:59.096Z" }, + { url = "https://files.pythonhosted.org/packages/8b/19/780138dacfe3f5024f4cf96e4005e91edf6653d53d3673be4844578faf1d/sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571", size = 3287646, upload-time = "2026-03-02T15:52:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/40/fd/f32ced124f01a23151f4777e4c705f3a470adc7bd241d9f36a7c941a33bf/sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617", size = 2116956, upload-time = "2026-03-02T15:46:54.535Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/dd767277f6feef12d05651538f280277e661698f617fa4d086cce6055416/sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c", size = 2141627, upload-time = "2026-03-02T15:46:55.849Z" }, + { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, + { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, + { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, + { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, + { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, + { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, +] + [[package]] name = "sse-starlette" version = "3.2.0"