Skip to content

Commit c4cf745

Browse files
Release 1.3.0 (#1262)
Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 5f73b1f commit c4cf745

File tree

21 files changed

+24
-476
lines changed

21 files changed

+24
-476
lines changed

examples/01_standalone_sdk/04_confirmation_mode_example.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
ConversationState,
1313
)
1414
from openhands.sdk.security.confirmation_policy import AlwaysConfirm, NeverConfirm
15+
from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer
1516
from openhands.tools.preset.default import get_default_agent
1617

1718

@@ -91,11 +92,14 @@ def run_until_finished(conversation: BaseConversation, confirmer: Callable) -> N
9192
api_key=SecretStr(api_key),
9293
)
9394

95+
agent = get_default_agent(llm=llm)
96+
conversation = Conversation(agent=agent, workspace=os.getcwd())
97+
98+
# Conditionally add security analyzer based on environment variable
9499
add_security_analyzer = bool(os.getenv("ADD_SECURITY_ANALYZER", "").strip())
95100
if add_security_analyzer:
96101
print("Agent security analyzer added.")
97-
agent = get_default_agent(llm=llm, add_security_analyzer=add_security_analyzer)
98-
conversation = Conversation(agent=agent, workspace=os.getcwd())
102+
conversation.set_security_analyzer(LLMSecurityAnalyzer())
99103

100104
# 1) Confirmation mode ON
101105
conversation.set_confirmation_policy(AlwaysConfirm())

examples/01_standalone_sdk/07_mcp_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
mcp_config=mcp_config,
5151
# This regex filters out all repomix tools except pack_codebase
5252
filter_tools_regex="^(?!repomix)(.*)|^repomix.*pack_codebase.*$",
53-
security_analyzer=LLMSecurityAnalyzer(),
5453
)
5554

5655
llm_messages = [] # collect raw LLM messages
@@ -67,6 +66,7 @@ def conversation_callback(event: Event):
6766
callbacks=[conversation_callback],
6867
workspace=cwd,
6968
)
69+
conversation.set_security_analyzer(LLMSecurityAnalyzer())
7070

7171
logger.info("Starting conversation with MCP integration...")
7272
conversation.send_message(

examples/01_standalone_sdk/16_llm_security_analyzer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,14 @@ def run_until_finished_with_security(
111111
Tool(name=FileEditorTool.name),
112112
]
113113

114-
# Agent with security analyzer
115-
security_analyzer = LLMSecurityAnalyzer()
116-
agent = Agent(llm=llm, tools=tools, security_analyzer=security_analyzer)
114+
# Agent
115+
agent = Agent(llm=llm, tools=tools)
117116

118117
# Conversation with persisted filestore
119118
conversation = Conversation(
120119
agent=agent, persistence_dir="./.conversations", workspace="."
121120
)
121+
conversation.set_security_analyzer(LLMSecurityAnalyzer())
122122
conversation.set_confirmation_policy(ConfirmRisky())
123123

124124
print("\n1) Safe command (LOW risk - should execute automatically)...")

openhands-agent-server/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "openhands-agent-server"
3-
version = "1.2.0"
3+
version = "1.3.0"
44
description = "OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent"
55

66
requires-python = ">=3.12"

openhands-sdk/openhands/sdk/agent/base.py

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,15 @@
55
from collections.abc import Generator, Iterable
66
from typing import TYPE_CHECKING, Any
77

8-
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator
8+
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
99

1010
from openhands.sdk.context.agent_context import AgentContext
1111
from openhands.sdk.context.condenser import CondenserBase, LLMSummarizingCondenser
1212
from openhands.sdk.context.prompts.prompt import render_template
1313
from openhands.sdk.llm import LLM
1414
from openhands.sdk.logger import get_logger
1515
from openhands.sdk.mcp import create_mcp_tools
16-
from openhands.sdk.security import analyzer
1716
from openhands.sdk.tool import BUILT_IN_TOOLS, Tool, ToolDefinition, resolve_tool
18-
from openhands.sdk.utils.deprecation import (
19-
warn_deprecated,
20-
)
2117
from openhands.sdk.utils.models import DiscriminatedUnionMixin
2218
from openhands.sdk.utils.pydantic_diff import pretty_pydantic_diff
2319

@@ -30,12 +26,6 @@
3026
logger = get_logger(__name__)
3127

3228

33-
AGENT_SECURITY_ANALYZER_DEPRECATION_DETAILS = (
34-
"Use `conversation = Conversation(); "
35-
"conversation.set_security_analyzer(...)` instead."
36-
)
37-
38-
3929
class AgentBase(DiscriminatedUnionMixin, ABC):
4030
"""Abstract base class for OpenHands agents.
4131
@@ -132,12 +122,6 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
132122
examples=[{"cli_mode": True}],
133123
)
134124

135-
security_analyzer: analyzer.SecurityAnalyzerBase | None = Field(
136-
default=None,
137-
description="Optional security analyzer to evaluate action risks.",
138-
examples=[{"kind": "LLMSecurityAnalyzer"}],
139-
)
140-
141125
condenser: CondenserBase | None = Field(
142126
default=None,
143127
description="Optional condenser to use for condensing conversation history.",
@@ -158,24 +142,6 @@ class AgentBase(DiscriminatedUnionMixin, ABC):
158142
# Runtime materialized tools; private and non-serializable
159143
_tools: dict[str, ToolDefinition] = PrivateAttr(default_factory=dict)
160144

161-
@model_validator(mode="before")
162-
@classmethod
163-
def _coerce_inputs(cls, data):
164-
if not isinstance(data, dict):
165-
return data
166-
d = dict(data)
167-
168-
if "security_analyzer" in d and d["security_analyzer"]:
169-
warn_deprecated(
170-
"Agent.security_analyzer",
171-
deprecated_in="1.1.0",
172-
removed_in="1.3.0",
173-
details=AGENT_SECURITY_ANALYZER_DEPRECATION_DETAILS,
174-
stacklevel=3,
175-
)
176-
177-
return d
178-
179145
@property
180146
def prompt_dir(self) -> str:
181147
"""Returns the directory where this class's module file is located."""
@@ -222,15 +188,6 @@ def init_state(
222188
def _initialize(self, state: "ConversationState"):
223189
"""Create an AgentBase instance from an AgentSpec."""
224190

225-
# 1) Migrate deprecated analyzer → state (if present)
226-
if self.security_analyzer and not state.security_analyzer:
227-
state.security_analyzer = self.security_analyzer
228-
# 2) Clear on the immutable model (allowed via object.__setattr__)
229-
try:
230-
object.__setattr__(self, "security_analyzer", None)
231-
except Exception:
232-
logger.warning("Could not clear deprecated Agent.security_analyzer")
233-
234191
if self._tools:
235192
logger.warning("Agent already initialized; skipping re-initialization.")
236193
return
@@ -299,8 +256,7 @@ def step(
299256
def resolve_diff_from_deserialized(self, persisted: "AgentBase") -> "AgentBase":
300257
"""
301258
Return a new AgentBase instance equivalent to `persisted` but with
302-
explicitly whitelisted fields (e.g. api_key, security_analyzer) taken from
303-
`self`.
259+
explicitly whitelisted fields (e.g. api_key) taken from `self`.
304260
"""
305261
if persisted.__class__ is not self.__class__:
306262
raise ValueError(
@@ -329,9 +285,6 @@ def resolve_diff_from_deserialized(self, persisted: "AgentBase") -> "AgentBase":
329285
)
330286
updates["condenser"] = new_condenser
331287

332-
# Allow security_analyzer to differ - use the runtime (self) version
333-
updates["security_analyzer"] = self.security_analyzer
334-
335288
# Create maps by tool name for easy lookup
336289
runtime_tools_map = {tool.name: tool for tool in self.tools}
337290
persisted_tools_map = {tool.name: tool for tool in persisted.tools}

openhands-sdk/openhands/sdk/conversation/conversation_stats.py

Lines changed: 1 addition & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,23 @@
1-
from pydantic import AliasChoices, BaseModel, Field, PrivateAttr
1+
from pydantic import BaseModel, Field, PrivateAttr
22

33
from openhands.sdk.llm.llm_registry import RegistryEvent
44
from openhands.sdk.llm.utils.metrics import Metrics
55
from openhands.sdk.logger import get_logger
6-
from openhands.sdk.utils.deprecation import (
7-
deprecated,
8-
)
96

107

118
logger = get_logger(__name__)
129

13-
SERVICE_TO_USAGE_DEPRECATION_DETAILS = (
14-
"Use usage_to_metrics instead of service_to_metrics."
15-
)
16-
RESTORED_SERVICES_DEPRECATION_DETAILS = (
17-
"Use _restored_usage_ids instead of _restored_services."
18-
)
19-
2010

2111
class ConversationStats(BaseModel):
2212
"""Track per-LLM usage metrics observed during conversations."""
2313

2414
usage_to_metrics: dict[str, Metrics] = Field(
2515
default_factory=dict,
26-
validation_alias=AliasChoices("usage_to_metrics", "service_to_metrics"),
27-
serialization_alias="usage_to_metrics",
2816
description="Active usage metrics tracked by the registry.",
2917
)
3018

3119
_restored_usage_ids: set[str] = PrivateAttr(default_factory=set)
3220

33-
@property
34-
@deprecated(
35-
deprecated_in="1.1.0",
36-
removed_in="1.3.0",
37-
details=SERVICE_TO_USAGE_DEPRECATION_DETAILS,
38-
)
39-
def service_to_metrics(
40-
self,
41-
) -> dict[str, Metrics]: # pragma: no cover - compatibility shim
42-
return self.usage_to_metrics
43-
44-
@service_to_metrics.setter
45-
@deprecated(
46-
deprecated_in="1.1.0",
47-
removed_in="1.3.0",
48-
details=SERVICE_TO_USAGE_DEPRECATION_DETAILS,
49-
)
50-
def service_to_metrics(
51-
self, value: dict[str, Metrics]
52-
) -> None: # pragma: no cover - compatibility shim
53-
self.usage_to_metrics = value
54-
55-
@property
56-
@deprecated(
57-
deprecated_in="1.1.0",
58-
removed_in="1.3.0",
59-
details=RESTORED_SERVICES_DEPRECATION_DETAILS,
60-
)
61-
def _restored_services(self) -> set[str]: # pragma: no cover - compatibility shim
62-
return self._restored_usage_ids
63-
6421
def get_combined_metrics(self) -> Metrics:
6522
total_metrics = Metrics()
6623
for metrics in self.usage_to_metrics.values():
@@ -73,16 +30,6 @@ def get_metrics_for_usage(self, usage_id: str) -> Metrics:
7330

7431
return self.usage_to_metrics[usage_id]
7532

76-
@deprecated(
77-
deprecated_in="1.1.0",
78-
removed_in="1.3.0",
79-
details=SERVICE_TO_USAGE_DEPRECATION_DETAILS,
80-
)
81-
def get_metrics_for_service(
82-
self, service_id: str
83-
) -> Metrics: # pragma: no cover - compatibility shim
84-
return self.get_metrics_for_usage(service_id)
85-
8633
def register_llm(self, event: RegistryEvent):
8734
# Listen for LLM creations and track their metrics
8835
llm = event.llm

openhands-sdk/openhands/sdk/llm/llm.py

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@
2929
if TYPE_CHECKING: # type hints only, avoid runtime import cycle
3030
from openhands.sdk.tool.tool import ToolDefinition
3131

32-
from openhands.sdk.utils.deprecation import (
33-
deprecated,
34-
warn_deprecated,
35-
)
3632
from openhands.sdk.utils.pydantic_diff import pretty_pydantic_diff
3733

3834

@@ -98,8 +94,6 @@
9894
LLMNoResponseError,
9995
)
10096

101-
SERVICE_ID_DEPRECATION_DETAILS = "Use LLM.usage_id instead of LLM.service_id."
102-
10397

10498
class LLM(BaseModel, RetryMixin, NonNativeToolCallingMixin):
10599
"""Language model interface for OpenHands agents.
@@ -332,16 +326,6 @@ def _coerce_inputs(cls, data):
332326
return data
333327
d = dict(data)
334328

335-
if "service_id" in d and "usage_id" not in d:
336-
warn_deprecated(
337-
"LLM.service_id",
338-
deprecated_in="1.1.0",
339-
removed_in="1.3.0",
340-
details=SERVICE_ID_DEPRECATION_DETAILS,
341-
stacklevel=3,
342-
)
343-
d["usage_id"] = d.pop("service_id")
344-
345329
model_val = d.get("model")
346330
if not model_val:
347331
raise ValueError("model must be specified in LLM")
@@ -422,24 +406,6 @@ def _serialize_secrets(self, v: SecretStr | None, info):
422406
# =========================================================================
423407
# Public API
424408
# =========================================================================
425-
@property
426-
@deprecated(
427-
deprecated_in="1.1.0",
428-
removed_in="1.3.0",
429-
details=SERVICE_ID_DEPRECATION_DETAILS,
430-
)
431-
def service_id(self) -> str:
432-
return self.usage_id
433-
434-
@service_id.setter
435-
@deprecated(
436-
deprecated_in="1.1.0",
437-
removed_in="1.3.0",
438-
details=SERVICE_ID_DEPRECATION_DETAILS,
439-
)
440-
def service_id(self, value: str) -> None:
441-
self.usage_id = value
442-
443409
@property
444410
def metrics(self) -> Metrics:
445411
"""Get usage metrics for this LLM instance.

openhands-sdk/openhands/sdk/llm/llm_registry.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,11 @@
66

77
from openhands.sdk.llm.llm import LLM
88
from openhands.sdk.logger import get_logger
9-
from openhands.sdk.utils.deprecation import (
10-
deprecated,
11-
)
129

1310

1411
logger = get_logger(__name__)
1512

1613

17-
SERVICE_TO_LLM_DEPRECATION_DETAILS = "Use usage_to_llm instead of service_to_llm."
18-
19-
LIST_SERVICES_DEPRECATION_DETAILS = "Use list_usage_ids instead of list_services."
20-
21-
2214
class RegistryEvent(BaseModel):
2315
llm: LLM
2416

@@ -77,15 +69,6 @@ def usage_to_llm(self) -> dict[str, LLM]:
7769

7870
return self._usage_to_llm
7971

80-
@property
81-
@deprecated(
82-
deprecated_in="1.1.0",
83-
removed_in="1.3.0",
84-
details=SERVICE_TO_LLM_DEPRECATION_DETAILS,
85-
)
86-
def service_to_llm(self) -> dict[str, LLM]: # pragma: no cover - compatibility shim
87-
return self._usage_to_llm
88-
8972
def add(self, llm: LLM) -> None:
9073
"""Add an LLM instance to the registry.
9174
@@ -137,13 +120,3 @@ def list_usage_ids(self) -> list[str]:
137120
"""List all registered usage IDs."""
138121

139122
return list(self._usage_to_llm.keys())
140-
141-
@deprecated(
142-
deprecated_in="1.1.0",
143-
removed_in="1.3.0",
144-
details=LIST_SERVICES_DEPRECATION_DETAILS,
145-
)
146-
def list_services(self) -> list[str]: # pragma: no cover - compatibility shim
147-
"""Deprecated alias for :meth:`list_usage_ids`."""
148-
149-
return list(self._usage_to_llm.keys())

openhands-sdk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "openhands-sdk"
3-
version = "1.2.0"
3+
version = "1.3.0"
44
description = "OpenHands SDK - Core functionality for building AI agents"
55

66
requires-python = ">=3.12"

0 commit comments

Comments
 (0)