From 7c1da85d4ecaf2bf35ed9e024098ae673353057a Mon Sep 17 00:00:00 2001 From: xingyaoww <38853559+xingyaoww@users.noreply.github.com> Date: Thu, 6 Nov 2025 03:09:05 +0000 Subject: [PATCH] docs: sync code blocks from agent-sdk examples Synced from agent-sdk ref: main --- sdk/getting-started.mdx | 37 +++++------ sdk/guides/agent-browser-use.mdx | 12 ++-- sdk/guides/agent-interactive-terminal.mdx | 5 +- sdk/guides/agent-server/api-sandbox.mdx | 1 - sdk/guides/agent-server/docker-sandbox.mdx | 43 ++++++++----- sdk/guides/agent-server/local-server.mdx | 5 +- sdk/guides/context-condenser.mdx | 13 ++-- sdk/guides/convo-async.mdx | 11 ++-- sdk/guides/convo-pause-and-resume.mdx | 18 +++--- sdk/guides/convo-persistence.mdx | 8 +-- .../convo-send-message-while-running.mdx | 8 +-- sdk/guides/custom-tools.mdx | 62 ++++++++++++++----- sdk/guides/hello-world.mdx | 37 +++++------ sdk/guides/llm-image-input.mdx | 12 +--- sdk/guides/llm-reasoning.mdx | 6 +- sdk/guides/llm-registry.mdx | 5 +- sdk/guides/llm-routing.mdx | 1 - sdk/guides/mcp.mdx | 16 ++--- sdk/guides/metrics.mdx | 25 +++----- sdk/guides/secrets.mdx | 8 +-- sdk/guides/security.mdx | 34 +++++----- sdk/guides/skill.mdx | 10 +-- 22 files changed, 180 insertions(+), 197 deletions(-) diff --git a/sdk/getting-started.mdx b/sdk/getting-started.mdx index aa60f225..a706b53b 100644 --- a/sdk/getting-started.mdx +++ b/sdk/getting-started.mdx @@ -74,37 +74,32 @@ Here's a complete example that creates an agent and asks it to perform a simple ```python icon="python" expandable examples/01_standalone_sdk/01_hello_world.py import os -from pydantic import SecretStr +from openhands.sdk import LLM, Agent, Conversation, Tool +from openhands.tools.execute_bash import BashTool +from openhands.tools.file_editor import FileEditorTool +from openhands.tools.task_tracker import TaskTrackerTool -from openhands.sdk import LLM, Conversation -from openhands.tools.preset.default import get_default_agent - -# Configure LLM and agent -# You can get an API key from https://app.all-hands.dev/settings/api-keys -api_key = os.getenv("LLM_API_KEY") -assert api_key is not None, "LLM_API_KEY environment variable is not set." -model = os.getenv("LLM_MODEL", "openhands/claude-sonnet-4-5-20250929") -base_url = os.getenv("LLM_BASE_URL") llm = LLM( - model=model, - api_key=SecretStr(api_key), - base_url=base_url, - usage_id="agent", + model="anthropic/claude-sonnet-4-5-20250929", + api_key=os.getenv("LLM_API_KEY"), +) + +agent = Agent( + llm=llm, + tools=[ + Tool(name=BashTool.name), + Tool(name=FileEditorTool.name), + Tool(name=TaskTrackerTool.name), + ], ) -agent = get_default_agent(llm=llm, cli_mode=True) -# Start a conversation and send some messages cwd = os.getcwd() conversation = Conversation(agent=agent, workspace=cwd) -# Send a message and let the agent run conversation.send_message("Write 3 facts about the current project into FACTS.txt.") conversation.run() - -# Report cost -cost = llm.metrics.accumulated_cost -print(f"EXAMPLE_COST: {cost}") +print("All done!") ``` Run the example: diff --git a/sdk/guides/agent-browser-use.mdx b/sdk/guides/agent-browser-use.mdx index 8c07e8d1..3a16a5e3 100644 --- a/sdk/guides/agent-browser-use.mdx +++ b/sdk/guides/agent-browser-use.mdx @@ -22,7 +22,7 @@ from openhands.sdk import ( LLMConvertibleEvent, get_logger, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.browser_use import BrowserToolSet from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -44,15 +44,12 @@ llm = LLM( # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) -register_tool("BrowserToolSet", BrowserToolSet) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), - Tool(name="BrowserToolSet"), + Tool(name=FileEditorTool.name), + Tool(name=BrowserToolSet.name), ] # If you need fine-grained browser control, you can manually register individual browser @@ -80,7 +77,6 @@ conversation.send_message( ) conversation.run() - print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): diff --git a/sdk/guides/agent-interactive-terminal.mdx b/sdk/guides/agent-interactive-terminal.mdx index 73c8c8ab..26c8e9b1 100644 --- a/sdk/guides/agent-interactive-terminal.mdx +++ b/sdk/guides/agent-interactive-terminal.mdx @@ -23,7 +23,7 @@ from openhands.sdk import ( LLMConvertibleEvent, get_logger, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool @@ -43,10 +43,9 @@ llm = LLM( # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, params={"no_change_timeout_seconds": 3}, ) ] diff --git a/sdk/guides/agent-server/api-sandbox.mdx b/sdk/guides/agent-server/api-sandbox.mdx index ad0c0869..d6434de5 100644 --- a/sdk/guides/agent-server/api-sandbox.mdx +++ b/sdk/guides/agent-server/api-sandbox.mdx @@ -60,7 +60,6 @@ if not runtime_api_key: logger.error("RUNTIME_API_KEY required") exit(1) - with APIRemoteWorkspace( runtime_api_url=os.getenv("RUNTIME_API_URL", "https://runtime.eval.all-hands.dev"), runtime_api_key=runtime_api_key, diff --git a/sdk/guides/agent-server/docker-sandbox.mdx b/sdk/guides/agent-server/docker-sandbox.mdx index 6e2d53d5..7c3de889 100644 --- a/sdk/guides/agent-server/docker-sandbox.mdx +++ b/sdk/guides/agent-server/docker-sandbox.mdx @@ -36,7 +36,6 @@ from openhands.workspace import DockerWorkspace logger = get_logger(__name__) - # 1) Ensure we have LLM API key api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." @@ -61,9 +60,9 @@ def detect_platform(): # the Docker container automatically with DockerWorkspace( # dynamically build agent-server image - # base_image="nikolaik/python-nodejs:python3.12-nodejs22", + base_image="nikolaik/python-nodejs:python3.12-nodejs22", # use pre-built image for faster startup - server_image="ghcr.io/openhands/agent-server:main-python", + # server_image="ghcr.io/openhands/agent-server:main-python", host_port=8010, platform=detect_platform(), ) as workspace: @@ -109,7 +108,7 @@ with DockerWorkspace( logger.info("🚀 Running conversation...") conversation.run() logger.info("✅ First task completed!") - logger.info(f"Agent status: {conversation.state.agent_status}") + logger.info(f"Agent status: {conversation.state.execution_status}") # Wait for events to settle (no events for 2 seconds) logger.info("⏳ Waiting for events to stop...") @@ -123,6 +122,9 @@ with DockerWorkspace( logger.info("✅ Second task completed!") # Report cost (must be before conversation.close()) + conversation.state._cached_state = ( + None # Invalidate cache to fetch latest stats + ) cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost print(f"EXAMPLE_COST: {cost}") finally: @@ -255,7 +257,6 @@ from openhands.workspace import DockerWorkspace logger = get_logger(__name__) - api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." @@ -447,7 +448,6 @@ from openhands.workspace import DockerWorkspace logger = get_logger(__name__) - api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." @@ -470,7 +470,7 @@ def detect_platform(): # Create a Docker-based remote workspace with extra ports for browser access with DockerWorkspace( base_image="nikolaik/python-nodejs:python3.12-nodejs22", - host_port=8010, + host_port=8011, platform=detect_platform(), extra_ports=True, # Expose extra ports for VSCode and VNC ) as workspace: @@ -509,17 +509,26 @@ with DockerWorkspace( ) conversation.run() - # Wait for user confirm to exit - y = None - while y != "y": - y = input( - "Because you've enabled extra_ports=True in DockerWorkspace, " - "you can open a browser tab to see the *actual* browser OpenHands " - "is interacting with via VNC.\n\n" - "Link: http://localhost:8012/vnc.html?autoconnect=1&resize=remote\n\n" - "Press 'y' and Enter to exit and terminate the workspace.\n" - ">> " + conversation.state._cached_state = None # Invalidate cache to fetch latest stats + cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost + print(f"EXAMPLE_COST: {cost}") + + if os.getenv("CI"): + logger.info( + "CI environment detected; skipping interactive prompt and closing workspace." # noqa: E501 ) + else: + # Wait for user confirm to exit when running locally + y = None + while y != "y": + y = input( + "Because you've enabled extra_ports=True in DockerWorkspace, " + "you can open a browser tab to see the *actual* browser OpenHands " + "is interacting with via VNC.\n\n" + "Link: http://localhost:8012/vnc.html?autoconnect=1&resize=remote\n\n" + "Press 'y' and Enter to exit and terminate the workspace.\n" + ">> " + ) ``` ```bash Running the Example diff --git a/sdk/guides/agent-server/local-server.mdx b/sdk/guides/agent-server/local-server.mdx index 00cbc26e..b582acca 100644 --- a/sdk/guides/agent-server/local-server.mdx +++ b/sdk/guides/agent-server/local-server.mdx @@ -206,7 +206,7 @@ with ManagedAPIServer(port=8001) as server: conversation.run() logger.info("✅ First task completed!") - logger.info(f"Agent status: {conversation.state.agent_status}") + logger.info(f"Agent status: {conversation.state.execution_status}") # Wait for events to stop coming (no events for 2 seconds) logger.info("⏳ Waiting for events to stop...") @@ -254,6 +254,9 @@ with ManagedAPIServer(port=8001) as server: logger.info(f" - {event}") # Report cost (must be before conversation.close()) + conversation.state._cached_state = ( + None # Invalidate cache to fetch latest stats + ) cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost print(f"EXAMPLE_COST: {cost}") diff --git a/sdk/guides/context-condenser.mdx b/sdk/guides/context-condenser.mdx index d0a9f978..991921cf 100644 --- a/sdk/guides/context-condenser.mdx +++ b/sdk/guides/context-condenser.mdx @@ -76,7 +76,7 @@ from openhands.sdk import ( get_logger, ) from openhands.sdk.context.condenser import LLMSummarizingCondenser -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool from openhands.tools.task_tracker import TaskTrackerTool @@ -98,15 +98,12 @@ llm = LLM( # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) -register_tool("TaskTrackerTool", TaskTrackerTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), - Tool(name="TaskTrackerTool"), + Tool(name=FileEditorTool.name), + Tool(name=TaskTrackerTool.name), ] # Create a condenser to manage the context. The condenser will automatically truncate @@ -163,7 +160,6 @@ conversation.send_message( ) conversation.run() - print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): @@ -187,7 +183,6 @@ print("Sending message to deserialized conversation...") conversation.send_message("Finally, clean up by deleting both files.") conversation.run() - print("=" * 100) print("Conversation finished with LLM Summarizing Condenser.") print(f"Total LLM messages collected: {len(llm_messages)}") diff --git a/sdk/guides/convo-async.mdx b/sdk/guides/convo-async.mdx index bf077cd6..531804d1 100644 --- a/sdk/guides/convo-async.mdx +++ b/sdk/guides/convo-async.mdx @@ -30,7 +30,7 @@ from openhands.sdk import ( get_logger, ) from openhands.sdk.conversation.types import ConversationCallbackType -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.sdk.utils.async_utils import AsyncCallbackWrapper from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -53,15 +53,12 @@ llm = LLM( # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) -register_tool("TaskTrackerTool", TaskTrackerTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), - Tool(name="TaskTrackerTool"), + Tool(name=FileEditorTool.name), + Tool(name=TaskTrackerTool.name), ] # Agent diff --git a/sdk/guides/convo-pause-and-resume.mdx b/sdk/guides/convo-pause-and-resume.mdx index dd84e86c..007144d9 100644 --- a/sdk/guides/convo-pause-and-resume.mdx +++ b/sdk/guides/convo-pause-and-resume.mdx @@ -22,7 +22,7 @@ from openhands.sdk import ( Agent, Conversation, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -40,20 +40,17 @@ llm = LLM( ) # Tools -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), + Tool(name=FileEditorTool.name), ] # Agent agent = Agent(llm=llm, tools=tools) conversation = Conversation(agent, workspace=os.getcwd()) - print("=" * 60) print("Pause and Continue Example") print("=" * 60) @@ -66,7 +63,7 @@ conversation.send_message( "one number per line. After you finish, summarize what you did." ) -print(f"Initial status: {conversation.state.agent_status}") +print(f"Initial status: {conversation.state.execution_status}") print() # Start the agent in a background thread @@ -85,10 +82,9 @@ conversation.pause() # Wait for the thread to finish (it will stop when paused) thread.join() -print(f"Agent status after pause: {conversation.state.agent_status}") +print(f"Agent status after pause: {conversation.state.execution_status}") print() - # Phase 3: Send a new message while paused print("Phase 3: Sending a new message while agent is paused...") conversation.send_message( @@ -99,12 +95,12 @@ print() # Phase 4: Resume the agent with .run() print("Phase 4: Resuming agent with .run()...") -print(f"Status before resume: {conversation.state.agent_status}") +print(f"Status before resume: {conversation.state.execution_status}") # Resume execution conversation.run() -print(f"Final status: {conversation.state.agent_status}") +print(f"Final status: {conversation.state.execution_status}") # Report cost cost = llm.metrics.accumulated_cost diff --git a/sdk/guides/convo-persistence.mdx b/sdk/guides/convo-persistence.mdx index c7ec8c51..99d95811 100644 --- a/sdk/guides/convo-persistence.mdx +++ b/sdk/guides/convo-persistence.mdx @@ -23,7 +23,7 @@ from openhands.sdk import ( LLMConvertibleEvent, get_logger, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -44,11 +44,9 @@ llm = LLM( # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ - Tool(name="BashTool"), - Tool(name="FileEditorTool"), + Tool(name=BashTool.name), + Tool(name=FileEditorTool.name), ] # Add MCP Tools diff --git a/sdk/guides/convo-send-message-while-running.mdx b/sdk/guides/convo-send-message-while-running.mdx index a3613645..1cc7cc6c 100644 --- a/sdk/guides/convo-send-message-while-running.mdx +++ b/sdk/guides/convo-send-message-while-running.mdx @@ -62,7 +62,7 @@ from openhands.sdk import ( Agent, Conversation, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -81,13 +81,11 @@ llm = LLM( # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), + Tool(name=FileEditorTool.name), ] # Agent diff --git a/sdk/guides/custom-tools.mdx b/sdk/guides/custom-tools.mdx index 6ca26ff6..58efa1b6 100644 --- a/sdk/guides/custom-tools.mdx +++ b/sdk/guides/custom-tools.mdx @@ -68,15 +68,14 @@ from openhands.sdk.tool import ( ) from openhands.tools.execute_bash import ( BashExecutor, + BashTool, ExecuteBashAction, - execute_bash_tool, ) from openhands.tools.file_editor import FileEditorTool logger = get_logger(__name__) - # --- Action / Observation --- @@ -135,8 +134,10 @@ class GrepExecutor(ToolExecutor[GrepAction, GrepObservation]): files: set[str] = set() # grep returns exit code 1 when no matches; treat as empty - if result.output.strip(): - for line in result.output.strip().splitlines(): + output_text = result.text + + if output_text.strip(): + for line in output_text.strip().splitlines(): matches.append(line) # Expect "path:line:content" — take the file part before first ":" file_path = line.split(":", 1)[0] @@ -157,6 +158,41 @@ _GREP_DESCRIPTION = """Fast content search tool. * When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead """ # noqa: E501 + +# --- Tool Definition --- + + +class GrepTool(ToolDefinition[GrepAction, GrepObservation]): + """A custom grep tool that searches file contents using regular expressions.""" + + @classmethod + def create( + cls, conv_state, bash_executor: BashExecutor | None = None + ) -> Sequence[ToolDefinition]: + """Create GrepTool instance with a GrepExecutor. + + Args: + conv_state: Conversation state to get working directory from. + bash_executor: Optional bash executor to reuse. If not provided, + a new one will be created. + + Returns: + A sequence containing a single GrepTool instance. + """ + if bash_executor is None: + bash_executor = BashExecutor(working_dir=conv_state.workspace.working_dir) + grep_executor = GrepExecutor(bash_executor) + + return [ + cls( + description=_GREP_DESCRIPTION, + action_type=GrepAction, + observation_type=GrepObservation, + executor=grep_executor, + ) + ] + + # Configure LLM api_key = os.getenv("LLM_API_KEY") assert api_key is not None, "LLM_API_KEY environment variable is not set." @@ -177,25 +213,19 @@ def _make_bash_and_grep_tools(conv_state) -> list[ToolDefinition]: """Create execute_bash and custom grep tools sharing one executor.""" bash_executor = BashExecutor(working_dir=conv_state.workspace.working_dir) - bash_tool = execute_bash_tool.set_executor(executor=bash_executor) - - grep_executor = GrepExecutor(bash_executor) - grep_tool = ToolDefinition( - name="grep", - description=_GREP_DESCRIPTION, - action_type=GrepAction, - observation_type=GrepObservation, - executor=grep_executor, - ) + # bash_tool = execute_bash_tool.set_executor(executor=bash_executor) + bash_tool = BashTool.create(conv_state, executor=bash_executor)[0] + + # Use the GrepTool.create() method with shared bash_executor + grep_tool = GrepTool.create(conv_state, bash_executor=bash_executor)[0] return [bash_tool, grep_tool] -register_tool("FileEditorTool", FileEditorTool) register_tool("BashAndGrepToolSet", _make_bash_and_grep_tools) tools = [ - Tool(name="FileEditorTool"), + Tool(name=FileEditorTool.name), Tool(name="BashAndGrepToolSet"), ] diff --git a/sdk/guides/hello-world.mdx b/sdk/guides/hello-world.mdx index 200fbacb..ca1ef62c 100644 --- a/sdk/guides/hello-world.mdx +++ b/sdk/guides/hello-world.mdx @@ -12,37 +12,32 @@ This is the most basic example showing how to set up and run an OpenHands agent: ```python icon="python" examples/01_standalone_sdk/01_hello_world.py import os -from pydantic import SecretStr +from openhands.sdk import LLM, Agent, Conversation, Tool +from openhands.tools.execute_bash import BashTool +from openhands.tools.file_editor import FileEditorTool +from openhands.tools.task_tracker import TaskTrackerTool -from openhands.sdk import LLM, Conversation -from openhands.tools.preset.default import get_default_agent - -# Configure LLM and agent -# You can get an API key from https://app.all-hands.dev/settings/api-keys -api_key = os.getenv("LLM_API_KEY") -assert api_key is not None, "LLM_API_KEY environment variable is not set." -model = os.getenv("LLM_MODEL", "openhands/claude-sonnet-4-5-20250929") -base_url = os.getenv("LLM_BASE_URL") llm = LLM( - model=model, - api_key=SecretStr(api_key), - base_url=base_url, - usage_id="agent", + model="anthropic/claude-sonnet-4-5-20250929", + api_key=os.getenv("LLM_API_KEY"), +) + +agent = Agent( + llm=llm, + tools=[ + Tool(name=BashTool.name), + Tool(name=FileEditorTool.name), + Tool(name=TaskTrackerTool.name), + ], ) -agent = get_default_agent(llm=llm, cli_mode=True) -# Start a conversation and send some messages cwd = os.getcwd() conversation = Conversation(agent=agent, workspace=cwd) -# Send a message and let the agent run conversation.send_message("Write 3 facts about the current project into FACTS.txt.") conversation.run() - -# Report cost -cost = llm.metrics.accumulated_cost -print(f"EXAMPLE_COST: {cost}") +print("All done!") ``` ```bash Running the Example diff --git a/sdk/guides/llm-image-input.mdx b/sdk/guides/llm-image-input.mdx index c048f688..ddd761d1 100644 --- a/sdk/guides/llm-image-input.mdx +++ b/sdk/guides/llm-image-input.mdx @@ -31,7 +31,6 @@ from openhands.sdk import ( TextContent, get_logger, ) -from openhands.sdk.tool.registry import register_tool from openhands.sdk.tool.spec import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -55,18 +54,14 @@ assert llm.vision_is_active(), "The selected LLM model does not support vision i cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) -register_tool("TaskTrackerTool", TaskTrackerTool) - agent = Agent( llm=llm, tools=[ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), - Tool(name="TaskTrackerTool"), + Tool(name=FileEditorTool.name), + Tool(name=TaskTrackerTool.name), ], ) @@ -105,7 +100,6 @@ conversation.send_message( ) conversation.run() - print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): diff --git a/sdk/guides/llm-reasoning.mdx b/sdk/guides/llm-reasoning.mdx index e59c3bf5..2273ed82 100644 --- a/sdk/guides/llm-reasoning.mdx +++ b/sdk/guides/llm-reasoning.mdx @@ -32,7 +32,7 @@ from openhands.sdk import ( RedactedThinkingBlock, ThinkingBlock, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool @@ -50,8 +50,7 @@ llm = LLM( ) # Setup agent with bash tool -register_tool("BashTool", BashTool) -agent = Agent(llm=llm, tools=[Tool(name="BashTool")]) +agent = Agent(llm=llm, tools=[Tool(name=BashTool.name)]) # Callback to display thinking blocks @@ -157,7 +156,6 @@ from openhands.tools.preset.default import get_default_agent logger = get_logger(__name__) - api_key = os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY") assert api_key, "Set LLM_API_KEY or OPENAI_API_KEY in your environment." diff --git a/sdk/guides/llm-registry.mdx b/sdk/guides/llm-registry.mdx index 0d5c8af2..2538bf0b 100644 --- a/sdk/guides/llm-registry.mdx +++ b/sdk/guides/llm-registry.mdx @@ -25,7 +25,7 @@ from openhands.sdk import ( TextContent, get_logger, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool @@ -54,8 +54,7 @@ llm = llm_registry.get("agent") # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -tools = [Tool(name="BashTool")] +tools = [Tool(name=BashTool.name)] # Agent agent = Agent(llm=llm, tools=tools) diff --git a/sdk/guides/llm-routing.mdx b/sdk/guides/llm-routing.mdx index 78779b9c..2faa062a 100644 --- a/sdk/guides/llm-routing.mdx +++ b/sdk/guides/llm-routing.mdx @@ -103,7 +103,6 @@ conversation.send_message( ) conversation.run() - print("=" * 100) print("Conversation finished. Got the following LLM messages:") for i, message in enumerate(llm_messages): diff --git a/sdk/guides/mcp.mdx b/sdk/guides/mcp.mdx index a2fe824a..f5c2a35f 100644 --- a/sdk/guides/mcp.mdx +++ b/sdk/guides/mcp.mdx @@ -28,7 +28,7 @@ from openhands.sdk import ( get_logger, ) from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -48,11 +48,9 @@ llm = LLM( ) cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ - Tool(name="BashTool"), - Tool(name="FileEditorTool"), + Tool(name=BashTool.name), + Tool(name=FileEditorTool.name), ] # Add MCP Tools @@ -162,7 +160,7 @@ from openhands.sdk import ( LLMConvertibleEvent, get_logger, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -182,13 +180,11 @@ llm = LLM( ) cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), + Tool(name=FileEditorTool.name), ] mcp_config = { diff --git a/sdk/guides/metrics.mdx b/sdk/guides/metrics.mdx index 895a450f..789270b0 100644 --- a/sdk/guides/metrics.mdx +++ b/sdk/guides/metrics.mdx @@ -30,7 +30,7 @@ from openhands.sdk import ( LLMConvertibleEvent, get_logger, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -50,11 +50,9 @@ llm = LLM( ) cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ - Tool(name="BashTool"), - Tool(name="FileEditorTool"), + Tool(name=BashTool.name), + Tool(name=FileEditorTool.name), ] # Add MCP Tools @@ -160,7 +158,7 @@ from openhands.sdk import ( TextContent, get_logger, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool @@ -189,8 +187,7 @@ llm = llm_registry.get("agent") # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -tools = [Tool(name="BashTool")] +tools = [Tool(name=BashTool.name)] # Agent agent = Agent(llm=llm, tools=tools) @@ -278,11 +275,8 @@ from openhands.sdk import ( TextContent, get_logger, ) -from openhands.sdk.tool.registry import register_tool from openhands.sdk.tool.spec import Tool -from openhands.tools.execute_bash import ( - BashTool, -) +from openhands.tools.execute_bash import BashTool logger = get_logger(__name__) @@ -309,8 +303,6 @@ llm_condenser = LLM( ) # Tools -register_tool("BashTool", BashTool) - condenser = LLMSummarizingCondenser(llm=llm_condenser, max_size=10, keep_first=2) cwd = os.getcwd() @@ -318,7 +310,7 @@ agent = Agent( llm=llm, tools=[ Tool( - name="BashTool", + name=BashTool.name, ), ], condenser=condenser, @@ -333,7 +325,6 @@ conversation.send_message( ) conversation.run() - # Demonstrate extraneous costs part of the conversation second_llm = LLM( usage_id="demo-secondary", @@ -346,7 +337,6 @@ completion_response = second_llm.completion( messages=[Message(role="user", content=[TextContent(text="echo 'More spend!'")])] ) - # Access total spend spend = conversation.conversation_stats.get_combined_metrics() print("\n=== Total Spend for Conversation ===\n") @@ -357,7 +347,6 @@ if spend.accumulated_token_usage: print(f"Cache Read Tokens: {spend.accumulated_token_usage.cache_read_tokens}") print(f"Cache Write Tokens: {spend.accumulated_token_usage.cache_write_tokens}") - spend_per_usage = conversation.conversation_stats.usage_to_metrics print("\n=== Spend Breakdown by Usage ID ===\n") rows = [] diff --git a/sdk/guides/secrets.mdx b/sdk/guides/secrets.mdx index 77ade9c0..6d33392e 100644 --- a/sdk/guides/secrets.mdx +++ b/sdk/guides/secrets.mdx @@ -20,7 +20,7 @@ from openhands.sdk import ( Conversation, ) from openhands.sdk.conversation.secret_source import SecretSource -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -38,11 +38,9 @@ llm = LLM( ) # Tools -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ - Tool(name="BashTool"), - Tool(name="FileEditorTool"), + Tool(name=BashTool.name), + Tool(name=FileEditorTool.name), ] # Agent diff --git a/sdk/guides/security.mdx b/sdk/guides/security.mdx index 7722dc09..c87aaf77 100644 --- a/sdk/guides/security.mdx +++ b/sdk/guides/security.mdx @@ -27,7 +27,10 @@ from collections.abc import Callable from pydantic import SecretStr from openhands.sdk import LLM, BaseConversation, Conversation -from openhands.sdk.conversation.state import AgentExecutionStatus, ConversationState +from openhands.sdk.conversation.state import ( + ConversationExecutionStatus, + ConversationState, +) from openhands.sdk.security.confirmation_policy import AlwaysConfirm, NeverConfirm from openhands.tools.preset.default import get_default_agent @@ -76,10 +79,10 @@ def run_until_finished(conversation: BaseConversation, confirmer: Callable) -> N on reject, call reject_pending_actions(). Preserves original error if agent waits but no actions exist. """ - while conversation.state.agent_status != AgentExecutionStatus.FINISHED: + while conversation.state.execution_status != ConversationExecutionStatus.FINISHED: if ( - conversation.state.agent_status - == AgentExecutionStatus.WAITING_FOR_CONFIRMATION + conversation.state.execution_status + == ConversationExecutionStatus.WAITING_FOR_CONFIRMATION ): pending = ConversationState.get_unmatched_actions(conversation.state.events) if not pending: @@ -145,7 +148,7 @@ print("\n=== Example Complete ===") print("Key points:") print( "- conversation.run() creates actions; confirmation mode " - "sets agent_status=WAITING_FOR_CONFIRMATION" + "sets execution_status=WAITING_FOR_CONFIRMATION" ) print("- User confirmation is handled via a single reusable function") print("- Rejection uses conversation.reject_pending_actions() and the loop continues") @@ -239,10 +242,13 @@ from collections.abc import Callable from pydantic import SecretStr from openhands.sdk import LLM, Agent, BaseConversation, Conversation -from openhands.sdk.conversation.state import AgentExecutionStatus, ConversationState +from openhands.sdk.conversation.state import ( + ConversationExecutionStatus, + ConversationState, +) from openhands.sdk.security.confirmation_policy import ConfirmRisky from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -293,14 +299,14 @@ def run_until_finished_with_security( """ Drive the conversation until FINISHED. - If WAITING_FOR_CONFIRMATION: ask the confirmer. - * On approve: set agent_status = IDLE (keeps original example’s behavior). + * On approve: set execution_status = IDLE (keeps original example’s behavior). * On reject: conversation.reject_pending_actions(...). - If WAITING but no pending actions: print warning and set IDLE (matches original). """ - while conversation.state.agent_status != AgentExecutionStatus.FINISHED: + while conversation.state.execution_status != ConversationExecutionStatus.FINISHED: if ( - conversation.state.agent_status - == AgentExecutionStatus.WAITING_FOR_CONFIRMATION + conversation.state.execution_status + == ConversationExecutionStatus.WAITING_FOR_CONFIRMATION ): pending = ConversationState.get_unmatched_actions(conversation.state.events) if not pending: @@ -329,13 +335,11 @@ llm = LLM( ) # Tools -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), + Tool(name=FileEditorTool.name), ] # Agent with security analyzer diff --git a/sdk/guides/skill.mdx b/sdk/guides/skill.mdx index f1ec4bc1..1d301c29 100644 --- a/sdk/guides/skill.mdx +++ b/sdk/guides/skill.mdx @@ -27,7 +27,7 @@ from openhands.sdk.context import ( KeywordTrigger, Skill, ) -from openhands.sdk.tool import Tool, register_tool +from openhands.sdk.tool import Tool from openhands.tools.execute_bash import BashTool from openhands.tools.file_editor import FileEditorTool @@ -48,13 +48,11 @@ llm = LLM( # Tools cwd = os.getcwd() -register_tool("BashTool", BashTool) -register_tool("FileEditorTool", FileEditorTool) tools = [ Tool( - name="BashTool", + name=BashTool.name, ), - Tool(name="FileEditorTool"), + Tool(name=FileEditorTool.name), ] agent_context = AgentContext( @@ -85,11 +83,9 @@ agent_context = AgentContext( user_message_suffix="The first character of your response should be 'I'", ) - # Agent agent = Agent(llm=llm, tools=tools, agent_context=agent_context) - llm_messages = [] # collect raw LLM messages