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
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
@file_router.post("/upload/{path:path}")
async def upload_file(
path: Annotated[str, FastApiPath(alias="path", description="Absolute file path.")],
file: UploadFile = File(...),
file: Annotated[UploadFile, File(...)],
) -> Success:
"""Upload a file to the workspace."""
logger.info(f"Uploading file: {path}")
Expand Down
16 changes: 8 additions & 8 deletions openhands-sdk/openhands/sdk/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,9 @@ def _get_action_event(
tool_call: MessageToolCall,
llm_response_id: str,
on_event: ConversationCallbackType,
thought: list[TextContent] = [],
thought: list[TextContent] | None = None,
reasoning_content: str | None = None,
thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] = [],
thinking_blocks: list[ThinkingBlock | RedactedThinkingBlock] | None = None,
responses_reasoning_item: ReasoningItemModel | None = None,
) -> ActionEvent | None:
"""Converts a tool call into an ActionEvent, validating arguments.
Expand All @@ -328,9 +328,9 @@ def _get_action_event(
# Persist assistant function_call so next turn has matching call_id
tc_event = ActionEvent(
source="agent",
thought=thought,
thought=thought or [],
reasoning_content=reasoning_content,
thinking_blocks=thinking_blocks,
thinking_blocks=thinking_blocks or [],
responses_reasoning_item=responses_reasoning_item,
tool_call=tool_call,
tool_name=tool_call.name,
Expand Down Expand Up @@ -380,9 +380,9 @@ def _get_action_event(
# Persist assistant function_call so next turn has matching call_id
tc_event = ActionEvent(
source="agent",
thought=thought,
thought=thought or [],
reasoning_content=reasoning_content,
thinking_blocks=thinking_blocks,
thinking_blocks=thinking_blocks or [],
responses_reasoning_item=responses_reasoning_item,
tool_call=tool_call,
tool_name=tool_call.name,
Expand All @@ -401,9 +401,9 @@ def _get_action_event(

action_event = ActionEvent(
action=action,
thought=thought,
thought=thought or [],
reasoning_content=reasoning_content,
thinking_blocks=thinking_blocks,
thinking_blocks=thinking_blocks or [],
responses_reasoning_item=responses_reasoning_item,
tool_name=tool.name,
tool_call_id=tool_call.id,
Expand Down
14 changes: 14 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,25 @@ select = [
"UP", # pyupgrade
"ARG", # flake8-unused-arguments
]
# Enforce rules that catch mutable defaults and related pitfalls
# - B006: mutable-argument-default
# - B008: function-call-in-default-argument
# - B039: mutable-contextvar-default
# - RUF012: mutable-class-default
extend-select = ["B006", "B008", "B039", "RUF012"]

[tool.ruff.lint.per-file-ignores]
# Test files often have unused arguments (fixtures, mocks, interface implementations)
"tests/**/*.py" = ["ARG"]


# Allowlist safe default calls for flake8-bugbear rules (e.g., FastAPI Depends)
[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = [
"fastapi.Depends",
"fastapi.params.Depends",
]

[tool.ruff.lint.isort]
known-first-party = ["openhands"]
combine-as-imports = true
Expand Down
8 changes: 5 additions & 3 deletions tests/sdk/agent/test_agent_llms_are_discoverable.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from pydantic import Field

from openhands.sdk import LLM, Agent, LLMSummarizingCondenser
from openhands.sdk.llm.router import MultimodalRouter

Expand Down Expand Up @@ -36,7 +38,7 @@ def test_automatic_llm_discovery_for_multiple_llms():

def test_automatic_llm_discovery_for_custom_agent_with_duplicates():
class CustomAgent(Agent):
model_routers: list[LLM] = []
model_routers: list[LLM] = Field(default_factory=list)

llm_service_id = "main-agent"
router_service_id = "secondary_llm"
Expand Down Expand Up @@ -94,8 +96,8 @@ def test_automatic_llm_discovery_with_multimodal_router():

def test_automatic_llm_discovery_with_llm_as_base_class():
class NewLLM(LLM):
list_llms: list[LLM] = []
dict_llms: dict[str, LLM] = {}
list_llms: list[LLM] = Field(default_factory=list)
dict_llms: dict[str, LLM] = Field(default_factory=dict)
raw_llm: LLM | None = None

list_llm = LLM(model="list-model", usage_id="list-model")
Expand Down
3 changes: 2 additions & 1 deletion tests/sdk/conversation/test_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json

from pydantic import Field
from rich.text import Text

from openhands.sdk.conversation.visualizer import (
Expand Down Expand Up @@ -34,7 +35,7 @@ class VisualizerMockAction(Action):
class VisualizerCustomAction(Action):
"""Custom action with overridden visualize method."""

task_list: list[dict] = []
task_list: list[dict] = Field(default_factory=list)

@property
def visualize(self) -> Text:
Expand Down
20 changes: 12 additions & 8 deletions tests/sdk/security/test_security_analyzer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Tests for the SecurityAnalyzer class."""

from pydantic import Field

from openhands.sdk.event import ActionEvent, PauseEvent
from openhands.sdk.llm import MessageToolCall, TextContent
from openhands.sdk.security.analyzer import SecurityAnalyzerBase
Expand All @@ -19,9 +21,9 @@ class SecurityAnalyzer(SecurityAnalyzerBase):
"""

risk_return_value: SecurityRisk = SecurityRisk.LOW
security_risk_calls: list[ActionEvent] = []
handle_api_request_calls: list[dict] = []
close_calls: list[bool] = []
security_risk_calls: list[ActionEvent] = Field(default_factory=list)
handle_api_request_calls: list[dict] = Field(default_factory=list)
close_calls: list[bool] = Field(default_factory=list)

def security_risk(self, action: ActionEvent) -> SecurityRisk:
"""Return configurable risk level for testing."""
Expand Down Expand Up @@ -131,11 +133,13 @@ def test_analyze_pending_actions_mixed_risks() -> None:

class VariableRiskAnalyzer(SecurityAnalyzer):
call_count: int = 0
risks: list[SecurityRisk] = [
SecurityRisk.LOW,
SecurityRisk.HIGH,
SecurityRisk.MEDIUM,
]
risks: list[SecurityRisk] = Field(
default_factory=lambda: [
SecurityRisk.LOW,
SecurityRisk.HIGH,
SecurityRisk.MEDIUM,
]
)

def security_risk(self, action: ActionEvent) -> SecurityRisk:
risk = self.risks[self.call_count % len(self.risks)]
Expand Down
Loading