Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/run-eval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ on:
sdk_ref:
description: SDK commit/ref to evaluate (must be a semantic version like v1.0.0 unless 'Allow unreleased branches' is checked)
required: true
default: v1.23.1
default: v1.24.0




Expand Down
2 changes: 1 addition & 1 deletion openhands-agent-server/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openhands-agent-server"
version = "1.23.1"
version = "1.24.0"
description = "OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent"

requires-python = ">=3.12"
Expand Down
35 changes: 0 additions & 35 deletions openhands-sdk/openhands/sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from importlib.metadata import PackageNotFoundError, version
from typing import TYPE_CHECKING, Any

from openhands.sdk.agent import (
Agent,
Expand Down Expand Up @@ -71,10 +70,6 @@
get_acp_provider,
validate_agent_settings,
)


if TYPE_CHECKING:
from openhands.sdk.settings import LLMAgentSettings
from openhands.sdk.settings.metadata import (
SettingProminence,
SettingsFieldMetadata,
Expand Down Expand Up @@ -119,35 +114,6 @@
# Print startup banner
_print_banner(__version__)

_DEPRECATED_SDK_EXPORTS: dict[str, dict[str, str]] = {
"LLMAgentSettings": {
"deprecated_in": "1.19.0",
"removed_in": "1.24.0",
"details": (
"Use ``OpenHandsAgentSettings`` directly. "
"``LLMAgentSettings`` was renamed in v1.19.0."
),
},
}


def __getattr__(name: str) -> Any:
if name in _DEPRECATED_SDK_EXPORTS:
from openhands.sdk.utils.deprecation import warn_deprecated

info = _DEPRECATED_SDK_EXPORTS[name]
warn_deprecated(
f"Importing {name!r} from openhands.sdk",
deprecated_in=info["deprecated_in"],
removed_in=info["removed_in"],
details=info["details"],
stacklevel=3,
)
from openhands.sdk import settings as _settings

return getattr(_settings, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
"LLM",
Expand Down Expand Up @@ -197,7 +163,6 @@ def __getattr__(name: str) -> Any:
"ACPProviderInfo",
"AgentSettingsBase",
"AgentSettingsConfig",
"LLMAgentSettings",
"OpenHandsAgentSettings",
"build_session_model_meta",
"default_agent_settings",
Expand Down
18 changes: 0 additions & 18 deletions openhands-sdk/openhands/sdk/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
AgentSettingsConfig,
CondenserSettings,
ConversationSettings,
LLMAgentSettings,
OpenHandsAgentSettings,
SettingsChoice,
SettingsFieldSchema,
Expand Down Expand Up @@ -86,7 +85,6 @@
"AgentSettingsConfig",
"CondenserSettings",
"ConversationSettings",
"LLMAgentSettings",
"OpenHandsAgentSettings",
"SETTINGS_METADATA_KEY",
"SETTINGS_SECTION_METADATA_KEY",
Expand Down Expand Up @@ -116,22 +114,6 @@


def __getattr__(name: str) -> Any:
if name == "LLMAgentSettings":
from openhands.sdk.utils.deprecation import warn_deprecated

warn_deprecated(
f"Importing {name!r} from openhands.sdk.settings",
deprecated_in="1.19.0",
removed_in="1.24.0",
details=(
"Use ``OpenHandsAgentSettings`` directly. "
"``LLMAgentSettings`` was renamed in v1.19.0."
),
stacklevel=3,
)
from . import model

return getattr(model, name)
if name in _MODEL_EXPORTS:
from . import model

Expand Down
21 changes: 11 additions & 10 deletions openhands-sdk/openhands/sdk/settings/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def _migrate_agent_settings_v1_to_v2(payload: dict[str, Any]) -> dict[str, Any]:
persisted payloads carried ``agent_kind: 'llm'``. The two classes are
field-compatible (``LLMAgentSettings`` is a subclass of
``OpenHandsAgentSettings`` that only narrows the discriminator literal),
and ``LLMAgentSettings`` is scheduled for removal in v1.24.0. Rewriting
and ``LLMAgentSettings``'s import aliases were removed in v1.24.0. Rewriting
the discriminator on read lets callers that explicitly validate as
``OpenHandsAgentSettings`` (the canonical class) accept legacy data
without losing any fields.
Expand Down Expand Up @@ -1250,17 +1250,18 @@ def create_agent(self) -> ACPAgent:


class LLMAgentSettings(OpenHandsAgentSettings):
"""Deprecated name for :class:`OpenHandsAgentSettings`.
"""Legacy ``agent_kind='llm'`` variant of :class:`OpenHandsAgentSettings`.

``LLMAgentSettings`` was the public class name before the v1.19.0 rename.
It is kept as a :class:`OpenHandsAgentSettings` subclass so existing
callers keep working. Importing this name from ``openhands.sdk.settings``
(or ``openhands.sdk``) emits a :class:`DeprecationWarning` via the
module-level ``__getattr__`` — no construction-time overhead.

Use :class:`OpenHandsAgentSettings` for all new code.

Scheduled for removal in v1.24.0.
The public import aliases (``from openhands.sdk import LLMAgentSettings`` and
``from openhands.sdk.settings import LLMAgentSettings``) were removed in
v1.24.0 — use :class:`OpenHandsAgentSettings` for all new code.

The class itself is retained (reachable at
``openhands.sdk.settings.model.LLMAgentSettings``) because it remains a
member of the settings discriminated union: it keeps ``agent_kind='llm'`` so
persisted legacy payloads still deserialize and the API-breakage checker
sees no field-value change versus the published release.
Comment thread
simonrosenberg marked this conversation as resolved.
"""

# Keep agent_kind as Literal["llm"] so the API-breakage checker sees no
Expand Down
56 changes: 2 additions & 54 deletions openhands-sdk/openhands/sdk/tool/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from openhands.sdk.logger import get_logger
from openhands.sdk.tool.spec import Tool
from openhands.sdk.tool.tool import ToolDefinition
from openhands.sdk.utils.deprecation import warn_deprecated


if TYPE_CHECKING:
Expand Down Expand Up @@ -54,32 +53,6 @@ def _resolve(
return _resolve


def _resolver_from_callable(
name: str, factory: Callable[..., Sequence[ToolDefinition]]
) -> Resolver:
def _resolve(
params: dict[str, Any], conv_state: "ConversationState"
) -> Sequence[ToolDefinition]:
try:
# Try to call with conv_state parameter first
created = factory(conv_state=conv_state, **params)
except TypeError as exc:
raise TypeError(
f"Unable to resolve tool '{name}': factory could not be called with "
f"params {params}."
) from exc
if not isinstance(created, Sequence) or not all(
isinstance(t, ToolDefinition) for t in created
):
raise TypeError(
f"Factory '{name}' must return Sequence[ToolDefinition], "
f"got {type(created)}"
)
return created

return _resolve


def _is_abstract_method(cls: type, name: str) -> bool:
try:
attr = inspect.getattr_static(cls, name)
Expand Down Expand Up @@ -127,13 +100,6 @@ def _usability_from_subclass(cls: type[ToolDefinition]) -> UsabilityChecker:
return lambda: cls.is_usable()


def _usability_from_callable(
_factory: Callable[..., Sequence[ToolDefinition]],
) -> UsabilityChecker:
# Callable factories are deprecated and have no usability hook.
return lambda: True


def _check_tool_usable(name: str, checker: UsabilityChecker) -> bool:
try:
return checker()
Expand All @@ -146,9 +112,7 @@ def _check_tool_usable(name: str, checker: UsabilityChecker) -> bool:

def register_tool(
name: str,
factory: ToolDefinition
| type[ToolDefinition]
| Callable[..., Sequence[ToolDefinition]],
factory: ToolDefinition | type[ToolDefinition],
) -> None:
if not isinstance(name, str) or not name.strip():
raise ValueError("ToolDefinition name must be a non-empty string")
Expand All @@ -159,32 +123,16 @@ def register_tool(
elif isinstance(factory, type) and issubclass(factory, ToolDefinition):
resolver = _resolver_from_subclass(name, factory)
usability_checker = _usability_from_subclass(factory)
elif callable(factory):
warn_deprecated(
"register_tool(callable_factory)",
deprecated_in="1.19.1",
removed_in="1.24.0",
details=(
"Register a ToolDefinition subclass with create(...) or a "
"ToolDefinition instance instead."
),
stacklevel=2,
)
resolver = _resolver_from_callable(name, factory)
usability_checker = _usability_from_callable(factory)
else:
raise TypeError(
"register_tool(...) only accepts: (1) a ToolDefinition instance with "
".executor, (2) a ToolDefinition subclass with .create(**params), or "
"(3) a callable factory returning a Sequence[ToolDefinition]"
".executor, or (2) a ToolDefinition subclass with .create(**params)"
)

# Track the module qualname for this tool
module_qualname = None
if isinstance(factory, type):
module_qualname = factory.__module__
elif callable(factory):
module_qualname = getattr(factory, "__module__", None)
elif isinstance(factory, ToolDefinition):
module_qualname = factory.__class__.__module__

Expand Down
2 changes: 1 addition & 1 deletion openhands-sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openhands-sdk"
version = "1.23.1"
version = "1.24.0"
Comment thread
simonrosenberg marked this conversation as resolved.
description = "OpenHands SDK - Core functionality for building AI agents"

requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion openhands-tools/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openhands-tools"
version = "1.23.1"
version = "1.24.0"
description = "OpenHands Tools - Runtime tools for AI agents"

requires-python = ">=3.12"
Expand Down
2 changes: 1 addition & 1 deletion openhands-workspace/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openhands-workspace"
version = "1.23.1"
version = "1.24.0"
description = "OpenHands Workspace - Docker and container-based workspace implementations"

requires-python = ">=3.12"
Expand Down
21 changes: 0 additions & 21 deletions tests/cross/test_registry_qualnames.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
"""Tests for tool registry module qualname tracking."""

import pytest
from deprecation import DeprecatedWarning

from openhands.sdk.tool.registry import (
get_tool_module_qualnames,
list_registered_tools,
Expand All @@ -25,24 +22,6 @@ def test_get_tool_module_qualnames_with_class():
assert qualnames["test_glob_class"] == "openhands.tools.glob.definition"


def test_get_tool_module_qualnames_with_callable():
"""Test that module qualnames are tracked when registering a callable."""

def test_factory(conv_state):
return []

# Register the callable
with pytest.warns(DeprecatedWarning, match=r"register_tool\(callable_factory\)"):
register_tool("test_callable", test_factory)

# Get the module qualnames
qualnames = get_tool_module_qualnames()

# Verify the tool is tracked with its module
assert "test_callable" in qualnames
assert "test_registry_qualnames" in qualnames["test_callable"]


def test_get_tool_module_qualnames_after_import():
"""Test that importing a tool module registers it with qualname."""
# Import glob tool module to trigger auto-registration
Expand Down
12 changes: 2 additions & 10 deletions tests/sdk/agent/test_agent_tool_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,9 @@ def create(cls, conv_state=None, **params) -> Sequence["_UpperTool"]:
]


def _make_tool(conv_state=None, **kwargs) -> Sequence[ToolDefinition]:
return _UpperTool.create(conv_state, **kwargs)


def test_agent_initializes_tools_from_toolspec_locally(monkeypatch):
# Register a simple local tool via registry
register_tool("upper", _make_tool)
register_tool("upper", _UpperTool)

llm = LLM(model="test-model", usage_id="test-llm")
agent = Agent(llm=llm, tools=[Tool(name="upper")])
Expand Down Expand Up @@ -161,13 +157,9 @@ def create(cls, conv_state=None, **params) -> Sequence["_CustomFinishTool"]:
]


def _make_custom_finish_tool(conv_state=None, **kwargs) -> Sequence[ToolDefinition]:
return _CustomFinishTool.create(conv_state, **kwargs)


def test_agent_replace_finish_with_custom_tool():
"""Test that the finish tool can be replaced with a custom implementation."""
register_tool("custom_finish", _make_custom_finish_tool)
register_tool("custom_finish", _CustomFinishTool)

llm = LLM(model="test-model", usage_id="test-llm")
agent = Agent(
Expand Down
7 changes: 1 addition & 6 deletions tests/sdk/agent/test_message_while_finishing.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,8 @@ def create(cls, conv_state=None, **params) -> Sequence["SleepTool"]:
]


def _make_sleep_tool(conv_state=None, **kwargs) -> Sequence[ToolDefinition]:
"""Create sleep tool for testing."""
return SleepTool.create(conv_state, **kwargs)


# Register the tool
register_tool("SleepTool", _make_sleep_tool)
register_tool("SleepTool", SleepTool)


class TestMessageWhileFinishing:
Expand Down
Loading
Loading