openhands-tools 1.43.0__tar.gz → 1.44.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/PKG-INFO +1 -1
  2. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/__init__.py +2 -0
  3. openhands_tools-1.44.0/openhands/tools/ask_oracle/__init__.py +36 -0
  4. openhands_tools-1.44.0/openhands/tools/ask_oracle/definition.py +114 -0
  5. openhands_tools-1.44.0/openhands/tools/ask_oracle/impl.py +120 -0
  6. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/browser_use/impl.py +12 -0
  7. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/__init__.py +6 -0
  8. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/default.py +19 -0
  9. openhands_tools-1.44.0/openhands/tools/preset/task_outcome.py +35 -0
  10. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands_tools.egg-info/PKG-INFO +1 -1
  11. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands_tools.egg-info/SOURCES.txt +8 -0
  12. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/pyproject.toml +1 -1
  13. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/apply_patch/__init__.py +0 -0
  14. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/apply_patch/core.py +0 -0
  15. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/apply_patch/definition.py +0 -0
  16. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/browser_use/__init__.py +0 -0
  17. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/browser_use/definition.py +0 -0
  18. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/browser_use/event_storage.py +0 -0
  19. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/browser_use/logging_fix.py +0 -0
  20. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/browser_use/recording.py +0 -0
  21. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/browser_use/server.py +0 -0
  22. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/delegate/__init__.py +0 -0
  23. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/delegate/definition.py +0 -0
  24. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/delegate/impl.py +0 -0
  25. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/delegate/visualizer.py +0 -0
  26. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/__init__.py +0 -0
  27. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/definition.py +0 -0
  28. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/editor.py +0 -0
  29. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/exceptions.py +0 -0
  30. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/impl.py +0 -0
  31. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/__init__.py +0 -0
  32. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/config.py +0 -0
  33. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/constants.py +0 -0
  34. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/diff.py +0 -0
  35. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/encoding.py +0 -0
  36. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/file_cache.py +0 -0
  37. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/history.py +0 -0
  38. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/file_editor/utils/shell.py +0 -0
  39. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/__init__.py +0 -0
  40. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/edit/__init__.py +0 -0
  41. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/edit/definition.py +0 -0
  42. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/edit/impl.py +0 -0
  43. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/file_change.py +0 -0
  44. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/list_directory/__init__.py +0 -0
  45. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/list_directory/definition.py +0 -0
  46. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/list_directory/impl.py +0 -0
  47. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/read_file/__init__.py +0 -0
  48. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/read_file/definition.py +0 -0
  49. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/read_file/impl.py +0 -0
  50. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/write_file/__init__.py +0 -0
  51. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/write_file/definition.py +0 -0
  52. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/gemini/write_file/impl.py +0 -0
  53. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/glob/__init__.py +0 -0
  54. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/glob/definition.py +0 -0
  55. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/glob/impl.py +0 -0
  56. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/grep/__init__.py +0 -0
  57. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/grep/definition.py +0 -0
  58. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/grep/impl.py +0 -0
  59. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/planning_file_editor/__init__.py +0 -0
  60. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/planning_file_editor/definition.py +0 -0
  61. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/planning_file_editor/impl.py +0 -0
  62. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/gemini.py +0 -0
  63. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/gpt5.py +0 -0
  64. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/planning.py +0 -0
  65. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/subagents/bash_runner.md +0 -0
  66. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/subagents/code_explorer.md +0 -0
  67. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/subagents/default.md +0 -0
  68. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/preset/subagents/web_researcher.md +0 -0
  69. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/py.typed +0 -0
  70. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/task/__init__.py +0 -0
  71. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/task/definition.py +0 -0
  72. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/task/impl.py +0 -0
  73. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/task/manager.py +0 -0
  74. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/task_tracker/__init__.py +0 -0
  75. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/task_tracker/definition.py +0 -0
  76. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/__init__.py +0 -0
  77. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/constants.py +0 -0
  78. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/definition.py +0 -0
  79. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/descriptions.py +0 -0
  80. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/env.py +0 -0
  81. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/impl.py +0 -0
  82. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/metadata.py +0 -0
  83. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/__init__.py +0 -0
  84. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/factory.py +0 -0
  85. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/interface.py +0 -0
  86. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/subprocess_terminal.py +0 -0
  87. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/terminal_session.py +0 -0
  88. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/tmux_pane_pool.py +0 -0
  89. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/tmux_terminal.py +0 -0
  90. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/terminal/windows_terminal.py +0 -0
  91. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/timeout_policy.py +0 -0
  92. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/utils/__init__.py +0 -0
  93. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/utils/command.py +0 -0
  94. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/terminal/utils/escape_filter.py +0 -0
  95. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/tom_consult/__init__.py +0 -0
  96. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/tom_consult/definition.py +0 -0
  97. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/tom_consult/executor.py +0 -0
  98. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/utils/__init__.py +0 -0
  99. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/utils/timeout.py +0 -0
  100. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/workflow/__init__.py +0 -0
  101. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/workflow/definition.py +0 -0
  102. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands/tools/workflow/impl.py +0 -0
  103. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands_tools.egg-info/dependency_links.txt +0 -0
  104. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands_tools.egg-info/requires.txt +0 -0
  105. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/openhands_tools.egg-info/top_level.txt +0 -0
  106. {openhands_tools-1.43.0 → openhands_tools-1.44.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-tools
3
- Version: 1.43.0
3
+ Version: 1.44.0
4
4
  Summary: OpenHands Tools - Runtime tools for AI agents
5
5
  Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
6
  Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
@@ -18,6 +18,7 @@ from ``openhands.tools.browser_use``.
18
18
 
19
19
  from importlib.metadata import PackageNotFoundError, version
20
20
 
21
+ from openhands.tools.ask_oracle import AskOracleTool
21
22
  from openhands.tools.delegate import DelegationVisualizer
22
23
  from openhands.tools.file_editor import FileEditorTool
23
24
  from openhands.tools.preset.default import (
@@ -40,6 +41,7 @@ except PackageNotFoundError:
40
41
 
41
42
  __all__ = [
42
43
  "__version__",
44
+ "AskOracleTool",
43
45
  "DelegationVisualizer",
44
46
  "FileEditorTool",
45
47
  "TaskToolSet",
@@ -0,0 +1,36 @@
1
+ """Ask-Oracle tool package.
2
+
3
+ Provides a read-only ``ask_oracle`` tool: a one-shot, tool-less sub-agent that
4
+ consults a stronger/more-capable LLM for a second opinion. The Oracle model is a
5
+ saved LLM profile, resolved by convention under the name ``oracle`` (see
6
+ ``ORACLE_PROFILE_NAME``).
7
+
8
+ Usage:
9
+ from openhands.tools.ask_oracle import AskOracleTool
10
+
11
+ agent = Agent(
12
+ llm=llm,
13
+ tools=[Tool(name=AskOracleTool.name)],
14
+ )
15
+
16
+ The agent's active conversation LLM is never switched. The Oracle call sends only
17
+ the Oracle system prompt plus the agent's question and optional context, without
18
+ forwarding conversation history or tools.
19
+ """
20
+
21
+ from openhands.tools.ask_oracle.definition import (
22
+ ORACLE_PROFILE_NAME,
23
+ AskOracleAction,
24
+ AskOracleObservation,
25
+ AskOracleTool,
26
+ )
27
+ from openhands.tools.ask_oracle.impl import AskOracleExecutor
28
+
29
+
30
+ __all__ = [
31
+ "ORACLE_PROFILE_NAME",
32
+ "AskOracleAction",
33
+ "AskOracleObservation",
34
+ "AskOracleExecutor",
35
+ "AskOracleTool",
36
+ ]
@@ -0,0 +1,114 @@
1
+ """Action, observation, and tool definitions for the ask_oracle tool."""
2
+
3
+ from collections.abc import Sequence
4
+ from typing import TYPE_CHECKING, Final, Self
5
+
6
+ from pydantic import Field
7
+ from rich.text import Text
8
+
9
+ from openhands.sdk.tool.registry import register_tool
10
+ from openhands.sdk.tool.tool import (
11
+ Action,
12
+ Observation,
13
+ ToolAnnotations,
14
+ ToolDefinition,
15
+ )
16
+
17
+
18
+ if TYPE_CHECKING:
19
+ from openhands.sdk.conversation.state import ConversationState
20
+
21
+
22
+ # The Oracle model is a saved LLM profile resolved by convention under this
23
+ # name. Save a profile named "oracle" (e.g. via LLMProfileStore.save("oracle",
24
+ # llm)) and the tool will consult it. No agent setting or wiring is required.
25
+ ORACLE_PROFILE_NAME: Final[str] = "oracle"
26
+
27
+
28
+ class AskOracleAction(Action):
29
+ """Action for asking the Oracle for advice."""
30
+
31
+ question: str = Field(
32
+ description=(
33
+ "The specific question or dilemma to ask the Oracle about. Use this "
34
+ "when you are stuck, uncertain, or need a second opinion."
35
+ )
36
+ )
37
+ context: str | None = Field(
38
+ default=None,
39
+ description=(
40
+ "Optional extra context, such as approaches already tried, constraints, "
41
+ "or the recommendation you are considering."
42
+ ),
43
+ )
44
+
45
+ @property
46
+ def visualize(self) -> Text:
47
+ content = Text()
48
+ content.append("Ask Oracle: ", style="bold cyan")
49
+ content.append(self.question)
50
+ if self.context:
51
+ content.append("\nContext: ", style="bold")
52
+ content.append(self.context)
53
+ return content
54
+
55
+
56
+ class AskOracleObservation(Observation):
57
+ """Observation returned by the Oracle consultation."""
58
+
59
+ @property
60
+ def visualize(self) -> Text:
61
+ content = Text()
62
+ if self.is_error:
63
+ content.append("Oracle consultation failed", style="bold red")
64
+ else:
65
+ content.append("Oracle recommendation", style="bold green")
66
+ if self.text:
67
+ content.append("\n")
68
+ content.append(self.text)
69
+ return content
70
+
71
+
72
+ _DESCRIPTION = (
73
+ "Ask the Oracle for a second opinion. The Oracle is a smart model intended "
74
+ "to help with difficult reasoning.\n\n"
75
+ "Use this when you are stuck, uncertain, comparing approaches, or need a "
76
+ "higher-quality recommendation before proceeding.\n\n"
77
+ "Treat the Oracle's response as strong guidance and follow its recommendation "
78
+ "unless you have a clear reason not to."
79
+ )
80
+
81
+
82
+ class AskOracleTool(ToolDefinition[AskOracleAction, AskOracleObservation]):
83
+ """Tool for consulting the Oracle (a saved LLM profile named "oracle")."""
84
+
85
+ @classmethod
86
+ def create(
87
+ cls,
88
+ conv_state: "ConversationState | None" = None, # noqa: ARG003
89
+ **params,
90
+ ) -> Sequence[Self]:
91
+ if params:
92
+ raise ValueError("AskOracleTool does not accept parameters")
93
+
94
+ # Import here to keep module import light and avoid any import cycles.
95
+ from openhands.tools.ask_oracle.impl import AskOracleExecutor
96
+
97
+ return [
98
+ cls(
99
+ description=_DESCRIPTION,
100
+ action_type=AskOracleAction,
101
+ observation_type=AskOracleObservation,
102
+ executor=AskOracleExecutor(),
103
+ annotations=ToolAnnotations(
104
+ readOnlyHint=True,
105
+ destructiveHint=False,
106
+ idempotentHint=False,
107
+ openWorldHint=True,
108
+ ),
109
+ )
110
+ ]
111
+
112
+
113
+ # Automatically register when this module is imported.
114
+ register_tool(AskOracleTool.name, AskOracleTool)
@@ -0,0 +1,120 @@
1
+ """Executor for the ask_oracle tool."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from openhands.sdk.agent.utils import make_llm_completion
6
+ from openhands.sdk.llm import Message, TextContent
7
+ from openhands.sdk.tool.tool import ToolExecutor
8
+ from openhands.tools.ask_oracle.definition import (
9
+ ORACLE_PROFILE_NAME,
10
+ AskOracleAction,
11
+ AskOracleObservation,
12
+ )
13
+
14
+
15
+ if TYPE_CHECKING:
16
+ from openhands.sdk.conversation.impl.local_conversation import LocalConversation
17
+
18
+
19
+ ORACLE_USAGE_PREFIX = "oracle"
20
+
21
+
22
+ _ORACLE_SYSTEM_PROMPT = """\
23
+ You are the Oracle: a highly capable reviewer giving a second opinion to an \
24
+ OpenHands agent.
25
+
26
+ Answer the agent's question directly. Do not call tools. Do not perform work \
27
+ directly. Give a concrete recommendation the agent can follow, including important \
28
+ risks or caveats."""
29
+
30
+ _ORACLE_USER_PROMPT_TEMPLATE = """\
31
+ Question:
32
+ {question}
33
+ {context_section}"""
34
+
35
+
36
+ class AskOracleExecutor(ToolExecutor[AskOracleAction, AskOracleObservation]):
37
+ """Consult the Oracle: a saved LLM profile named ``oracle``.
38
+
39
+ The Oracle is resolved by convention from the conversation's LLM profile
40
+ store under the name ``oracle`` (``ORACLE_PROFILE_NAME``). The call is
41
+ stateless: it sends only the Oracle system prompt plus the agent's question
42
+ and optional context, with no conversation history and no tools, and returns
43
+ the Oracle's text. The active conversation LLM is never switched.
44
+ """
45
+
46
+ def __call__(
47
+ self,
48
+ action: AskOracleAction,
49
+ conversation: "LocalConversation | None" = None,
50
+ ) -> AskOracleObservation:
51
+ if conversation is None:
52
+ return AskOracleObservation.from_text(
53
+ text="Cannot ask the Oracle without an active conversation.",
54
+ is_error=True,
55
+ )
56
+
57
+ try:
58
+ oracle_llm = conversation.get_or_create_profile_llm(
59
+ profile_name=ORACLE_PROFILE_NAME,
60
+ usage_id=f"{ORACLE_USAGE_PREFIX}:{ORACLE_PROFILE_NAME}",
61
+ )
62
+ except FileNotFoundError:
63
+ return AskOracleObservation.from_text(
64
+ text=(
65
+ "The Oracle is not available because no profile named "
66
+ f"'{ORACLE_PROFILE_NAME}' was found. Save one to enable it."
67
+ ),
68
+ is_error=True,
69
+ )
70
+ except ValueError as exc:
71
+ return AskOracleObservation.from_text(
72
+ text=f"The Oracle is not available: {exc}",
73
+ is_error=True,
74
+ )
75
+ except Exception as exc:
76
+ return AskOracleObservation.from_text(
77
+ text=f"The Oracle is not available: {type(exc).__name__}: {exc}",
78
+ is_error=True,
79
+ )
80
+
81
+ context_section = (
82
+ f"\nAdditional context from the agent:\n{action.context}\n"
83
+ if action.context
84
+ else ""
85
+ )
86
+ user_prompt = _ORACLE_USER_PROMPT_TEMPLATE.format(
87
+ question=action.question,
88
+ context_section=context_section,
89
+ )
90
+ messages = [
91
+ Message(
92
+ role="system",
93
+ content=[TextContent(text=_ORACLE_SYSTEM_PROMPT)],
94
+ ),
95
+ Message(role="user", content=[TextContent(text=user_prompt)]),
96
+ ]
97
+
98
+ try:
99
+ llm_response = make_llm_completion(oracle_llm, messages)
100
+ except Exception as exc:
101
+ return AskOracleObservation.from_text(
102
+ text=(
103
+ "The Oracle encountered an error and did not return a "
104
+ f"response: {type(exc).__name__}: {exc}"
105
+ ),
106
+ is_error=True,
107
+ )
108
+
109
+ oracle_text = "".join(
110
+ content.text
111
+ for content in llm_response.message.content
112
+ if isinstance(content, TextContent)
113
+ ).strip()
114
+ if not oracle_text:
115
+ return AskOracleObservation.from_text(
116
+ text="The Oracle did not return a response.",
117
+ is_error=True,
118
+ )
119
+
120
+ return AskOracleObservation.from_text(text=oracle_text)
@@ -13,6 +13,7 @@ import threading
13
13
  from collections.abc import Callable, Coroutine
14
14
  from pathlib import Path
15
15
  from typing import TYPE_CHECKING, Any, Final, TypeVar
16
+ from uuid import uuid4
16
17
 
17
18
 
18
19
  if TYPE_CHECKING:
@@ -356,6 +357,9 @@ class BrowserToolExecutor(ToolExecutor[BrowserAction, BrowserObservation]):
356
357
  "allowed_domains": allowed_domains or [],
357
358
  "executable_path": executable_path,
358
359
  "chromium_sandbox": not running_as_root,
360
+ "user_data_dir": str(
361
+ Path.home() / ".config" / "browseruse" / "profiles" / uuid4().hex
362
+ ),
359
363
  **config,
360
364
  }
361
365
 
@@ -692,6 +696,14 @@ class BrowserToolExecutor(ToolExecutor[BrowserAction, BrowserObservation]):
692
696
  except Exception as e:
693
697
  logger.warning(f"Error during browser cleanup: {e}")
694
698
  finally:
699
+ # Remove the browser profile directory to avoid disk accumulation.
700
+ # browser_use doesn't clean up the user_data_dir on shutdown.
701
+ user_data_dir = self._config.get("user_data_dir")
702
+ if user_data_dir and "browseruse/profiles/" in user_data_dir:
703
+ try:
704
+ shutil.rmtree(user_data_dir, ignore_errors=True)
705
+ except Exception:
706
+ pass
695
707
  try:
696
708
  # Always close the async executor
697
709
  self._async_executor.close()
@@ -26,6 +26,10 @@ from .default import (
26
26
  from .gemini import get_gemini_agent, get_gemini_tools
27
27
  from .gpt5 import get_gpt5_agent
28
28
  from .planning import get_planning_agent
29
+ from .task_outcome import (
30
+ TaskOutcome,
31
+ TaskOutcomeStatus,
32
+ )
29
33
 
30
34
 
31
35
  __all__ = [
@@ -36,4 +40,6 @@ __all__ = [
36
40
  "get_gpt5_agent",
37
41
  "get_planning_agent",
38
42
  "register_builtins_agents",
43
+ "TaskOutcome",
44
+ "TaskOutcomeStatus",
39
45
  ]
@@ -1,6 +1,7 @@
1
1
  """Default preset configuration for OpenHands agents."""
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Any
4
5
 
5
6
  from openhands.sdk import Agent, agent_definition_to_factory, load_agents_from_dir
6
7
  from openhands.sdk.context.condenser import default_condenser
@@ -9,6 +10,8 @@ from openhands.sdk.llm.llm import LLM
9
10
  from openhands.sdk.logger import get_logger
10
11
  from openhands.sdk.subagent import AgentDefinition, register_agent_if_absent
11
12
  from openhands.sdk.tool import Tool
13
+ from openhands.sdk.tool.builtins import BUILT_IN_TOOLS, FinishTool
14
+ from openhands.sdk.tool.registry import list_registered_tools, register_tool
12
15
 
13
16
 
14
17
  logger = get_logger(__name__)
@@ -73,11 +76,26 @@ def get_default_condenser(llm: LLM) -> CondenserBase:
73
76
  def get_default_agent(
74
77
  llm: LLM,
75
78
  cli_mode: bool = False,
79
+ finish_tool_response_schema: Any | None = None,
76
80
  ) -> Agent:
77
81
  tools = get_default_tools(
78
82
  # Disable browser tools in CLI mode
79
83
  enable_browser=not cli_mode,
80
84
  )
85
+ agent_kwargs: dict[str, Any] = {}
86
+ if finish_tool_response_schema is not None:
87
+ if FinishTool.__name__ not in list_registered_tools():
88
+ register_tool(FinishTool.__name__, FinishTool)
89
+ tools.append(
90
+ Tool(
91
+ name=FinishTool.__name__,
92
+ params={"response_schema": finish_tool_response_schema},
93
+ )
94
+ )
95
+ agent_kwargs["include_default_tools"] = [
96
+ tool.__name__ for tool in BUILT_IN_TOOLS if tool is not FinishTool
97
+ ]
98
+
81
99
  agent = Agent(
82
100
  llm=llm,
83
101
  tools=tools,
@@ -85,6 +103,7 @@ def get_default_agent(
85
103
  condenser=get_default_condenser(
86
104
  llm=llm.model_copy(update={"usage_id": "condenser"})
87
105
  ),
106
+ **agent_kwargs,
88
107
  )
89
108
  return agent
90
109
 
@@ -0,0 +1,35 @@
1
+ """Experimental structured task outcome models for conversation finish actions."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ TaskOutcomeStatus = Literal[
9
+ "success",
10
+ "partial_success",
11
+ "blocked",
12
+ "failed",
13
+ "unknown",
14
+ ]
15
+
16
+
17
+ class TaskOutcome(BaseModel):
18
+ """Latest semantic outcome reported for a conversation task.
19
+
20
+ Experimental: this structured response model may change as task outcome
21
+ reporting is refined.
22
+ """
23
+
24
+ model_config = ConfigDict(populate_by_name=True)
25
+
26
+ status: TaskOutcomeStatus = Field(
27
+ description="Agent's semantic assessment of task completion."
28
+ )
29
+ summary: str = Field(
30
+ alias="outcome_summary",
31
+ description=(
32
+ "Concise outcome summary. Include what was completed, blockers, "
33
+ "required user action, and relevant next steps."
34
+ ),
35
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-tools
3
- Version: 1.43.0
3
+ Version: 1.44.0
4
4
  Summary: OpenHands Tools - Runtime tools for AI agents
5
5
  Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
6
  Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
@@ -4,6 +4,9 @@ pyproject.toml
4
4
  ./openhands/tools/apply_patch/__init__.py
5
5
  ./openhands/tools/apply_patch/core.py
6
6
  ./openhands/tools/apply_patch/definition.py
7
+ ./openhands/tools/ask_oracle/__init__.py
8
+ ./openhands/tools/ask_oracle/definition.py
9
+ ./openhands/tools/ask_oracle/impl.py
7
10
  ./openhands/tools/browser_use/__init__.py
8
11
  ./openhands/tools/browser_use/definition.py
9
12
  ./openhands/tools/browser_use/event_storage.py
@@ -56,6 +59,7 @@ pyproject.toml
56
59
  ./openhands/tools/preset/gemini.py
57
60
  ./openhands/tools/preset/gpt5.py
58
61
  ./openhands/tools/preset/planning.py
62
+ ./openhands/tools/preset/task_outcome.py
59
63
  ./openhands/tools/preset/subagents/bash_runner.md
60
64
  ./openhands/tools/preset/subagents/code_explorer.md
61
65
  ./openhands/tools/preset/subagents/default.md
@@ -98,6 +102,9 @@ openhands/tools/py.typed
98
102
  openhands/tools/apply_patch/__init__.py
99
103
  openhands/tools/apply_patch/core.py
100
104
  openhands/tools/apply_patch/definition.py
105
+ openhands/tools/ask_oracle/__init__.py
106
+ openhands/tools/ask_oracle/definition.py
107
+ openhands/tools/ask_oracle/impl.py
101
108
  openhands/tools/browser_use/__init__.py
102
109
  openhands/tools/browser_use/definition.py
103
110
  openhands/tools/browser_use/event_storage.py
@@ -150,6 +157,7 @@ openhands/tools/preset/default.py
150
157
  openhands/tools/preset/gemini.py
151
158
  openhands/tools/preset/gpt5.py
152
159
  openhands/tools/preset/planning.py
160
+ openhands/tools/preset/task_outcome.py
153
161
  openhands/tools/preset/subagents/bash_runner.md
154
162
  openhands/tools/preset/subagents/code_explorer.md
155
163
  openhands/tools/preset/subagents/default.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhands-tools"
3
- version = "1.43.0"
3
+ version = "1.44.0"
4
4
  description = "OpenHands Tools - Runtime tools for AI agents"
5
5
 
6
6
  requires-python = ">=3.12"