From d0ced56580fee608a9418b0d7ed947b891aa17ce Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 17 Oct 2025 16:16:32 -0400 Subject: [PATCH 01/46] add update-docs --- .github/update-docs.yml | 90 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/update-docs.yml diff --git a/.github/update-docs.yml b/.github/update-docs.yml new file mode 100644 index 00000000..aba45940 --- /dev/null +++ b/.github/update-docs.yml @@ -0,0 +1,90 @@ +name: Sync agent-sdk OpenAPI spec + +on: + repository_dispatch: + types: [update-agent-sdk] + workflow_dispatch: + inputs: + target_branch: + description: 'Target branch in docs repo (defaults to main)' + required: false + type: string + default: 'main' + +permissions: + contents: write + +jobs: + generate-openapi: + runs-on: ubuntu-latest + env: + TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }} + TARGET_BRANCH: ${{ github.event.client_payload.target_branch || inputs.target_branch || 'main' }} + steps: + - name: Checkout docs repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: true + + - name: Configure Git + run: | + git config user.name "all-hands-bot" + git config user.email "contact@all-hands.dev" + + - name: Setup target branch + run: | + echo "Target branch: $TARGET_BRANCH" + + # Fetch all branches + git fetch origin + + # Check if target branch exists on remote + if git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "$TARGET_BRANCH"; then + echo "Branch $TARGET_BRANCH exists, checking out" + git checkout "$TARGET_BRANCH" + git pull origin "$TARGET_BRANCH" + else + echo "Branch $TARGET_BRANCH does not exist, creating new branch" + git checkout -b "$TARGET_BRANCH" + fi + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Clone agent-sdk + env: + TOKEN: ${{ env.TOKEN }} + run: | + # Get source branch from payload or default to main + SOURCE_BRANCH="${{ github.event.client_payload.branch || 'main' }}" + + tmpdir="$(mktemp -d)" + echo "Cloning agent-sdk from branch: $SOURCE_BRANCH to $tmpdir" + + auth_url="https://x-access-token:${TOKEN}@github.com/${{ github.repository_owner }}/agent-sdk.git" + git clone --depth=1 --branch="$SOURCE_BRANCH" "$auth_url" "$tmpdir/agent-sdk" + + echo "SDK_DIR=$tmpdir/agent-sdk" >> "$GITHUB_ENV" + echo "SOURCE_BRANCH=$SOURCE_BRANCH" >> "$GITHUB_ENV" + + - name: Generate OpenAPI spec + run: | + cd "$SDK_DIR" + uv sync --frozen + mkdir -p "$GITHUB_WORKSPACE/openapi" + SCHEMA_PATH="$GITHUB_WORKSPACE/openapi/agent-sdk.json" \ + uv run python openhands/agent_server/openapi.py + + - name: Commit and push OpenAPI spec + run: | + git add openapi/agent-sdk.json + + if git diff --cached --quiet; then + echo "No OpenAPI changes to commit." + else + SHA="${{ github.event.client_payload.sha || 'manual' }}" + BRANCH="${SOURCE_BRANCH:-main}" + git commit -m "sync(openapi): agent-sdk/$BRANCH ${SHA:0:7}" + git push origin "$TARGET_BRANCH" + fi From eb53362b5ddcfb8b54d845eca75fdba84d538e07 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 17 Oct 2025 16:20:36 -0400 Subject: [PATCH 02/46] add workflow to sync sdk changes --- .../{update-docs.yml => sync-sdk-changes.yml} | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) rename .github/{update-docs.yml => sync-sdk-changes.yml} (71%) diff --git a/.github/update-docs.yml b/.github/sync-sdk-changes.yml similarity index 71% rename from .github/update-docs.yml rename to .github/sync-sdk-changes.yml index aba45940..ee9ec7ff 100644 --- a/.github/update-docs.yml +++ b/.github/sync-sdk-changes.yml @@ -1,15 +1,10 @@ -name: Sync agent-sdk OpenAPI spec +name: Sync agent-sdk changes on: repository_dispatch: types: [update-agent-sdk] workflow_dispatch: inputs: - target_branch: - description: 'Target branch in docs repo (defaults to main)' - required: false - type: string - default: 'main' permissions: contents: write @@ -32,23 +27,6 @@ jobs: git config user.name "all-hands-bot" git config user.email "contact@all-hands.dev" - - name: Setup target branch - run: | - echo "Target branch: $TARGET_BRANCH" - - # Fetch all branches - git fetch origin - - # Check if target branch exists on remote - if git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "$TARGET_BRANCH"; then - echo "Branch $TARGET_BRANCH exists, checking out" - git checkout "$TARGET_BRANCH" - git pull origin "$TARGET_BRANCH" - else - echo "Branch $TARGET_BRANCH does not exist, creating new branch" - git checkout -b "$TARGET_BRANCH" - fi - - name: Install uv uses: astral-sh/setup-uv@v7 @@ -71,7 +49,7 @@ jobs: - name: Generate OpenAPI spec run: | cd "$SDK_DIR" - uv sync --frozen + make build mkdir -p "$GITHUB_WORKSPACE/openapi" SCHEMA_PATH="$GITHUB_WORKSPACE/openapi/agent-sdk.json" \ uv run python openhands/agent_server/openapi.py From 9e55965b39b60e88a8ace0b2d2120fcc69d94f35 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 17 Oct 2025 16:33:07 -0400 Subject: [PATCH 03/46] push to same branch --- .github/sync-sdk-changes.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/sync-sdk-changes.yml b/.github/sync-sdk-changes.yml index ee9ec7ff..ddb96c11 100644 --- a/.github/sync-sdk-changes.yml +++ b/.github/sync-sdk-changes.yml @@ -14,7 +14,6 @@ jobs: runs-on: ubuntu-latest env: TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }} - TARGET_BRANCH: ${{ github.event.client_payload.target_branch || inputs.target_branch || 'main' }} steps: - name: Checkout docs repo uses: actions/checkout@v4 @@ -64,5 +63,5 @@ jobs: SHA="${{ github.event.client_payload.sha || 'manual' }}" BRANCH="${SOURCE_BRANCH:-main}" git commit -m "sync(openapi): agent-sdk/$BRANCH ${SHA:0:7}" - git push origin "$TARGET_BRANCH" + git push origin fi From a7f0af2daa6bd92c0923fef044db1b9e8cf72c01 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 17 Oct 2025 16:34:02 -0400 Subject: [PATCH 04/46] remove old workflow --- .github/update-docs.yml | 90 ----------------------------------------- 1 file changed, 90 deletions(-) delete mode 100644 .github/update-docs.yml diff --git a/.github/update-docs.yml b/.github/update-docs.yml deleted file mode 100644 index aba45940..00000000 --- a/.github/update-docs.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: Sync agent-sdk OpenAPI spec - -on: - repository_dispatch: - types: [update-agent-sdk] - workflow_dispatch: - inputs: - target_branch: - description: 'Target branch in docs repo (defaults to main)' - required: false - type: string - default: 'main' - -permissions: - contents: write - -jobs: - generate-openapi: - runs-on: ubuntu-latest - env: - TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }} - TARGET_BRANCH: ${{ github.event.client_payload.target_branch || inputs.target_branch || 'main' }} - steps: - - name: Checkout docs repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: true - - - name: Configure Git - run: | - git config user.name "all-hands-bot" - git config user.email "contact@all-hands.dev" - - - name: Setup target branch - run: | - echo "Target branch: $TARGET_BRANCH" - - # Fetch all branches - git fetch origin - - # Check if target branch exists on remote - if git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "$TARGET_BRANCH"; then - echo "Branch $TARGET_BRANCH exists, checking out" - git checkout "$TARGET_BRANCH" - git pull origin "$TARGET_BRANCH" - else - echo "Branch $TARGET_BRANCH does not exist, creating new branch" - git checkout -b "$TARGET_BRANCH" - fi - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Clone agent-sdk - env: - TOKEN: ${{ env.TOKEN }} - run: | - # Get source branch from payload or default to main - SOURCE_BRANCH="${{ github.event.client_payload.branch || 'main' }}" - - tmpdir="$(mktemp -d)" - echo "Cloning agent-sdk from branch: $SOURCE_BRANCH to $tmpdir" - - auth_url="https://x-access-token:${TOKEN}@github.com/${{ github.repository_owner }}/agent-sdk.git" - git clone --depth=1 --branch="$SOURCE_BRANCH" "$auth_url" "$tmpdir/agent-sdk" - - echo "SDK_DIR=$tmpdir/agent-sdk" >> "$GITHUB_ENV" - echo "SOURCE_BRANCH=$SOURCE_BRANCH" >> "$GITHUB_ENV" - - - name: Generate OpenAPI spec - run: | - cd "$SDK_DIR" - uv sync --frozen - mkdir -p "$GITHUB_WORKSPACE/openapi" - SCHEMA_PATH="$GITHUB_WORKSPACE/openapi/agent-sdk.json" \ - uv run python openhands/agent_server/openapi.py - - - name: Commit and push OpenAPI spec - run: | - git add openapi/agent-sdk.json - - if git diff --cached --quiet; then - echo "No OpenAPI changes to commit." - else - SHA="${{ github.event.client_payload.sha || 'manual' }}" - BRANCH="${SOURCE_BRANCH:-main}" - git commit -m "sync(openapi): agent-sdk/$BRANCH ${SHA:0:7}" - git push origin "$TARGET_BRANCH" - fi From 38a8a4e213a066d2075369d34594d036376eea58 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 17 Oct 2025 16:43:52 -0400 Subject: [PATCH 05/46] update sync sdk changes workflow --- .github/sync-sdk-changes.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/sync-sdk-changes.yml b/.github/sync-sdk-changes.yml index ddb96c11..c5d762d2 100644 --- a/.github/sync-sdk-changes.yml +++ b/.github/sync-sdk-changes.yml @@ -2,7 +2,7 @@ name: Sync agent-sdk changes on: repository_dispatch: - types: [update-agent-sdk] + types: [update] workflow_dispatch: inputs: @@ -10,7 +10,7 @@ permissions: contents: write jobs: - generate-openapi: + update: runs-on: ubuntu-latest env: TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }} From b78c5a84ad72a827148ea9bb9652ee1f3c42ad58 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 17 Oct 2025 17:09:33 -0400 Subject: [PATCH 06/46] initial docs --- sdk/getting-started.mdx | 154 ++++++++++++++++++++++++++++++++++ sdk/index.mdx | 180 +++++++++++++++++++++++++--------------- 2 files changed, 265 insertions(+), 69 deletions(-) create mode 100644 sdk/getting-started.mdx diff --git a/sdk/getting-started.mdx b/sdk/getting-started.mdx new file mode 100644 index 00000000..03a3af1f --- /dev/null +++ b/sdk/getting-started.mdx @@ -0,0 +1,154 @@ +--- +title: Getting Started +description: Install the OpenHands SDK and build AI agents that write software. +icon: rocket +--- + +The OpenHands SDK is a modular framework for building AI agents that interact with code, files, and system commands. Agents can execute bash commands, edit files, browse the web, and more. + +## Prerequisites + +Install the **[uv package manager](https://docs.astral.sh/uv/)** (version 0.8.13+): + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +## Installation + +### Step 1: Acquire an LLM API Key + +The SDK requires an LLM API key from any [LiteLLM-supported provider](https://docs.litellm.ai/docs/providers). See our [recommended models](/openhands/usage/llms/llms) for best results. + + + + Sign up for [OpenHands Cloud](https://app.all-hands.dev) and get an API key from the [API keys page](https://app.all-hands.dev/settings/api-keys). This gives you access to recommended models with no markup. + + [Learn more →](/openhands/usage/llms/openhands-llms) + + + + Get an API key directly from providers like: + - [Anthropic](https://console.anthropic.com/) + - [OpenAI](https://platform.openai.com/) + - [Other LiteLLM-supported providers](https://docs.litellm.ai/docs/providers) + + + +**Set Your API Key:** + +```bash +export LLM_API_KEY=your-api-key-here +``` + +### Step 2: Install the SDK + +```bash +# Clone the repository +git clone https://github.com/All-Hands-AI/agent-sdk.git +cd agent-sdk + +# Install dependencies and setup development environment +make build +``` + +### Step 3: Run Your First Agent + +Create a file `hello_agent.py`: + +```python +import os +from pydantic import SecretStr +from openhands.sdk import LLM, Conversation +from openhands.sdk.preset.default import get_default_agent + +# Configure LLM +api_key = os.getenv("LLM_API_KEY") +assert api_key is not None, "LLM_API_KEY environment variable is not set." +llm = LLM( + model="openhands/claude-sonnet-4-5-20250929", + api_key=SecretStr(api_key), +) + +# Create agent with default tools and configuration +agent = get_default_agent( + llm=llm, + working_dir=os.getcwd(), + cli_mode=True, # Disable browser tools for CLI environments +) + +# Create conversation, send a message, and run +conversation = Conversation(agent=agent) +conversation.send_message("Create a Python file that prints 'Hello, World!'") +conversation.run() +``` + +Run the agent: + +```bash +uv run python hello_agent.py +``` + +You should see the agent understand your request, create a Python file, write the code, and report completion. + +See [`examples/01_standalone_sdk/01_hello_world.py`](https://github.com/All-Hands-AI/agent-sdk/blob/main/examples/01_standalone_sdk/01_hello_world.py) for the complete example. + +## Core Concepts + +**Agent**: An AI-powered entity that can reason, plan, and execute actions using tools. + +**Tools**: Capabilities like executing bash commands, editing files, or browsing the web. + +**Workspace**: The execution environment where agents operate (local, Docker, or remote). + +**Conversation**: Manages the interaction lifecycle between you and the agent. + +## Basic Workflow + +1. **Configure LLM**: Choose model and provide API key +2. **Create Agent**: Use preset or custom configuration +3. **Add Tools**: Enable capabilities (bash, file editing, etc.) +4. **Start Conversation**: Create conversation context +5. **Send Message**: Provide task description +6. **Run Agent**: Agent executes until task completes or stops +7. **Get Result**: Review agent's output and actions + + +## Try More Examples + +The repository includes 24+ examples demonstrating various capabilities: + +```bash +# Simple hello world +uv run python examples/01_standalone_sdk/01_hello_world.py + +# Custom tools +uv run python examples/01_standalone_sdk/02_custom_tools.py + +# With microagents +uv run python examples/01_standalone_sdk/03_activate_microagent.py + +# See all examples +ls examples/01_standalone_sdk/ +``` + + +## Next Steps + +### Explore Documentation + +- **[SDK Architecture](/sdk/architecture/overview)** - Deep dive into components +- **[Tool Documentation](/sdk/architecture/tools/overview)** - Available tools +- **[Workspace Options](/sdk/architecture/workspace/overview)** - Execution environments + +### Build Custom Solutions + +- **[Custom Tools](/sdk/guides/custom-tools)** - Create custom tools to expand agent capabilities +- **[MCP Integration](/sdk/guides/mcp-integration)** - Connect to external tools via Model Context Protocol +- **[Docker Workspaces](/sdk/guides/remote-agent-server/docker-sandboxed-server)** - Sandbox agent execution in containers + +### Get Help + +- **[Slack Community](https://all-hands.dev/joinslack)** - Ask questions and share projects +- **[GitHub Issues](https://github.com/All-Hands-AI/agent-sdk/issues)** - Report bugs or request features +- **[Example Directory](https://github.com/All-Hands-AI/agent-sdk/tree/main/examples)** - Browse working code samples diff --git a/sdk/index.mdx b/sdk/index.mdx index 43e033a0..363670c8 100644 --- a/sdk/index.mdx +++ b/sdk/index.mdx @@ -1,6 +1,8 @@ --- -title: Introduction -description: A clean, modular SDK for building AI agents. Core agent framework and production-ready tool implementations. +title: OpenHands SDK +description: Build AI agents that write software. A clean, modular SDK with production-ready tools. +icon: code +mode: wide --- The [OpenHands SDK](https://github.com/All-Hands-AI/agent-sdk) allows you to build things with agents that write software. For instance, some use cases include: @@ -11,70 +13,110 @@ The [OpenHands SDK](https://github.com/All-Hands-AI/agent-sdk) allows you to bui This SDK also powers [OpenHands](https://github.com/All-Hands-AI/OpenHands), an all-batteries-included coding agent that you can access through a GUI, CLI, or API. -## Hello World Example - -This is what it looks like to write a program with an OpenHands agent: - -```python -import os -from pydantic import SecretStr -from openhands.sdk import LLM, Conversation -from openhands.sdk.preset.default import get_default_agent - -# Configure LLM -api_key = os.getenv("LLM_API_KEY") -assert api_key is not None, "LLM_API_KEY environment variable is not set." -llm = LLM( - model="openhands/claude-sonnet-4-5-20250929", - api_key=SecretStr(api_key), -) - -# Create agent with default tools and configuration -agent = get_default_agent( - llm=llm, - working_dir=os.getcwd(), - cli_mode=True, # Disable browser tools for CLI environments -) - -# Create conversation, send a message, and run -conversation = Conversation(agent=agent) -conversation.send_message("Create a Python file that prints 'Hello, World!'") -conversation.run() -``` - -## Installation & Quickstart - -### Prerequisites - -- Python 3.12+ -- [`uv` package manager](https://docs.astral.sh/uv/) (version 0.8.13+) - -### Acquire and Set an LLM API Key - -Obtain an API key from your favorite LLM provider, any [provider supported by LiteLLM](https://docs.litellm.ai/docs/providers) -is supported by the Agent SDK, although we have a set of [recommended models](/openhands/usage/llms/llms) that -work well with OpenHands agents. - -If you want to get started quickly, you can sign up for the [OpenHands Cloud](https://app.all-hands.dev) and go to the -[API key page](https://app.all-hands.dev/settings/api-keys), which allows you to use most of our recommended models -with no markup -- documentation is [here](/openhands/usage/llms/openhands-llms). - -Once you do this, you can `export LLM_API_KEY=xxx` to use all the examples. - -### Setup - -Once this is done, run the following to do a Hello World example. - -```bash -# Clone the repository -git clone https://github.com/All-Hands-AI/agent-sdk.git -cd agent-sdk - -# Install dependencies and setup development environment -make build - -# Verify installation -uv run python examples/01_hello_world.py -``` - -For more detailed documentation and examples, refer to the `examples/` directory which contains comprehensive usage examples covering all major features of the SDK. +## Get Started + + + + Install the SDK, run your first agent, and explore comprehensive guides. + + + +## Learn the SDK + + + + Understand the SDK's architecture: agents, tools, workspaces, and more. + + + Explore the complete SDK API and source code. + + + +## Build with Examples + + + + Build local agents with custom tools and capabilities. + + + Run agents on remote servers with Docker sandboxing. + + + Automate repository tasks with agent-powered workflows. + + + +## Extend and Customize + + + + Create custom tools to expand agent capabilities. + + + Connect to external tools via Model Context Protocol. + + + Add specialized knowledge that activates on-demand. + + + +## Deploy to Production + + + + Sandbox agent execution in isolated Docker containers. + + + Deploy agents as HTTP services for multi-user access. + + + +## Community + + + + Connect with the OpenHands community on Slack. + + + Contribute to the SDK or report issues on GitHub. + + From 4a27d79282c72b00fdd3f2846067ec106384b44b Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 17 Oct 2025 17:56:51 -0400 Subject: [PATCH 07/46] update how to install --- sdk/getting-started.mdx | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/sdk/getting-started.mdx b/sdk/getting-started.mdx index 03a3af1f..d4308365 100644 --- a/sdk/getting-started.mdx +++ b/sdk/getting-started.mdx @@ -43,14 +43,29 @@ export LLM_API_KEY=your-api-key-here ### Step 2: Install the SDK -```bash -# Clone the repository -git clone https://github.com/All-Hands-AI/agent-sdk.git -cd agent-sdk + + + ```bash + pip install openhands-sdk # Core SDK (openhands.sdk) + pip install openhands-tools # Built-in tools (openhands.tools) + # Optional: required for sandboxed workspaces in Docker or remote servers + pip install openhands-workspace # Workspace backends (openhands.workspace) + pip install openhands-agent-server # Remote agent server (openhands.agent_server) + ``` + + + + ```bash + # Clone the repository + git clone https://github.com/All-Hands-AI/agent-sdk.git + cd agent-sdk + + # Install dependencies and setup development environment + make build + ``` + + -# Install dependencies and setup development environment -make build -``` ### Step 3: Run Your First Agent From 12e11604d4c2bdb7b457044c81639b12bfa2e6c2 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 12:30:01 -0400 Subject: [PATCH 08/46] update index --- sdk/index.mdx | 93 ++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/sdk/index.mdx b/sdk/index.mdx index 363670c8..e1a04ea8 100644 --- a/sdk/index.mdx +++ b/sdk/index.mdx @@ -5,13 +5,56 @@ icon: code mode: wide --- -The [OpenHands SDK](https://github.com/All-Hands-AI/agent-sdk) allows you to build things with agents that write software. For instance, some use cases include: +The OpenHands Cloud Agent SDK is a set of Python and REST APIs for building **agents that work with code**. -1. A documentation system that checks the changes made to your codebase this week and updates them -2. An SRE system that reads your server logs and your codebase, then uses this info to debug new errors that are appearing in prod -3. A customer onboarding system that takes all of their documents in unstructured format and enters information into your database +You can use the Cloud Agent SDK for: -This SDK also powers [OpenHands](https://github.com/All-Hands-AI/OpenHands), an all-batteries-included coding agent that you can access through a GUI, CLI, or API. +- One-off tasks, like building a README for your repo +- Routine maintenance tasks, like updating dependencies +- Major tasks that involve multiple agents, like refactors and rewrites + +You can even use the SDK to build new developer experiences—it’s the engine behind the [OpenHands CLI](/openhands/usage/how-to/cli-mode) and [OpenHands Cloud](/openhands/usage/cloud/openhands-cloud). + +Get started with some examples or keep reading to learn more. + +## Features + + + + A unified Python API that enables you to run agents locally or in the cloud, define custom agent behaviors, and create custom tools. + + + Ready-to-use tools for executing Bash commands, editing files, browsing the web, integrating with MCP, and more. + + + A production-ready server that runs agents anywhere, including Docker and Kubernetes, while connecting seamlessly to the Python API. + + + +## Why OpenHands Agent SDK? + +### Emphasis on coding + +While other agent SDKs (e.g. [LangChain](https://python.langchain.com/docs/tutorials/agents/)) are focused on more general use cases, like delivering chat-based support or automating back-office tasks, OpenHands is purpose-built for software engineering. + +While some folks do use OpenHands to solve more general tasks (code is a powerful tool!), most of us use OpenHands to work with code. + +### State-of-the-Art Performance + +OpenHands is a top performer across a wide variety of benchmarks, including SWE-bench, SWT-bench, and multi-SWE-bench. The SDK includes a number of state-of-the-art agentic features developed by our research team, including: + +- Task planning and decomposition +- Automatic context compression +- Security analysis +- Strong agent-computer interfaces + +OpenHands has attracted researchers from a wide variety of academic institutions, and is [becoming the preferred harness](https://x.com/Alibaba_Qwen/status/1947766835023335516) for evaluating LLMs on coding tasks. + +### Free and Open Source + +OpenHands is also the leading open source framework for coding agents. It’s MIT-licensed, and can work with any LLM—including big proprietary LLMs like Claude and OpenAI, as well as open source LLMs like Qwen and Devstral. + +Other SDKs (e.g. [Claude Code](https://github.com/anthropics/claude-agent-sdk-python)) are proprietary and lock you into a particular model. Given how quickly models are evolving, it’s best to stay model-agnostic! ## Get Started @@ -64,46 +107,6 @@ This SDK also powers [OpenHands](https://github.com/All-Hands-AI/OpenHands), an -## Extend and Customize - - - - Create custom tools to expand agent capabilities. - - - Connect to external tools via Model Context Protocol. - - - Add specialized knowledge that activates on-demand. - - - -## Deploy to Production - - - - Sandbox agent execution in isolated Docker containers. - - - Deploy agents as HTTP services for multi-user access. - - - ## Community From b5da598d6670c45da7e88fdbb54eb7a47d27dd53 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 13:14:04 -0400 Subject: [PATCH 09/46] add workflow to sync code blocks --- .github/scripts/README.md | 135 ++++++++++ .github/scripts/sync_code_blocks.py | 216 +++++++++++++++ .github/workflows/sync-docs-code-blocks.yml | 54 ++++ sdk/guides/custom-tools.mdx | 283 ++++++++++++++++++++ 4 files changed, 688 insertions(+) create mode 100644 .github/scripts/README.md create mode 100755 .github/scripts/sync_code_blocks.py create mode 100644 .github/workflows/sync-docs-code-blocks.yml create mode 100644 sdk/guides/custom-tools.mdx diff --git a/.github/scripts/README.md b/.github/scripts/README.md new file mode 100644 index 00000000..35861a81 --- /dev/null +++ b/.github/scripts/README.md @@ -0,0 +1,135 @@ +# Documentation Code Block Sync + +This directory contains scripts for automatically syncing code blocks in documentation files with their corresponding source files from the agent-sdk repository. + +## Overview + +The `sync_code_blocks.py` script ensures that code examples in the documentation always match the actual source code in the agent-sdk `examples/` directory. This prevents documentation drift and ensures users always see accurate, working examples. + +## How It Works + +1. **Scans MDX Files**: The script recursively scans all `.mdx` files in the docs repository +2. **Finds Code Blocks**: It looks for Python code blocks with file references using the pattern: + ```markdown + ```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools.py + + ``` + ``` +3. **Extracts File Path**: The file path is extracted from the code block metadata (e.g., `examples/01_standalone_sdk/02_custom_tools.py`) +4. **Reads Source File**: The actual source file is read from the checked-out agent-sdk repository +5. **Compares Content**: The code block content is compared with the actual file content +6. **Updates Docs**: If there are differences, the documentation file is automatically updated + +## Usage + +### Via GitHub Actions + +The workflow `.github/workflows/sync-docs-code-blocks.yml` automatically runs: +- **Daily at 2 AM UTC** to catch any changes +- **Manually** via workflow dispatch (allows specifying a custom agent-sdk branch/tag) + +When differences are detected, the workflow: +1. Checks out the docs repository +2. Checks out the agent-sdk repository into `agent-sdk/` subdirectory +3. Runs the sync script +4. Creates a pull request with the updates + +### Manual Run + +To test locally: + +```bash +cd docs + +# Clone agent-sdk if not already present +git clone https://github.com/All-Hands-AI/agent-sdk.git agent-sdk + +# Run the script +python .github/scripts/sync_code_blocks.py + +# Clean up +rm -rf agent-sdk +``` + +## Code Block Format + +For the script to detect and sync code blocks, they must follow this format: + +```markdown +```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools.py + +``` +``` + +The file reference must: +- Start with `examples/` +- End with `.py` +- Be a valid path relative to the agent-sdk repository root + +Examples: +- `examples/01_standalone_sdk/02_custom_tools.py` +- `examples/02_remote_agent_server/01_convo_with_local_agent_server.py` +- `examples/03_github_workflows/01_basic_action/action.py` + +## Features + +- **Automatic Detection**: Finds all code blocks with file references +- **Smart Comparison**: Normalizes content (trailing whitespace, line endings) for accurate comparison +- **Batch Updates**: Can update multiple files in a single run +- **GitHub Integration**: Automatically creates PRs when changes are needed +- **Safe Operation**: Only updates files with actual differences +- **Clear Logging**: Provides detailed output about what's being processed and updated +- **Flexible Scheduling**: Daily automatic runs plus manual trigger option + +## Configuration + +### Workflow Configuration + +Edit `.github/workflows/sync-docs-code-blocks.yml` to customize: +- Schedule timing (cron expression) +- Default agent-sdk branch +- PR title/body templates + +### Script Behavior + +The script: +- Expects agent-sdk to be checked out in `docs/agent-sdk/` +- Scans all `.mdx` files recursively +- Updates files in-place +- Sets GitHub Actions output for PR creation + +## Troubleshooting + +### "Source file not found" warnings + +This means the script found a code block reference but couldn't locate the corresponding source file. This can happen if: +- The file reference doesn't match an actual file in `agent-sdk/examples/` +- The file has been moved or renamed in agent-sdk +- The agent-sdk checkout is incomplete or at the wrong ref + +### No changes detected when you expect them + +Check that: +1. The code block format matches the expected pattern (with full `examples/` path) +2. The file path in the code block is correct and includes `.py` extension +3. Whitespace differences are normalized (trailing spaces are ignored) +4. The agent-sdk repository is checked out at the correct branch/tag + +### Script fails with path errors + +Ensure: +- The script is run from the docs repository root, or +- The agent-sdk repository is checked out in `docs/agent-sdk/` + +## Maintenance + +- Keep the regex patterns updated if the code block format changes +- Update the workflow if the repository structure changes +- Monitor workflow runs to catch any issues early +- Review PRs to ensure changes are expected + +## Related Files + +- `.github/workflows/sync-docs-code-blocks.yml` - GitHub Actions workflow +- `agent-sdk/examples/` - Source files that are synced to documentation +- `sdk/guides/` - Documentation files containing code blocks diff --git a/.github/scripts/sync_code_blocks.py b/.github/scripts/sync_code_blocks.py new file mode 100755 index 00000000..1f6b4ebf --- /dev/null +++ b/.github/scripts/sync_code_blocks.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +""" +Sync code blocks in documentation files with their corresponding source files. + +This script: +1. Scans MDX files for code blocks with file references (e.g., ```python expandable examples/01_standalone_sdk/02_custom_tools.py) +2. Extracts the file path from the code block metadata +3. Reads the actual content from the source file in agent-sdk/ +4. Compares the code block content with the actual file content +5. Updates the documentation if there are differences +""" + +import os +import re +import sys +from pathlib import Path +from typing import List, Tuple, Optional + + +def find_mdx_files(docs_path: Path) -> List[Path]: + """Find all MDX files in the docs directory.""" + mdx_files = [] + for root, dirs, files in os.walk(docs_path): + for file in files: + if file.endswith('.mdx'): + mdx_files.append(Path(root) / file) + return mdx_files + + +def extract_code_blocks(content: str) -> List[Tuple[str, str, int, int]]: + """ + Extract code blocks that reference source files. + + Returns list of tuples: (file_reference, code_content, start_pos, end_pos) + + Pattern matches blocks like: + ```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools.py + + ``` + """ + # Pattern to match code blocks with file references + # Captures the file path and the code content + pattern = r'```python[^\n]*\s+(examples/[^\s]+\.py)\n(.*?)```' + + matches = [] + for match in re.finditer(pattern, content, re.DOTALL): + file_ref = match.group(1) + code_content = match.group(2) + start_pos = match.start() + end_pos = match.end() + matches.append((file_ref, code_content, start_pos, end_pos)) + + return matches + + +def read_source_file(agent_sdk_path: Path, file_ref: str) -> Optional[str]: + """ + Read the actual source file content. + + Args: + agent_sdk_path: Path to agent-sdk repository + file_ref: File reference like "examples/01_standalone_sdk/02_custom_tools.py" + + Returns: + File content or None if file not found + """ + # file_ref is already the full path relative to agent-sdk root + source_path = agent_sdk_path / file_ref + + if not source_path.exists(): + print(f"Warning: Source file not found: {source_path}") + return None + + try: + with open(source_path, 'r', encoding='utf-8') as f: + return f.read() + except Exception as e: + print(f"Error reading {source_path}: {e}") + return None + + +def normalize_content(content: str) -> str: + """Normalize content for comparison (remove trailing whitespace, normalize line endings).""" + # Split into lines, strip trailing whitespace from each line, rejoin + lines = [line.rstrip() for line in content.splitlines()] + return '\n'.join(lines) + + +def update_doc_file(doc_path: Path, content: str, code_blocks: List[Tuple[str, str, int, int]], + agent_sdk_path: Path) -> bool: + """ + Update documentation file with correct code blocks. + + Returns True if changes were made, False otherwise. + """ + changes_made = False + new_content = content + offset = 0 # Track offset due to content changes + + for file_ref, old_code, start_pos, end_pos in code_blocks: + # Read actual source file + actual_content = read_source_file(agent_sdk_path, file_ref) + + if actual_content is None: + continue + + # Normalize both for comparison + old_normalized = normalize_content(old_code) + actual_normalized = normalize_content(actual_content) + + if old_normalized != actual_normalized: + print(f"\n📝 Found difference in {doc_path.name} for {file_ref}") + print(f" Updating code block...") + + # Calculate adjusted positions + adj_start = start_pos + offset + adj_end = end_pos + offset + + # Extract the opening line (```python ... file_ref) + opening_line_match = re.search(r'```python[^\n]*\s+' + re.escape(file_ref), + new_content[adj_start:adj_end]) + if opening_line_match: + opening_line = opening_line_match.group(0) + + # Construct new code block (don't add extra newline if content already ends with one) + if actual_content.endswith('\n'): + new_block = f"{opening_line}\n{actual_content}```" + else: + new_block = f"{opening_line}\n{actual_content}\n```" + old_block = new_content[adj_start:adj_end] + + # Replace in content + new_content = new_content[:adj_start] + new_block + new_content[adj_end:] + + # Update offset + offset += len(new_block) - len(old_block) + changes_made = True + + if changes_made: + try: + with open(doc_path, 'w', encoding='utf-8') as f: + f.write(new_content) + print(f"✅ Updated {doc_path}") + return True + except Exception as e: + print(f"❌ Error writing {doc_path}: {e}") + return False + + return False + + +def main(): + # Script is in docs/.github/scripts/ + script_dir = Path(__file__).parent.parent.parent # docs root + # agent-sdk is at the same level as docs, not inside docs + agent_sdk_path = script_dir.parent / 'agent-sdk' + + print(f"🔍 Scanning for MDX files in {script_dir}") + print(f"📁 Agent SDK path: {agent_sdk_path}") + + if not agent_sdk_path.exists(): + print(f"❌ Agent SDK path does not exist: {agent_sdk_path}") + print(f" Make sure agent-sdk repository is checked out at the same level as docs directory") + sys.exit(1) + + # Find all MDX files + mdx_files = find_mdx_files(script_dir) + print(f"📄 Found {len(mdx_files)} MDX files") + + total_changes = 0 + files_changed = [] + + # Process each MDX file + for mdx_file in mdx_files: + try: + with open(mdx_file, 'r', encoding='utf-8') as f: + content = f.read() + + # Extract code blocks + code_blocks = extract_code_blocks(content) + + if not code_blocks: + continue + + print(f"\n📋 Processing {mdx_file.relative_to(script_dir)}") + print(f" Found {len(code_blocks)} code block(s) with file references") + + # Update file if needed + if update_doc_file(mdx_file, content, code_blocks, agent_sdk_path): + total_changes += 1 + files_changed.append(str(mdx_file.relative_to(script_dir))) + + except Exception as e: + print(f"❌ Error processing {mdx_file}: {e}") + continue + + # Summary + print("\n" + "="*60) + if total_changes > 0: + print(f"✅ Updated {total_changes} file(s):") + for file in files_changed: + print(f" - {file}") + # Set output for GitHub Actions + if 'GITHUB_OUTPUT' in os.environ: + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('changes=true\n') + else: + print("✅ All code blocks are in sync!") + if 'GITHUB_OUTPUT' in os.environ: + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('changes=false\n') + print("="*60) + + +if __name__ == '__main__': + main() diff --git a/.github/workflows/sync-docs-code-blocks.yml b/.github/workflows/sync-docs-code-blocks.yml new file mode 100644 index 00000000..4e9cc156 --- /dev/null +++ b/.github/workflows/sync-docs-code-blocks.yml @@ -0,0 +1,54 @@ +name: Sync Documentation Code Blocks + +on: + schedule: + # Run daily at 2 AM UTC to catch any changes + - cron: '0 2 * * *' + workflow_dispatch: + inputs: + agent_sdk_ref: + description: 'Agent SDK branch/tag/commit to sync from' + required: false + default: 'main' + +permissions: + contents: write + pull-requests: write + +jobs: + sync-code-blocks: + runs-on: ubuntu-latest + steps: + - name: Checkout docs repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Checkout agent-sdk + uses: actions/checkout@v4 + with: + repository: All-Hands-AI/agent-sdk + path: agent-sdk + ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }} + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Sync code blocks + id: sync + run: | + python .github/scripts/sync_code_blocks.py + + - name: Commit and push changes + if: steps.sync.outputs.changes == 'true' + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "docs: sync code blocks from agent-sdk examples + + Synced from agent-sdk ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }}" + git push diff --git a/sdk/guides/custom-tools.mdx b/sdk/guides/custom-tools.mdx new file mode 100644 index 00000000..6be1b5fa --- /dev/null +++ b/sdk/guides/custom-tools.mdx @@ -0,0 +1,283 @@ +--- +title: Custom Tools +description: Tools define what agents can do. The SDK includes built-in tools for common operations and supports creating custom tools for specialized needs. +--- + +## Built-in Tools + +```python +from openhands.tools import BashTool, FileEditorTool +from openhands.tools.preset import get_default_tools + +# Use specific tools +agent = Agent(llm=llm, tools=[BashTool.create(), FileEditorTool.create()]) + +# Or use preset +tools = get_default_tools() +agent = Agent(llm=llm, tools=tools) +``` + +See [Tools Overview](/sdk/architecture/tools/overview) for the complete list of available tools. + +## Understanding the Tool System + +The SDK's tool system is built around three core components: + +1. **Action** - Defines input parameters (what the tool accepts) +2. **Observation** - Defines output data (what the tool returns) +3. **Executor** - Implements the tool's logic (what the tool does) + +These components are tied together by a **ToolDefinition** that registers the tool with the agent. For architectural details and advanced usage patterns, see [Tool System Architecture](/sdk/architecture/sdk/tool). + +## Creating a Custom Tool + + +This example is available on GitHub: [examples/01_standalone_sdk/02_custom_tools.py](https://github.com/All-Hands-AI/agent-sdk/blob/main/examples/01_standalone_sdk/02_custom_tools.py) + + +Here's a minimal example of creating a custom grep tool: + +```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools +"""Advanced example showing explicit executor usage and custom grep tool.""" + +import os +import shlex +from collections.abc import Sequence + +from pydantic import Field, SecretStr + +from openhands.sdk import ( + LLM, + Action, + Agent, + Conversation, + Event, + ImageContent, + LLMConvertibleEvent, + Observation, + TextContent, + ToolDefinition, + get_logger, +) +from openhands.sdk.tool import ( + Tool, + ToolExecutor, + register_tool, +) +from openhands.tools.execute_bash import ( + BashExecutor, + ExecuteBashAction, + execute_bash_tool, +) +from openhands.tools.file_editor import FileEditorTool + + +logger = get_logger(__name__) + + +# --- Action / Observation --- + + +class GrepAction(Action): + pattern: str = Field(description="Regex to search for") + path: str = Field( + default=".", description="Directory to search (absolute or relative)" + ) + include: str | None = Field( + default=None, description="Optional glob to filter files (e.g. '*.py')" + ) + + +class GrepObservation(Observation): + matches: list[str] = Field(default_factory=list) + files: list[str] = Field(default_factory=list) + count: int = 0 + + @property + def to_llm_content(self) -> Sequence[TextContent | ImageContent]: + if not self.count: + return [TextContent(text="No matches found.")] + files_list = "\n".join(f"- {f}" for f in self.files[:20]) + sample = "\n".join(self.matches[:10]) + more = "\n..." if self.count > 10 else "" + ret = ( + f"Found {self.count} matching lines.\n" + f"Files:\n{files_list}\n" + f"Sample:\n{sample}{more}" + ) + return [TextContent(text=ret)] + + +# --- Executor --- + + +class GrepExecutor(ToolExecutor[GrepAction, GrepObservation]): + def __init__(self, bash: BashExecutor): + self.bash: BashExecutor = bash + + def __call__(self, action: GrepAction) -> GrepObservation: + root = os.path.abspath(action.path) + pat = shlex.quote(action.pattern) + root_q = shlex.quote(root) + + # Use grep -r; add --include when provided + if action.include: + inc = shlex.quote(action.include) + cmd = f"grep -rHnE --include {inc} {pat} {root_q} 2>/dev/null | head -100" + else: + cmd = f"grep -rHnE {pat} {root_q} 2>/dev/null | head -100" + + result = self.bash(ExecuteBashAction(command=cmd)) + + matches: list[str] = [] + 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(): + matches.append(line) + # Expect "path:line:content" — take the file part before first ":" + file_path = line.split(":", 1)[0] + if file_path: + files.add(os.path.abspath(file_path)) + + return GrepObservation(matches=matches, files=sorted(files), count=len(matches)) + + +# Tool description +_GREP_DESCRIPTION = """Fast content search tool. +* Searches file contents using regular expressions +* Supports full regex syntax (eg. "log.*Error", "function\\s+\\w+", etc.) +* Filter files by pattern with the include parameter (eg. "*.js", "*.{ts,tsx}") +* Returns matching file paths sorted by modification time. +* Only the first 100 results are returned. Consider narrowing your search with stricter regex patterns or provide path parameter if you need more results. +* Use this tool when you need to find files containing specific patterns +* When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead +""" # noqa: E501 + +# Configure LLM +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( + service_id="agent", + model=model, + base_url=base_url, + api_key=SecretStr(api_key), +) + +# Tools - demonstrating both simplified and advanced patterns +cwd = os.getcwd() + + +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, + ) + + return [bash_tool, grep_tool] + + +register_tool("FileEditorTool", FileEditorTool) +register_tool("BashAndGrepToolSet", _make_bash_and_grep_tools) + +tools = [ + Tool(name="FileEditorTool"), + Tool(name="BashAndGrepToolSet"), +] + +# Agent +agent = Agent(llm=llm, tools=tools) + +llm_messages = [] # collect raw LLM messages + + +def conversation_callback(event: Event): + if isinstance(event, LLMConvertibleEvent): + llm_messages.append(event.to_llm_message()) + + +conversation = Conversation( + agent=agent, callbacks=[conversation_callback], workspace=cwd +) + +conversation.send_message( + "Hello! Can you use the grep tool to find all files " + "containing the word 'class' in this project, then create a summary file listing them? " # noqa: E501 + "Use the pattern 'class' to search and include only Python files with '*.py'." # noqa: E501 +) +conversation.run() + +conversation.send_message("Great! Now delete that file.") +conversation.run() + +print("=" * 100) +print("Conversation finished. Got the following LLM messages:") +for i, message in enumerate(llm_messages): + print(f"Message {i}: {str(message)[:200]}") +``` + +## Key Points + +### Factory Functions +Tool factory functions receive `conv_state` as a parameter, allowing access to workspace information: + +```python +def make_tools(conv_state) -> list[ToolDefinition]: + working_dir = conv_state.workspace.working_dir + # Create and configure tools... + return [tool1, tool2] +``` + +### Shared Executors +Multiple tools can share executors for efficiency and state consistency: + +```python +# Both tools use the same BashExecutor and working directory +bash_executor = BashExecutor(working_dir=conv_state.workspace.working_dir) +bash_tool = execute_bash_tool.set_executor(executor=bash_executor) +grep_tool = ToolDefinition(..., executor=GrepExecutor(bash_executor)) +``` + +### Output Formatting +The `to_llm_content()` method formats observations for the LLM: + +```python +@property +def to_llm_content(self): + # Return clear, structured text for the LLM + return [TextContent(text=f"Found {self.count} results...")] +``` + +## Running the Example + +```bash +export LLM_API_KEY="your-api-key" +cd agent-sdk +uv run python examples/01_standalone_sdk/02_custom_tools.py +``` + +## When to Create Custom Tools + +Create custom tools when you need to: +- Combine multiple operations into a single, structured interface +- Add typed parameters with validation +- Format complex outputs for LLM consumption +- Integrate with external APIs or services + +## Next Steps + +- **[Tool System Architecture](/sdk/architecture/sdk/tool)** - Deep dive into the tool system +- **[MCP Integration](/sdk/guides/standalone-sdk/mcp-integration)** - Use Model Context Protocol servers +- **[Pre-built Tools](/sdk/architecture/tools/overview)** - Browse available tools From 721256017809c68f8a24bd8c591eed1f2d04c2a0 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 13:18:14 -0400 Subject: [PATCH 10/46] finish agent-sdk --- sdk/guides/custom-tools.mdx | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/sdk/guides/custom-tools.mdx b/sdk/guides/custom-tools.mdx index 6be1b5fa..c9bbebab 100644 --- a/sdk/guides/custom-tools.mdx +++ b/sdk/guides/custom-tools.mdx @@ -230,34 +230,68 @@ for i, message in enumerate(llm_messages): ## Key Points +### Tool Registration +Tools are registered using `register_tool()` and referenced by name: + +```python +# Register a simple tool class +register_tool("FileEditorTool", FileEditorTool) + +# Register a factory function that creates multiple tools +register_tool("BashAndGrepToolSet", _make_bash_and_grep_tools) + +# Use registered tools by name +tools = [ + Tool(name="FileEditorTool"), + Tool(name="BashAndGrepToolSet"), +] +``` + ### Factory Functions Tool factory functions receive `conv_state` as a parameter, allowing access to workspace information: ```python -def make_tools(conv_state) -> list[ToolDefinition]: - working_dir = conv_state.workspace.working_dir +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) # Create and configure tools... - return [tool1, tool2] + return [bash_tool, grep_tool] ``` ### Shared Executors Multiple tools can share executors for efficiency and state consistency: ```python -# Both tools use the same BashExecutor and working directory bash_executor = BashExecutor(working_dir=conv_state.workspace.working_dir) bash_tool = execute_bash_tool.set_executor(executor=bash_executor) -grep_tool = ToolDefinition(..., executor=GrepExecutor(bash_executor)) + +grep_executor = GrepExecutor(bash_executor) +grep_tool = ToolDefinition( + name="grep", + description=_GREP_DESCRIPTION, + action_type=GrepAction, + observation_type=GrepObservation, + executor=grep_executor, +) ``` ### Output Formatting -The `to_llm_content()` method formats observations for the LLM: +The `to_llm_content()` property formats observations for the LLM: ```python @property -def to_llm_content(self): - # Return clear, structured text for the LLM - return [TextContent(text=f"Found {self.count} results...")] +def to_llm_content(self) -> Sequence[TextContent | ImageContent]: + if not self.count: + return [TextContent(text="No matches found.")] + files_list = "\n".join(f"- {f}" for f in self.files[:20]) + sample = "\n".join(self.matches[:10]) + more = "\n..." if self.count > 10 else "" + ret = ( + f"Found {self.count} matching lines.\n" + f"Files:\n{files_list}\n" + f"Sample:\n{sample}{more}" + ) + return [TextContent(text=ret)] ``` ## Running the Example @@ -279,5 +313,4 @@ Create custom tools when you need to: ## Next Steps - **[Tool System Architecture](/sdk/architecture/sdk/tool)** - Deep dive into the tool system -- **[MCP Integration](/sdk/guides/standalone-sdk/mcp-integration)** - Use Model Context Protocol servers -- **[Pre-built Tools](/sdk/architecture/tools/overview)** - Browse available tools +- **[Model Context Protocol (MCP) Integration](/sdk/guides/mcp)** - Use Model Context Protocol servers From 8c37280c6b4b61d1ca60ae43117a41ad6607f4ef Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 13:36:01 -0400 Subject: [PATCH 11/46] add mcp eample --- sdk/guides/mcp.mdx | 248 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 sdk/guides/mcp.mdx diff --git a/sdk/guides/mcp.mdx b/sdk/guides/mcp.mdx new file mode 100644 index 00000000..f854f5f5 --- /dev/null +++ b/sdk/guides/mcp.mdx @@ -0,0 +1,248 @@ +--- +title: Model Context Protocol (MCP) +description: MCP enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically. +--- + +MCP (Model Context Protocol) is a protocol for exposing tools and resources to AI agents. Read more [here](https://modelcontextprotocol.io/). + + +## Basic MCP Usage + + +This example is available on GitHub: [examples/01_standalone_sdk/07_mcp_integration.py](https://github.com/All-Hands-AI/agent-sdk/blob/main/examples/01_standalone_sdk/07_mcp_integration.py) + + +Here's an example integrating MCP servers with an agent: + +```python icon="python" expandable examples/01_standalone_sdk/07_mcp_integration +import os + +from pydantic import SecretStr + +from openhands.sdk import ( + LLM, + Agent, + Conversation, + Event, + LLMConvertibleEvent, + get_logger, +) +from openhands.sdk.security.llm_analyzer import LLMSecurityAnalyzer +from openhands.sdk.tool import Tool, register_tool +from openhands.tools.execute_bash import BashTool +from openhands.tools.file_editor import FileEditorTool + + +logger = get_logger(__name__) + +# Configure LLM +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( + service_id="agent", + model=model, + base_url=base_url, + api_key=SecretStr(api_key), +) + +cwd = os.getcwd() +register_tool("BashTool", BashTool) +register_tool("FileEditorTool", FileEditorTool) +tools = [ + Tool(name="BashTool"), + Tool(name="FileEditorTool"), +] + +# Add MCP Tools +mcp_config = { + "mcpServers": { + "fetch": {"command": "uvx", "args": ["mcp-server-fetch"]}, + "repomix": {"command": "npx", "args": ["-y", "repomix@1.4.2", "--mcp"]}, + } +} +# Agent +agent = Agent( + llm=llm, + tools=tools, + mcp_config=mcp_config, + # This regex filters out all repomix tools except pack_codebase + filter_tools_regex="^(?!repomix)(.*)|^repomix.*pack_codebase.*$", + security_analyzer=LLMSecurityAnalyzer(), +) + +llm_messages = [] # collect raw LLM messages + + +def conversation_callback(event: Event): + if isinstance(event, LLMConvertibleEvent): + llm_messages.append(event.to_llm_message()) + + +# Conversation +conversation = Conversation( + agent=agent, + callbacks=[conversation_callback], + workspace=cwd, +) + +logger.info("Starting conversation with MCP integration...") +conversation.send_message( + "Read https://github.com/All-Hands-AI/OpenHands and write 3 facts " + "about the project into FACTS.txt." +) +conversation.run() + +conversation.send_message("Great! Now delete that file.") +conversation.run() + +print("=" * 100) +print("Conversation finished. Got the following LLM messages:") +for i, message in enumerate(llm_messages): + print(f"Message {i}: {str(message)[:200]}") +``` + +```bash Running the example +export LLM_API_KEY="your-api-key" +cd agent-sdk +uv run python examples/01_standalone_sdk/07_mcp_integration.py +``` + + +This example demonstrates: + +**MCP Configuration:** +Configure MCP servers using a dictionary with server names and connection details following [this configuration format](https://gofastmcp.com/clients/client#configuration-format) + +```python highlight={3-4} +mcp_config = { + "mcpServers": { + "fetch": {"command": "uvx", "args": ["mcp-server-fetch"]}, + "repomix": {"command": "npx", "args": ["-y", "repomix@1.4.2", "--mcp"]}, + } +} +``` + +**Tool Filtering:** Use `filter_tools_regex` to control which MCP tools are available to the agent + +```python highlight={5} +agent = Agent( + llm=llm, + tools=tools, + mcp_config=mcp_config, + filter_tools_regex="^(?!repomix)(.*)|^repomix.*pack_codebase.*$", +) +``` + +## MCP with OAuth + + +This example is available on GitHub: [examples/01_standalone_sdk/08_mcp_with_oauth.py](https://github.com/All-Hands-AI/agent-sdk/blob/main/examples/01_standalone_sdk/08_mcp_with_oauth.py) + + +For MCP servers requiring OAuth authentication: + + +```python icon="python" expandable examples/01_standalone_sdk/08_mcp_with_oauth +import os + +from pydantic import SecretStr + +from openhands.sdk import ( + LLM, + Agent, + Conversation, + Event, + LLMConvertibleEvent, + get_logger, +) +from openhands.sdk.tool import Tool, register_tool +from openhands.tools.execute_bash import BashTool +from openhands.tools.file_editor import FileEditorTool + + +logger = get_logger(__name__) + +# Configure LLM +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( + service_id="agent", + model=model, + base_url=base_url, + api_key=SecretStr(api_key), +) + +cwd = os.getcwd() +register_tool("BashTool", BashTool) +register_tool("FileEditorTool", FileEditorTool) +tools = [ + Tool( + name="BashTool", + ), + Tool(name="FileEditorTool"), +] + +mcp_config = { + "mcpServers": {"Notion": {"url": "https://mcp.notion.com/mcp", "auth": "oauth"}} +} +agent = Agent(llm=llm, tools=tools, mcp_config=mcp_config) + +llm_messages = [] # collect raw LLM messages + + +def conversation_callback(event: Event): + if isinstance(event, LLMConvertibleEvent): + llm_messages.append(event.to_llm_message()) + + +# Conversation +conversation = Conversation( + agent=agent, + callbacks=[conversation_callback], +) + +logger.info("Starting conversation with MCP integration...") +conversation.send_message("Can you search about OpenHands V1 in my notion workspace?") +conversation.run() + +print("=" * 100) +print("Conversation finished. Got the following LLM messages:") +for i, message in enumerate(llm_messages): + print(f"Message {i}: {str(message)[:200]}") +``` + +```bash Running the example +export LLM_API_KEY="your-api-key" +cd agent-sdk +uv run python examples/01_standalone_sdk/08_mcp_with_oauth.py +``` + +This example demonstrates: + +**OAuth Configuration** +- Configure OAuth-enabled MCP servers by specifying the URL and auth type +- The SDK automatically handles the OAuth flow when first connecting +- When the agent first attempts to use an OAuth-protected MCP server's tools, the SDK initiates the OAuth flow via [FastMCP](https://gofastmcp.com/servers/auth/authentication) +- User will be prompted to authenticate +- Access tokens are securely stored and automatically refreshed by FastMCP as needed + +```python highlight={5} +mcp_config = { + "mcpServers": { + "Notion": { + "url": "https://mcp.notion.com/mcp", + "auth": "oauth" + } + } +} +``` + +## Next Steps + +- **[MCP Architecture](/sdk/architecture/sdk/mcp)** - Technical details and internals +- **[Custom Tools](/sdk/guides/custom-tools)** - Creating native SDK tools +- **[Security Analyzer](/sdk/guides/security-analyzer)** - Securing tool usage From 57cae33b71a8507ba2fad042105d4167777dcd2f Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 13:39:21 -0400 Subject: [PATCH 12/46] make mcp as a few sections --- sdk/guides/mcp.mdx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/sdk/guides/mcp.mdx b/sdk/guides/mcp.mdx index f854f5f5..71517d8e 100644 --- a/sdk/guides/mcp.mdx +++ b/sdk/guides/mcp.mdx @@ -1,6 +1,6 @@ --- -title: Model Context Protocol (MCP) -description: MCP enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically. +title: Model Context Protocol +description: Model Context Protocol (MCP) enables dynamic tool integration from external servers. Agents can discover and use MCP-provided tools automatically. --- MCP (Model Context Protocol) is a protocol for exposing tools and resources to AI agents. Read more [here](https://modelcontextprotocol.io/). @@ -110,9 +110,8 @@ uv run python examples/01_standalone_sdk/07_mcp_integration.py ``` -This example demonstrates: +### MCP Configuration -**MCP Configuration:** Configure MCP servers using a dictionary with server names and connection details following [this configuration format](https://gofastmcp.com/clients/client#configuration-format) ```python highlight={3-4} @@ -124,7 +123,9 @@ mcp_config = { } ``` -**Tool Filtering:** Use `filter_tools_regex` to control which MCP tools are available to the agent +### Tool Filtering + +Use `filter_tools_regex` to control which MCP tools are available to the agent ```python highlight={5} agent = Agent( @@ -221,9 +222,7 @@ cd agent-sdk uv run python examples/01_standalone_sdk/08_mcp_with_oauth.py ``` -This example demonstrates: - -**OAuth Configuration** +### OAuth Configuration - Configure OAuth-enabled MCP servers by specifying the URL and auth type - The SDK automatically handles the OAuth flow when first connecting - When the agent first attempts to use an OAuth-protected MCP server's tools, the SDK initiates the OAuth flow via [FastMCP](https://gofastmcp.com/servers/auth/authentication) From 785d5e3d46a92b588903917b472a75ac2fb8d329 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 13:51:23 -0400 Subject: [PATCH 13/46] refactor example --- sdk/guides/custom-tools.mdx | 16 +++--- sdk/guides/hello-world.mdx | 102 ++++++++++++++++++++++++++++++++++++ sdk/guides/mcp.mdx | 4 +- 3 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 sdk/guides/hello-world.mdx diff --git a/sdk/guides/custom-tools.mdx b/sdk/guides/custom-tools.mdx index c9bbebab..17767678 100644 --- a/sdk/guides/custom-tools.mdx +++ b/sdk/guides/custom-tools.mdx @@ -37,7 +37,7 @@ This example is available on GitHub: [examples/01_standalone_sdk/02_custom_tools Here's a minimal example of creating a custom grep tool: -```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools +```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools.py """Advanced example showing explicit executor usage and custom grep tool.""" import os @@ -228,7 +228,11 @@ for i, message in enumerate(llm_messages): print(f"Message {i}: {str(message)[:200]}") ``` -## Key Points +```bash Running the Example +export LLM_API_KEY="your-api-key" +cd agent-sdk +uv run python examples/01_standalone_sdk/02_custom_tools.py +``` ### Tool Registration Tools are registered using `register_tool()` and referenced by name: @@ -294,14 +298,6 @@ def to_llm_content(self) -> Sequence[TextContent | ImageContent]: return [TextContent(text=ret)] ``` -## Running the Example - -```bash -export LLM_API_KEY="your-api-key" -cd agent-sdk -uv run python examples/01_standalone_sdk/02_custom_tools.py -``` - ## When to Create Custom Tools Create custom tools when you need to: diff --git a/sdk/guides/hello-world.mdx b/sdk/guides/hello-world.mdx new file mode 100644 index 00000000..219d8d6b --- /dev/null +++ b/sdk/guides/hello-world.mdx @@ -0,0 +1,102 @@ +--- +title: Hello World +description: The simplest possible OpenHands agent - configure an LLM, create an agent, and complete a task. +--- + + +This example is available on GitHub: [examples/01_standalone_sdk/01_hello_world.py](https://github.com/All-Hands-AI/agent-sdk/blob/main/examples/01_standalone_sdk/01_hello_world.py) + + +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, 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, + service_id="agent", +) +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() +``` + +```bash Running the Example +export LLM_API_KEY="your-api-key" +cd agent-sdk +uv run python examples/01_standalone_sdk/01_hello_world.py +``` + +### LLM Configuration +Configure the language model that will power your agent: + +```python +llm = LLM( + model=model, + api_key=SecretStr(api_key), + base_url=base_url, # Optional + service_id="agent" +) +``` + +### Default Agent +Use the preset agent with common built-in tools: + +```python +agent = get_default_agent(llm=llm, cli_mode=True) +``` + +The default agent includes BashTool, FileEditorTool, etc. See [Tools Overview](/sdk/architecture/tools/overview) for the complete list of available tools. + +### Conversation +Start a conversation to manage the agent's lifecycle: + +```python +conversation = Conversation(agent=agent, workspace=cwd) +conversation.send_message("Write 3 facts about the current project into FACTS.txt.") +conversation.run() +``` + +## Expected Behavior + +When you run this example: +1. The agent analyzes the current directory +2. Gathers information about the project +3. Creates `FACTS.txt` with 3 relevant facts +4. Completes and exits + +Example output file: + +```text +FACTS.txt +--------- +1. This is a Python project using the OpenHands Agent SDK. +2. The project includes examples demonstrating various agent capabilities. +3. The SDK provides tools for file manipulation, bash execution, and more. +``` + +## Next Steps + +- **[Custom Tools](/sdk/guides/custom-tools)** - Create custom tools for specialized needs +- **[Model Context Protocol (MCP)](/sdk/guides/mcp)** - Integrate external MCP servers +- **[Security Analyzer](/sdk/guides/security-analyzer)** - Add security validation to tool usage diff --git a/sdk/guides/mcp.mdx b/sdk/guides/mcp.mdx index 71517d8e..0e471f96 100644 --- a/sdk/guides/mcp.mdx +++ b/sdk/guides/mcp.mdx @@ -14,7 +14,7 @@ This example is available on GitHub: [examples/01_standalone_sdk/07_mcp_integrat Here's an example integrating MCP servers with an agent: -```python icon="python" expandable examples/01_standalone_sdk/07_mcp_integration +```python icon="python" expandable examples/01_standalone_sdk/07_mcp_integration.py import os from pydantic import SecretStr @@ -145,7 +145,7 @@ This example is available on GitHub: [examples/01_standalone_sdk/08_mcp_with_oau For MCP servers requiring OAuth authentication: -```python icon="python" expandable examples/01_standalone_sdk/08_mcp_with_oauth +```python icon="python" expandable examples/01_standalone_sdk/08_mcp_with_oauth.py import os from pydantic import SecretStr From c33885f94ce83584556a7618a68373d97f2b9a1a Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:24:24 -0400 Subject: [PATCH 14/46] run sync docs on every push --- .github/workflows/sync-docs-code-blocks.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sync-docs-code-blocks.yml b/.github/workflows/sync-docs-code-blocks.yml index 4e9cc156..5ae92a62 100644 --- a/.github/workflows/sync-docs-code-blocks.yml +++ b/.github/workflows/sync-docs-code-blocks.yml @@ -1,6 +1,9 @@ name: Sync Documentation Code Blocks on: + push: + branches: + - '**' # run on every branch schedule: # Run daily at 2 AM UTC to catch any changes - cron: '0 2 * * *' @@ -18,6 +21,8 @@ permissions: jobs: sync-code-blocks: runs-on: ubuntu-latest + # Prevent loops when the bot's own commit triggers another push + if: github.actor != 'github-actions[bot]' steps: - name: Checkout docs repository uses: actions/checkout@v4 @@ -39,16 +44,27 @@ jobs: - name: Sync code blocks id: sync + shell: bash run: | + set -euo pipefail python .github/scripts/sync_code_blocks.py + # Expose whether any files changed + if [[ -n "$(git status --porcelain)" ]]; then + echo "changes=true" >> "$GITHUB_OUTPUT" + else + echo "changes=false" >> "$GITHUB_OUTPUT" + fi + - name: Commit and push changes if: steps.sync.outputs.changes == 'true' + shell: bash run: | + set -euo pipefail git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" git add -A - git commit -m "docs: sync code blocks from agent-sdk examples + git commit -m "docs: sync code blocks from agent-sdk examples [skip ci] Synced from agent-sdk ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }}" git push From 032d52a7a62542026d9d39b6522f8423a2d65a30 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:28:51 -0400 Subject: [PATCH 15/46] add repo.md --- .openhands/microagents/repo.md | 239 +++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 .openhands/microagents/repo.md diff --git a/.openhands/microagents/repo.md b/.openhands/microagents/repo.md new file mode 100644 index 00000000..7095e10c --- /dev/null +++ b/.openhands/microagents/repo.md @@ -0,0 +1,239 @@ +# Documentation System Overview + +The documentation for this project follows a synchronized approach where code examples in the docs are automatically kept in sync with the actual example files in the agent-sdk repository. + +## Documentation Structure + +### Dual-Repository Setup +The documentation system operates across two repositories: +1. **agent-sdk**: Contains source code and example files +2. **docs**: Contains MDX documentation files + +These repositories are designed to be checked out at the same level in the filesystem, allowing the sync script to reference both. + +### GitHub Workflow + +**Location**: `docs/.github/workflows/sync-docs-code-blocks.yml` + +**Process:** +1. Checkout docs repository +2. Checkout agent-sdk repository (sibling directory) +3. Install Python and dependencies +4. Run sync script +5. Commit and push changes if needed +6. Create PR or push directly + + +## Automatic Code Synchronization + +### How It Works + +The sync system ensures documentation code blocks match the actual source files: + +1. **Trigger**: GitHub Actions workflow runs on: + - Pull requests to docs repository + - Manual workflow dispatch + - Scheduled intervals + +2. **Scan**: Python script (`docs/.github/scripts/sync_code_blocks.py`) scans all MDX files + +3. **Extract**: Finds code blocks with file references using regex: + - Pattern: ```python icon="python" expandable examples/path/to/file.py + +4. **Compare**: Reads actual file from agent-sdk repository and compares content + +5. **Update**: If differences found, updates the code block in the MDX file + +6. **Commit**: Creates a commit with updated documentation + +### Sync Script Details + +**Location**: `docs/.github/scripts/sync_code_blocks.py` + +**Key Functions:** +- `find_mdx_files()` - Recursively finds all .mdx files +- `extract_code_blocks()` - Uses regex to find synced code blocks +- `read_source_file()` - Reads content from agent-sdk repository +- `normalize_content()` - Normalizes whitespace for comparison +- `update_doc_file()` - Updates MDX files with current source content + +**Pattern Matching:** +```python +pattern = r'```python[^\n]*\s+(examples/[^\s]+\.py)\n(.*?)```' +``` +- Captures file path: `examples/01_standalone_sdk/02_custom_tools.py` +- Captures code content between opening and closing ``` + + +## MDX Documentation Format + +### Standard Structure +Documentation files follow this pattern (see `docs/sdk/guides/custom-tools.mdx` and `docs/sdk/guides/mcp.mdx` as reference): + +1. **Frontmatter** - YAML metadata with title and description +2. **Introduction** - Brief overview of the feature +3. **Example Section** with: + - `` component linking to GitHub source + - Main code block with special syntax for auto-sync + - Bash code block showing how to run the example +4. **Detailed Explanations** - Breaking down key concepts with inline code highlights +5. **Next Steps** - Links to related documentation + +### Code Block Syntax + +#### Python Code Blocks (Auto-Synced) +```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools.py + +``` + +**Key attributes:** +- `icon="python"` - Displays Python icon in the UI +- `expandable` - Makes the code block collapsible +- `examples/01_standalone_sdk/02_custom_tools.py` - File path relative to agent-sdk root + - This path is used by the sync script to locate the source file + - Path must match the actual file location in agent-sdk repository + +You should make sure your code block follows the same format for the auto-sync between actual code and example to work. + +#### Bash Code Blocks (Manual) +```bash Running the Example +export LLM_API_KEY="your-api-key" +cd agent-sdk +uv run python examples/01_standalone_sdk/02_custom_tools.py +``` + +**Note:** The title "Running the Example" appears as a label in the rendered documentation. + +#### Inline Code Highlights +Use `highlight={line_numbers}` to emphasize specific lines: + +```python highlight={3-4} +mcp_config = { + "mcpServers": { + "fetch": {"command": "uvx", "args": ["mcp-server-fetch"]}, + "repomix": {"command": "npx", "args": ["-y", "repomix@1.4.2", "--mcp"]}, + } +} +``` + +### Note Component +Links to the GitHub source file: + +```mdx + +This example is available on GitHub: [examples/01_standalone_sdk/02_custom_tools.py](https://github.com/All-Hands-AI/agent-sdk/blob/main/examples/01_standalone_sdk/02_custom_tools.py) + +``` + +## Writing New Documentation + +### Step-by-Step Guide + +1. **Create Example File** in `agent-sdk/examples/` + ```python + # agent-sdk/examples/01_standalone_sdk/XX_new_feature.py + import os + from openhands.sdk import LLM, Agent + # ... your example code + ``` + +2. **Create MDX File** in `docs/sdk/guides/` + ```mdx + --- + title: New Feature Guide + description: How to use the new feature + --- + + ## Basic Example + + + This example is available on GitHub: [examples/01_standalone_sdk/XX_new_feature.py](...) + + + ```python icon="python" expandable examples/01_standalone_sdk/XX_new_feature.py + import os + from openhands.sdk import LLM, Agent + # ... copy initial code here + ``` + + ```bash Running the Example + export LLM_API_KEY="your-api-key" + cd agent-sdk + uv run python examples/01_standalone_sdk/XX_new_feature.py + ``` + + ### Key Concept + Explain the feature with code highlights... + ``` + +3. **Sync Will Handle Updates** + - Initial code can be copied manually or left minimal + - Sync workflow will automatically update to match actual file + - Subsequent changes to example file auto-propagate to docs + +### Best Practices + +1. **File Paths**: Always use relative paths from agent-sdk root + - ✅ `examples/01_standalone_sdk/02_custom_tools.py` + - ❌ `agent-sdk/examples/...` or absolute paths + +2. **Code Organization**: Keep example files focused and self-contained + - Include all necessary imports + - Add comments explaining key concepts + - Make examples runnable with minimal setup + +3. **Documentation Flow**: + - Start with complete working example + - Break down into sections with highlights + - Explain concepts with inline code snippets + - Link to related documentation + +4. **Consistency**: Follow existing patterns + - Use same icon/expandable syntax + - Include "Running the Example" bash block + - Add component with GitHub link + - Structure sections similarly to other guides + +5. **Testing**: Before committing documentation + - Ensure example file runs successfully + - Verify file paths are correct + - Check that code blocks render properly + - Test all links work + +## Common Patterns + +### Multi-File Examples +When an example spans multiple files, you can include multiple synced code blocks: + +```mdx +```python icon="python" expandable examples/01_standalone_sdk/main.py +# main file content +``` + +```python icon="python" expandable examples/01_standalone_sdk/helper.py +# helper file content +``` +``` + +### Partial Code Examples +For showing specific patterns without full sync, use regular code blocks: + +```mdx +```python +# This won't be auto-synced +def example_pattern(): + pass +``` +``` + +### Configuration Examples +For YAML, JSON, or other config files, use appropriate language tags: + +```yaml +name: Example Workflow +on: [push] +``` + +## Mintlify documentation + +You can check https://www.mintlify.com/docs for documentation on what our doc site supported. From 6ea863ab000c7cfb54caafe1ed21a0c75a5d711c Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:32:56 -0400 Subject: [PATCH 16/46] fix ci --- .github/workflows/sync-docs-code-blocks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-docs-code-blocks.yml b/.github/workflows/sync-docs-code-blocks.yml index 5ae92a62..a6d01866 100644 --- a/.github/workflows/sync-docs-code-blocks.yml +++ b/.github/workflows/sync-docs-code-blocks.yml @@ -33,7 +33,7 @@ jobs: uses: actions/checkout@v4 with: repository: All-Hands-AI/agent-sdk - path: agent-sdk + path: ../agent-sdk ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }} fetch-depth: 0 From 54a658cd77dfef2ec881bed7484d9ecb19c12d66 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:38:23 -0400 Subject: [PATCH 17/46] try fix workflow --- .github/scripts/sync_code_blocks.py | 202 ++++++++++---------- .github/workflows/sync-docs-code-blocks.yml | 15 +- 2 files changed, 115 insertions(+), 102 deletions(-) diff --git a/.github/scripts/sync_code_blocks.py b/.github/scripts/sync_code_blocks.py index 1f6b4ebf..2ba2e759 100755 --- a/.github/scripts/sync_code_blocks.py +++ b/.github/scripts/sync_code_blocks.py @@ -14,66 +14,55 @@ import re import sys from pathlib import Path -from typing import List, Tuple, Optional -def find_mdx_files(docs_path: Path) -> List[Path]: +def find_mdx_files(docs_path: Path) -> list[Path]: """Find all MDX files in the docs directory.""" - mdx_files = [] - for root, dirs, files in os.walk(docs_path): + mdx_files: list[Path] = [] + for root, _, files in os.walk(docs_path): for file in files: - if file.endswith('.mdx'): + if file.endswith(".mdx"): mdx_files.append(Path(root) / file) return mdx_files -def extract_code_blocks(content: str) -> List[Tuple[str, str, int, int]]: +def extract_code_blocks(content: str) -> list[tuple[str, str, int, int]]: """ Extract code blocks that reference source files. - + Returns list of tuples: (file_reference, code_content, start_pos, end_pos) - + Pattern matches blocks like: ```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools.py ``` """ - # Pattern to match code blocks with file references - # Captures the file path and the code content + # Captures examples/...*.py after the first line, then the body up to ``` pattern = r'```python[^\n]*\s+(examples/[^\s]+\.py)\n(.*?)```' - - matches = [] + matches: list[tuple[str, str, int, int]] = [] for match in re.finditer(pattern, content, re.DOTALL): file_ref = match.group(1) code_content = match.group(2) start_pos = match.start() end_pos = match.end() matches.append((file_ref, code_content, start_pos, end_pos)) - return matches -def read_source_file(agent_sdk_path: Path, file_ref: str) -> Optional[str]: +def read_source_file(agent_sdk_path: Path, file_ref: str) -> str | None: """ Read the actual source file content. - + Args: agent_sdk_path: Path to agent-sdk repository file_ref: File reference like "examples/01_standalone_sdk/02_custom_tools.py" - - Returns: - File content or None if file not found """ - # file_ref is already the full path relative to agent-sdk root source_path = agent_sdk_path / file_ref - if not source_path.exists(): print(f"Warning: Source file not found: {source_path}") return None - try: - with open(source_path, 'r', encoding='utf-8') as f: - return f.read() + return source_path.read_text(encoding="utf-8") except Exception as e: print(f"Error reading {source_path}: {e}") return None @@ -81,136 +70,157 @@ def read_source_file(agent_sdk_path: Path, file_ref: str) -> Optional[str]: def normalize_content(content: str) -> str: """Normalize content for comparison (remove trailing whitespace, normalize line endings).""" - # Split into lines, strip trailing whitespace from each line, rejoin - lines = [line.rstrip() for line in content.splitlines()] - return '\n'.join(lines) + return "\n".join(line.rstrip() for line in content.splitlines()) -def update_doc_file(doc_path: Path, content: str, code_blocks: List[Tuple[str, str, int, int]], - agent_sdk_path: Path) -> bool: +def resolve_paths() -> tuple[Path, Path]: + """ + Determine docs root and agent-sdk path robustly across CI and local layouts. + Priority for agent-sdk path: + 1) AGENT_SDK_PATH (env override) + 2) $GITHUB_WORKSPACE/agent-sdk + 3) docs_root/'agent-sdk' + 4) docs_root.parent/'agent-sdk' (legacy) + """ + # docs repo root (script is at docs/.github/scripts/sync_code_blocks.py) + script_file = Path(__file__).resolve() + docs_root = script_file.parent.parent.parent + + candidates: list[Path] = [] + + # 1) Explicit env override + env_override = os.environ.get("AGENT_SDK_PATH") + if env_override: + candidates.append(Path(env_override).expanduser().resolve()) + + # 2) Standard GitHub workspace sibling + gh_ws = os.environ.get("GITHUB_WORKSPACE") + if gh_ws: + candidates.append(Path(gh_ws).resolve() / "agent-sdk") + + # 3) Sibling inside the docs repo root + candidates.append(docs_root / "agent-sdk") + + # 4) Legacy parent-of-docs-root layout + candidates.append(docs_root.parent / "agent-sdk") + + print(f"🔍 Scanning for MDX files in {docs_root}") + print("🔎 Trying agent-sdk paths (in order):") + for p in candidates: + print(f" - {p}") + + for p in candidates: + if p.exists(): + print(f"📁 Using Agent SDK path: {p}") + return docs_root, p + + # If none exist, fail with a helpful message + print("❌ Agent SDK path not found in any of the expected locations.") + print(" Set AGENT_SDK_PATH, or checkout the repo to one of the tried paths above.") + sys.exit(1) + + +def update_doc_file( + doc_path: Path, + content: str, + code_blocks: list[tuple[str, str, int, int]], + agent_sdk_path: Path, +) -> bool: """ Update documentation file with correct code blocks. - + Returns True if changes were made, False otherwise. """ changes_made = False new_content = content offset = 0 # Track offset due to content changes - + for file_ref, old_code, start_pos, end_pos in code_blocks: - # Read actual source file actual_content = read_source_file(agent_sdk_path, file_ref) - if actual_content is None: continue - - # Normalize both for comparison + old_normalized = normalize_content(old_code) actual_normalized = normalize_content(actual_content) - + if old_normalized != actual_normalized: print(f"\n📝 Found difference in {doc_path.name} for {file_ref}") - print(f" Updating code block...") - - # Calculate adjusted positions + print(" Updating code block...") + adj_start = start_pos + offset adj_end = end_pos + offset - - # Extract the opening line (```python ... file_ref) - opening_line_match = re.search(r'```python[^\n]*\s+' + re.escape(file_ref), - new_content[adj_start:adj_end]) + + opening_line_match = re.search( + r"```python[^\n]*\s+" + re.escape(file_ref), + new_content[adj_start:adj_end], + ) if opening_line_match: opening_line = opening_line_match.group(0) - - # Construct new code block (don't add extra newline if content already ends with one) - if actual_content.endswith('\n'): + # Preserve trailing newline behavior + if actual_content.endswith("\n"): new_block = f"{opening_line}\n{actual_content}```" else: new_block = f"{opening_line}\n{actual_content}\n```" old_block = new_content[adj_start:adj_end] - - # Replace in content + new_content = new_content[:adj_start] + new_block + new_content[adj_end:] - - # Update offset offset += len(new_block) - len(old_block) changes_made = True - + if changes_made: try: - with open(doc_path, 'w', encoding='utf-8') as f: - f.write(new_content) + doc_path.write_text(new_content, encoding="utf-8") print(f"✅ Updated {doc_path}") return True except Exception as e: print(f"❌ Error writing {doc_path}: {e}") return False - + return False -def main(): - # Script is in docs/.github/scripts/ - script_dir = Path(__file__).parent.parent.parent # docs root - # agent-sdk is at the same level as docs, not inside docs - agent_sdk_path = script_dir.parent / 'agent-sdk' - - print(f"🔍 Scanning for MDX files in {script_dir}") - print(f"📁 Agent SDK path: {agent_sdk_path}") - - if not agent_sdk_path.exists(): - print(f"❌ Agent SDK path does not exist: {agent_sdk_path}") - print(f" Make sure agent-sdk repository is checked out at the same level as docs directory") - sys.exit(1) - +def main() -> None: + docs_root, agent_sdk_path = resolve_paths() + # Find all MDX files - mdx_files = find_mdx_files(script_dir) + mdx_files = find_mdx_files(docs_root) print(f"📄 Found {len(mdx_files)} MDX files") - + total_changes = 0 - files_changed = [] - - # Process each MDX file + files_changed: list[str] = [] + for mdx_file in mdx_files: try: - with open(mdx_file, 'r', encoding='utf-8') as f: - content = f.read() - - # Extract code blocks + content = mdx_file.read_text(encoding="utf-8") code_blocks = extract_code_blocks(content) - if not code_blocks: continue - - print(f"\n📋 Processing {mdx_file.relative_to(script_dir)}") + + print(f"\n📋 Processing {mdx_file.relative_to(docs_root)}") print(f" Found {len(code_blocks)} code block(s) with file references") - - # Update file if needed - if update_doc_file(mdx_file, content, code_blocks, agent_sdk_path): + + if update_doc_file(mdxx_file := mdx_file, content=content, code_blocks=code_blocks, agent_sdk_path=agent_sdk_path): total_changes += 1 - files_changed.append(str(mdx_file.relative_to(script_dir))) - + files_changed.append(str(mdxx_file.relative_to(docs_root))) except Exception as e: print(f"❌ Error processing {mdx_file}: {e}") continue - - # Summary - print("\n" + "="*60) + + print("\n" + "=" * 60) if total_changes > 0: print(f"✅ Updated {total_changes} file(s):") for file in files_changed: print(f" - {file}") - # Set output for GitHub Actions - if 'GITHUB_OUTPUT' in os.environ: - with open(os.environ['GITHUB_OUTPUT'], 'a') as f: - f.write('changes=true\n') + if "GITHUB_OUTPUT" in os.environ: + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: + f.write("changes=true\n") else: print("✅ All code blocks are in sync!") - if 'GITHUB_OUTPUT' in os.environ: - with open(os.environ['GITHUB_OUTPUT'], 'a') as f: - f.write('changes=false\n') - print("="*60) + if "GITHUB_OUTPUT" in os.environ: + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: + f.write("changes=false\n") + print("=" * 60) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/.github/workflows/sync-docs-code-blocks.yml b/.github/workflows/sync-docs-code-blocks.yml index a6d01866..4a7f5d9a 100644 --- a/.github/workflows/sync-docs-code-blocks.yml +++ b/.github/workflows/sync-docs-code-blocks.yml @@ -24,19 +24,22 @@ jobs: # Prevent loops when the bot's own commit triggers another push if: github.actor != 'github-actions[bot]' steps: - - name: Checkout docs repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Checkout agent-sdk uses: actions/checkout@v4 with: repository: All-Hands-AI/agent-sdk - path: ../agent-sdk + path: agent-sdk ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }} fetch-depth: 0 + - name: Sync code blocks + id: sync + env: + # Optional, explicit override (script will also auto-detect without this) + AGENT_SDK_PATH: ${{ github.workspace }}/agent-sdk + run: | + python .github/scripts/sync_code_blocks.py + - name: Set up Python uses: actions/setup-python@v5 with: From ce42986a0e2e8171331ee113711331f625056d9a Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:43:11 -0400 Subject: [PATCH 18/46] refactor --- docs.json | 38 ++++++++++++++++++++++++--- sdk/{ => arch}/llms/configuration.mdx | 0 sdk/{ => arch}/llms/index.mdx | 0 sdk/{ => arch}/llms/providers.mdx | 0 4 files changed, 34 insertions(+), 4 deletions(-) rename sdk/{ => arch}/llms/configuration.mdx (100%) rename sdk/{ => arch}/llms/index.mdx (100%) rename sdk/{ => arch}/llms/providers.mdx (100%) diff --git a/docs.json b/docs.json index cb8a661b..238cb4d3 100644 --- a/docs.json +++ b/docs.json @@ -177,12 +177,42 @@ "tab": "Agent SDK (v1)", "pages": [ "sdk/index", + "sdk/getting-started", { - "group": "Language Models", + "group": "Guides", "pages": [ - "sdk/llms/index", - "sdk/llms/configuration", - "sdk/llms/providers" + { + "group": "Python API", + "pages": [ + "sdk/guides/hello-world", + "sdk/guides/custom-tools", + "sdk/guides/mcp" + ] + }, + { + "group": "Remote Agent Server", + "pages": [ + ] + }, + { + "group": "GitHub Workflows", + "pages": [ + ] + } + ] + }, + { + "group": "Architecture", + "pages": [ + "sdk/arch/overview", + { + "group": "Language Models", + "pages": [ + "sdk/arch/llms/index", + "sdk/arch/llms/configuration", + "sdk/arch/llms/providers" + ] + } ] } ] diff --git a/sdk/llms/configuration.mdx b/sdk/arch/llms/configuration.mdx similarity index 100% rename from sdk/llms/configuration.mdx rename to sdk/arch/llms/configuration.mdx diff --git a/sdk/llms/index.mdx b/sdk/arch/llms/index.mdx similarity index 100% rename from sdk/llms/index.mdx rename to sdk/arch/llms/index.mdx diff --git a/sdk/llms/providers.mdx b/sdk/arch/llms/providers.mdx similarity index 100% rename from sdk/llms/providers.mdx rename to sdk/arch/llms/providers.mdx From f8bae5e3798448a130e534a04acaab15ec0cc0f4 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:44:14 -0400 Subject: [PATCH 19/46] move workflow --- .github/{ => workflows}/sync-sdk-changes.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/sync-sdk-changes.yml (100%) diff --git a/.github/sync-sdk-changes.yml b/.github/workflows/sync-sdk-changes.yml similarity index 100% rename from .github/sync-sdk-changes.yml rename to .github/workflows/sync-sdk-changes.yml From 7c1b0d3f0a3637039946451f747d6e2833dc3555 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:48:54 -0400 Subject: [PATCH 20/46] remove overview first --- docs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/docs.json b/docs.json index 238cb4d3..b7277a22 100644 --- a/docs.json +++ b/docs.json @@ -204,7 +204,6 @@ { "group": "Architecture", "pages": [ - "sdk/arch/overview", { "group": "Language Models", "pages": [ From f80e372354bd55949855e5ecb8a11a4bc20bf324 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:49:44 -0400 Subject: [PATCH 21/46] reorg tests --- docs.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs.json b/docs.json index b7277a22..b72219e2 100644 --- a/docs.json +++ b/docs.json @@ -181,14 +181,9 @@ { "group": "Guides", "pages": [ - { - "group": "Python API", - "pages": [ - "sdk/guides/hello-world", - "sdk/guides/custom-tools", - "sdk/guides/mcp" - ] - }, + "sdk/guides/hello-world", + "sdk/guides/custom-tools", + "sdk/guides/mcp", { "group": "Remote Agent Server", "pages": [ From 30684b5977cf5729f2e127cab89a6ae0729c0e4c Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:52:42 -0400 Subject: [PATCH 22/46] fix bug --- .github/workflows/sync-docs-code-blocks.yml | 22 +++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/sync-docs-code-blocks.yml b/.github/workflows/sync-docs-code-blocks.yml index 4a7f5d9a..c0f44495 100644 --- a/.github/workflows/sync-docs-code-blocks.yml +++ b/.github/workflows/sync-docs-code-blocks.yml @@ -21,9 +21,13 @@ permissions: jobs: sync-code-blocks: runs-on: ubuntu-latest - # Prevent loops when the bot's own commit triggers another push if: github.actor != 'github-actions[bot]' steps: + - name: Checkout docs repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Checkout agent-sdk uses: actions/checkout@v4 with: @@ -32,27 +36,19 @@ jobs: ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }} fetch-depth: 0 - - name: Sync code blocks - id: sync - env: - # Optional, explicit override (script will also auto-detect without this) - AGENT_SDK_PATH: ${{ github.workspace }}/agent-sdk - run: | - python .github/scripts/sync_code_blocks.py - - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Sync code blocks - id: sync + id: detect_changes + env: + AGENT_SDK_PATH: ${{ github.workspace }}/agent-sdk shell: bash run: | set -euo pipefail python .github/scripts/sync_code_blocks.py - - # Expose whether any files changed if [[ -n "$(git status --porcelain)" ]]; then echo "changes=true" >> "$GITHUB_OUTPUT" else @@ -60,7 +56,7 @@ jobs: fi - name: Commit and push changes - if: steps.sync.outputs.changes == 'true' + if: steps.detect_changes.outputs.changes == 'true' # <-- updated reference shell: bash run: | set -euo pipefail From b5b7c2e8ad90e0a18904fee3cca18c4c1c100cb0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Oct 2025 18:52:56 +0000 Subject: [PATCH 23/46] docs: sync code blocks from agent-sdk examples [skip ci] Synced from agent-sdk ref: main --- agent-sdk | 1 + sdk/guides/custom-tools.mdx | 2 +- sdk/guides/hello-world.mdx | 2 +- sdk/guides/mcp.mdx | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) create mode 160000 agent-sdk diff --git a/agent-sdk b/agent-sdk new file mode 160000 index 00000000..53ed9bb6 --- /dev/null +++ b/agent-sdk @@ -0,0 +1 @@ +Subproject commit 53ed9bb631943008d2e15f669656aceb9092d34b diff --git a/sdk/guides/custom-tools.mdx b/sdk/guides/custom-tools.mdx index 17767678..6ab533df 100644 --- a/sdk/guides/custom-tools.mdx +++ b/sdk/guides/custom-tools.mdx @@ -161,7 +161,7 @@ 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( - service_id="agent", + usage_id="agent", model=model, base_url=base_url, api_key=SecretStr(api_key), diff --git a/sdk/guides/hello-world.mdx b/sdk/guides/hello-world.mdx index 219d8d6b..d33e6c11 100644 --- a/sdk/guides/hello-world.mdx +++ b/sdk/guides/hello-world.mdx @@ -28,7 +28,7 @@ llm = LLM( model=model, api_key=SecretStr(api_key), base_url=base_url, - service_id="agent", + usage_id="agent", ) agent = get_default_agent(llm=llm, cli_mode=True) diff --git a/sdk/guides/mcp.mdx b/sdk/guides/mcp.mdx index 0e471f96..7f813855 100644 --- a/sdk/guides/mcp.mdx +++ b/sdk/guides/mcp.mdx @@ -41,7 +41,7 @@ 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( - service_id="agent", + usage_id="agent", model=model, base_url=base_url, api_key=SecretStr(api_key), @@ -171,7 +171,7 @@ 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( - service_id="agent", + usage_id="agent", model=model, base_url=base_url, api_key=SecretStr(api_key), From b4873875369cd8980306a6885528ed8b95cb20ad Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 14:55:10 -0400 Subject: [PATCH 24/46] still run ci --- .github/workflows/sync-docs-code-blocks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-docs-code-blocks.yml b/.github/workflows/sync-docs-code-blocks.yml index c0f44495..e4ac2fc0 100644 --- a/.github/workflows/sync-docs-code-blocks.yml +++ b/.github/workflows/sync-docs-code-blocks.yml @@ -63,7 +63,7 @@ jobs: git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" git add -A - git commit -m "docs: sync code blocks from agent-sdk examples [skip ci] + git commit -m "docs: sync code blocks from agent-sdk examples Synced from agent-sdk ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }}" git push From 78710ef8b6d90dd660d6aa0d85861100d1fad2d4 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 15:02:31 -0400 Subject: [PATCH 25/46] add workflow to automatically sync openapi --- .github/workflows/sync-agent-sdk-openapi.yml | 90 ++++++++++++++++++++ .github/workflows/sync-sdk-changes.yml | 67 --------------- 2 files changed, 90 insertions(+), 67 deletions(-) create mode 100644 .github/workflows/sync-agent-sdk-openapi.yml delete mode 100644 .github/workflows/sync-sdk-changes.yml diff --git a/.github/workflows/sync-agent-sdk-openapi.yml b/.github/workflows/sync-agent-sdk-openapi.yml new file mode 100644 index 00000000..b2cb42c0 --- /dev/null +++ b/.github/workflows/sync-agent-sdk-openapi.yml @@ -0,0 +1,90 @@ +name: Sync agent-sdk OpenAPI + +on: + push: + branches: + - '**' # run on every branch + schedule: + - cron: '0 2 * * *' # nightly catch-up + workflow_dispatch: + inputs: + agent_sdk_ref: + description: 'Agent SDK branch/tag/commit to generate from' + required: false + default: 'main' + +permissions: + contents: write + +jobs: + sync-openapi: + runs-on: ubuntu-latest + # Avoid infinite loops on our own commits + if: github.actor != 'github-actions[bot]' + + env: + # Prefer a PAT if agent-sdk is private; otherwise GITHUB_TOKEN is fine. + GH_CLONE_TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }} + # For workflow_dispatch this will be set; for push/schedule it will fall back to main. + AGENT_SDK_REF: ${{ github.event.inputs.agent_sdk_ref || 'main' }} + + steps: + - name: Checkout docs repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: true + + - name: Checkout agent-sdk + uses: actions/checkout@v4 + with: + repository: All-Hands-AI/agent-sdk + path: agent-sdk + ref: ${{ env.AGENT_SDK_REF }} + fetch-depth: 0 + # If private, uncomment: + # token: ${{ env.GH_CLONE_TOKEN }} + + - name: Configure Git + run: | + git config user.name "all-hands-bot" + git config user.email "contact@all-hands.dev" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + + - name: Generate OpenAPI spec + working-directory: agent-sdk + env: + SCHEMA_PATH: ${{ github.workspace }}/openapi/agent-sdk.json + run: | + set -euo pipefail + make build + mkdir -p "$(dirname "$SCHEMA_PATH")" + uv run python openhands/agent_server/openapi.py + + - name: Detect changes + id: detect_changes + run: | + if [[ -n "$(git status --porcelain)" ]]; then + echo "changes=true" >> "$GITHUB_OUTPUT" + else + echo "changes=false" >> "$GITHUB_OUTPUT" + fi + + - name: Commit and push OpenAPI spec + if: steps.detect_changes.outputs.changes == 'true' + run: | + set -euo pipefail + git add openapi/agent-sdk.json + # Re-check in case only unrelated files changed + if git diff --cached --quiet; then + echo "No OpenAPI changes to commit." + exit 0 + fi + + SHA_SHORT="$(git -C agent-sdk rev-parse --short=7 HEAD || echo manual)" + BRANCH="${AGENT_SDK_REF:-main}" + + git commit -m "sync(openapi): agent-sdk/${BRANCH} ${SHA_SHORT}" + git push diff --git a/.github/workflows/sync-sdk-changes.yml b/.github/workflows/sync-sdk-changes.yml deleted file mode 100644 index c5d762d2..00000000 --- a/.github/workflows/sync-sdk-changes.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Sync agent-sdk changes - -on: - repository_dispatch: - types: [update] - workflow_dispatch: - inputs: - -permissions: - contents: write - -jobs: - update: - runs-on: ubuntu-latest - env: - TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }} - steps: - - name: Checkout docs repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: true - - - name: Configure Git - run: | - git config user.name "all-hands-bot" - git config user.email "contact@all-hands.dev" - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Clone agent-sdk - env: - TOKEN: ${{ env.TOKEN }} - run: | - # Get source branch from payload or default to main - SOURCE_BRANCH="${{ github.event.client_payload.branch || 'main' }}" - - tmpdir="$(mktemp -d)" - echo "Cloning agent-sdk from branch: $SOURCE_BRANCH to $tmpdir" - - auth_url="https://x-access-token:${TOKEN}@github.com/${{ github.repository_owner }}/agent-sdk.git" - git clone --depth=1 --branch="$SOURCE_BRANCH" "$auth_url" "$tmpdir/agent-sdk" - - echo "SDK_DIR=$tmpdir/agent-sdk" >> "$GITHUB_ENV" - echo "SOURCE_BRANCH=$SOURCE_BRANCH" >> "$GITHUB_ENV" - - - name: Generate OpenAPI spec - run: | - cd "$SDK_DIR" - make build - mkdir -p "$GITHUB_WORKSPACE/openapi" - SCHEMA_PATH="$GITHUB_WORKSPACE/openapi/agent-sdk.json" \ - uv run python openhands/agent_server/openapi.py - - - name: Commit and push OpenAPI spec - run: | - git add openapi/agent-sdk.json - - if git diff --cached --quiet; then - echo "No OpenAPI changes to commit." - else - SHA="${{ github.event.client_payload.sha || 'manual' }}" - BRANCH="${SOURCE_BRANCH:-main}" - git commit -m "sync(openapi): agent-sdk/$BRANCH ${SHA:0:7}" - git push origin - fi From 01ff5e191eebadd10d5102e932236035cce794e0 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 15:05:59 -0400 Subject: [PATCH 26/46] remove inaccurate stuff --- .openhands/microagents/repo.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.openhands/microagents/repo.md b/.openhands/microagents/repo.md index 7095e10c..3510965f 100644 --- a/.openhands/microagents/repo.md +++ b/.openhands/microagents/repo.md @@ -2,27 +2,6 @@ The documentation for this project follows a synchronized approach where code examples in the docs are automatically kept in sync with the actual example files in the agent-sdk repository. -## Documentation Structure - -### Dual-Repository Setup -The documentation system operates across two repositories: -1. **agent-sdk**: Contains source code and example files -2. **docs**: Contains MDX documentation files - -These repositories are designed to be checked out at the same level in the filesystem, allowing the sync script to reference both. - -### GitHub Workflow - -**Location**: `docs/.github/workflows/sync-docs-code-blocks.yml` - -**Process:** -1. Checkout docs repository -2. Checkout agent-sdk repository (sibling directory) -3. Install Python and dependencies -4. Run sync script -5. Commit and push changes if needed -6. Create PR or push directly - ## Automatic Code Synchronization From 7bccf46b4f8448adbddaebb8993309dc5f202055 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 15:07:07 -0400 Subject: [PATCH 27/46] fix path --- .github/workflows/sync-agent-sdk-openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-agent-sdk-openapi.yml b/.github/workflows/sync-agent-sdk-openapi.yml index b2cb42c0..39274a34 100644 --- a/.github/workflows/sync-agent-sdk-openapi.yml +++ b/.github/workflows/sync-agent-sdk-openapi.yml @@ -61,7 +61,7 @@ jobs: set -euo pipefail make build mkdir -p "$(dirname "$SCHEMA_PATH")" - uv run python openhands/agent_server/openapi.py + uv run python openhands-agent-server/openhands/agent_server/openapi.py - name: Detect changes id: detect_changes From 85567ef5cce72abbed8d9578750bbcaa59d5eb7a Mon Sep 17 00:00:00 2001 From: all-hands-bot Date: Mon, 20 Oct 2025 19:07:42 +0000 Subject: [PATCH 28/46] sync(openapi): agent-sdk/main 53ed9bb --- openapi/agent-sdk.json | 6278 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 6278 insertions(+) create mode 100644 openapi/agent-sdk.json diff --git a/openapi/agent-sdk.json b/openapi/agent-sdk.json new file mode 100644 index 00000000..e499decb --- /dev/null +++ b/openapi/agent-sdk.json @@ -0,0 +1,6278 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenHands Agent Server", + "description": "OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent", + "version": "0.1.0" + }, + "paths": { + "/alive": { + "get": { + "tags": [ + "Server Details" + ], + "summary": "Alive", + "operationId": "alive_alive_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/health": { + "get": { + "tags": [ + "Server Details" + ], + "summary": "Health", + "operationId": "health_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "string", + "title": "Response Health Health Get" + } + } + } + } + } + } + }, + "/server_info": { + "get": { + "tags": [ + "Server Details" + ], + "summary": "Get Server Info", + "operationId": "get_server_info_server_info_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerInfo" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/events/search": { + "get": { + "tags": [ + "Events" + ], + "summary": "Search Conversation Events", + "description": "Search / List local events", + "operationId": "search_conversation_events_api_conversations__conversation_id__events_search_get", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + }, + { + "name": "page_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Optional next_page_id from the previously returned page" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "The max number of results in the page", + "lte": 100, + "default": 100 + } + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Optional filter by event kind/type (e.g., ActionEvent, MessageEvent)" + } + }, + { + "name": "sort_order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/EventSortOrder", + "title": "Sort order for events", + "default": "TIMESTAMP" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventPage" + } + } + } + }, + "404": { + "description": "Conversation not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/events/count": { + "get": { + "tags": [ + "Events" + ], + "summary": "Count Conversation Events", + "description": "Count local events matching the given filters", + "operationId": "count_conversation_events_api_conversations__conversation_id__events_count_get", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Optional filter by event kind/type (e.g., ActionEvent, MessageEvent)" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "integer", + "title": "Response Count Conversation Events Api Conversations Conversation Id Events Count Get" + } + } + } + }, + "404": { + "description": "Conversation not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/events/{event_id}": { + "get": { + "tags": [ + "Events" + ], + "summary": "Get Conversation Event", + "description": "Get a local event given an id", + "operationId": "get_conversation_event_api_conversations__conversation_id__events__event_id__get", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + }, + { + "name": "event_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Event Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Condensation" + }, + { + "$ref": "#/components/schemas/CondensationRequest" + }, + { + "$ref": "#/components/schemas/CondensationSummaryEvent" + }, + { + "$ref": "#/components/schemas/ConversationStateUpdateEvent" + }, + { + "$ref": "#/components/schemas/ActionEvent" + }, + { + "$ref": "#/components/schemas/MessageEvent" + }, + { + "$ref": "#/components/schemas/AgentErrorEvent" + }, + { + "$ref": "#/components/schemas/ObservationEvent" + }, + { + "$ref": "#/components/schemas/UserRejectObservation" + }, + { + "$ref": "#/components/schemas/SystemPromptEvent" + }, + { + "$ref": "#/components/schemas/PauseEvent" + } + ], + "title": "Response Get Conversation Event Api Conversations Conversation Id Events Event Id Get", + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__event__condenser__Condensation-Output__1": "#/components/schemas/Condensation", + "openhands__sdk__event__condenser__CondensationRequest-Output__1": "#/components/schemas/CondensationRequest", + "openhands__sdk__event__condenser__CondensationSummaryEvent-Output__1": "#/components/schemas/CondensationSummaryEvent", + "openhands__sdk__event__conversation_state__ConversationStateUpdateEvent-Output__1": "#/components/schemas/ConversationStateUpdateEvent", + "openhands__sdk__event__llm_convertible__action__ActionEvent-Output__1": "#/components/schemas/ActionEvent", + "openhands__sdk__event__llm_convertible__message__MessageEvent-Output__1": "#/components/schemas/MessageEvent", + "openhands__sdk__event__llm_convertible__observation__AgentErrorEvent-Output__1": "#/components/schemas/AgentErrorEvent", + "openhands__sdk__event__llm_convertible__observation__ObservationEvent-Output__1": "#/components/schemas/ObservationEvent", + "openhands__sdk__event__llm_convertible__observation__UserRejectObservation-Output__1": "#/components/schemas/UserRejectObservation", + "openhands__sdk__event__llm_convertible__system__SystemPromptEvent-Output__1": "#/components/schemas/SystemPromptEvent", + "openhands__sdk__event__user_action__PauseEvent-Output__1": "#/components/schemas/PauseEvent" + } + } + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/events": { + "get": { + "tags": [ + "Events" + ], + "summary": "Batch Get Conversation Events", + "description": "Get a batch of local events given their ids, returning null for any\nmissing item.", + "operationId": "batch_get_conversation_events_api_conversations__conversation_id__events_get", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Event Ids" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/Event" + }, + { + "type": "null" + } + ] + }, + "title": "Response Batch Get Conversation Events Api Conversations Conversation Id Events Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "Events" + ], + "summary": "Send Message", + "description": "Send a message to a conversation", + "operationId": "send_message_api_conversations__conversation_id__events_post", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendMessageRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/events/respond_to_confirmation": { + "post": { + "tags": [ + "Events" + ], + "summary": "Respond To Confirmation", + "description": "Accept or reject a pending action in confirmation mode.", + "operationId": "respond_to_confirmation_api_conversations__conversation_id__events_respond_to_confirmation_post", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfirmationResponseRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/search": { + "get": { + "tags": [ + "Conversations" + ], + "summary": "Search Conversations", + "description": "Search / List conversations", + "operationId": "search_conversations_api_conversations_search_get", + "parameters": [ + { + "name": "page_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Optional next_page_id from the previously returned page" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "The max number of results in the page", + "lte": 100, + "default": 100 + } + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/AgentExecutionStatus" + }, + { + "type": "null" + } + ], + "title": "Optional filter by agent execution status" + } + }, + { + "name": "sort_order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/ConversationSortOrder", + "title": "Sort order for conversations", + "default": "CREATED_AT_DESC" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationPage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/count": { + "get": { + "tags": [ + "Conversations" + ], + "summary": "Count Conversations", + "description": "Count conversations matching the given filters", + "operationId": "count_conversations_api_conversations_count_get", + "parameters": [ + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/AgentExecutionStatus" + }, + { + "type": "null" + } + ], + "title": "Optional filter by agent execution status" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "integer", + "title": "Response Count Conversations Api Conversations Count Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}": { + "get": { + "tags": [ + "Conversations" + ], + "summary": "Get Conversation", + "description": "Given an id, get a conversation", + "operationId": "get_conversation_api_conversations__conversation_id__get", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationInfo" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Conversations" + ], + "summary": "Delete Conversation", + "description": "Permanently delete a conversation.", + "operationId": "delete_conversation_api_conversations__conversation_id__delete", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Conversations" + ], + "summary": "Update Conversation", + "description": "Update conversation metadata.\n\nThis endpoint allows updating conversation details like title.", + "operationId": "update_conversation_api_conversations__conversation_id__patch", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateConversationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations": { + "get": { + "tags": [ + "Conversations" + ], + "summary": "Batch Get Conversations", + "description": "Get a batch of conversations given their ids, returning null for\nany missing item", + "operationId": "batch_get_conversations_api_conversations_get", + "parameters": [ + { + "name": "ids", + "in": "query", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "title": "Ids" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ConversationInfo" + }, + { + "type": "null" + } + ] + }, + "title": "Response Batch Get Conversations Api Conversations Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "Conversations" + ], + "summary": "Start Conversation", + "description": "Start a conversation in the local environment.", + "operationId": "start_conversation_api_conversations_post", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StartConversationRequest", + "examples": [ + { + "agent": { + "llm": { + "model": "your-model-provider/your-model-name", + "api_key": "**********", + "reasoning_effort": "high", + "usage_id": "your-llm-service" + }, + "tools": [ + { + "name": "BashTool" + }, + { + "name": "FileEditorTool" + }, + { + "name": "TaskTrackerTool" + } + ] + }, + "workspace": { + "working_dir": "workspace/project" + }, + "initial_message": { + "content": [ + { + "text": "Flip a coin!" + } + ] + } + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationInfo" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/pause": { + "post": { + "tags": [ + "Conversations" + ], + "summary": "Pause Conversation", + "description": "Pause a conversation, allowing it to be resumed later.", + "operationId": "pause_conversation_api_conversations__conversation_id__pause_post", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/run": { + "post": { + "tags": [ + "Conversations" + ], + "summary": "Run Conversation", + "description": "Start running the conversation in the background.", + "operationId": "run_conversation_api_conversations__conversation_id__run_post", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "409": { + "description": "Conversation is already running" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/secrets": { + "post": { + "tags": [ + "Conversations" + ], + "summary": "Update Conversation Secrets", + "description": "Update secrets for a conversation.", + "operationId": "update_conversation_secrets_api_conversations__conversation_id__secrets_post", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateSecretsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/confirmation_policy": { + "post": { + "tags": [ + "Conversations" + ], + "summary": "Set Conversation Confirmation Policy", + "description": "Set the confirmation policy for a conversation.", + "operationId": "set_conversation_confirmation_policy_api_conversations__conversation_id__confirmation_policy_post", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetConfirmationPolicyRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/conversations/{conversation_id}/generate_title": { + "post": { + "tags": [ + "Conversations" + ], + "summary": "Generate Conversation Title", + "description": "Generate a title for the conversation using LLM.", + "operationId": "generate_conversation_title_api_conversations__conversation_id__generate_title_post", + "parameters": [ + { + "name": "conversation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Conversation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateTitleRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateTitleResponse" + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/tools/": { + "get": { + "tags": [ + "Tools" + ], + "summary": "List Available Tools", + "description": "List all available tools.", + "operationId": "list_available_tools_api_tools__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Response List Available Tools Api Tools Get" + } + } + } + } + } + } + }, + "/api/bash/bash_events/search": { + "get": { + "tags": [ + "Bash" + ], + "summary": "Search Bash Events", + "description": "Search / List bash event events", + "operationId": "search_bash_events_api_bash_bash_events_search_get", + "parameters": [ + { + "name": "kind__eq", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "enum": [ + "BashCommand", + "BashOutput" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Kind Eq" + } + }, + { + "name": "command_id__eq", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Command Id Eq" + } + }, + { + "name": "timestamp__gte", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Timestamp Gte" + } + }, + { + "name": "timestamp__lt", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Timestamp Lt" + } + }, + { + "name": "sort_order", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/BashEventSortOrder", + "default": "TIMESTAMP" + } + }, + { + "name": "page_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Optional next_page_id from the previously returned page" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "exclusiveMinimum": 0, + "title": "The max number of results in the page", + "lte": 100, + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BashEventPage" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/bash/bash_events/{event_id}": { + "get": { + "tags": [ + "Bash" + ], + "summary": "Get Bash Event", + "description": "Get a bash event event given an id", + "operationId": "get_bash_event_api_bash_bash_events__event_id__get", + "parameters": [ + { + "name": "event_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Event Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/BashCommand" + }, + { + "$ref": "#/components/schemas/BashOutput" + } + ], + "title": "Response Get Bash Event Api Bash Bash Events Event Id Get", + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__agent_server__models__BashCommand-Output__1": "#/components/schemas/BashCommand", + "openhands__agent_server__models__BashOutput-Output__1": "#/components/schemas/BashOutput" + } + } + } + } + } + }, + "404": { + "description": "Item not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/bash/bash_events/": { + "get": { + "tags": [ + "Bash" + ], + "summary": "Batch Get Bash Events", + "description": "Get a batch of bash event events given their ids, returning null for any\nmissing item.", + "operationId": "batch_get_bash_events_api_bash_bash_events__get", + "requestBody": { + "content": { + "application/json": { + "schema": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Event Ids" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/BashEventBase" + }, + { + "type": "null" + } + ] + }, + "type": "array", + "title": "Response Batch Get Bash Events Api Bash Bash Events Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/bash/execute_bash_command": { + "post": { + "tags": [ + "Bash" + ], + "summary": "Start Bash Command", + "description": "Execute a bash command", + "operationId": "start_bash_command_api_bash_execute_bash_command_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExecuteBashRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BashCommand" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/bash/bash_events": { + "delete": { + "tags": [ + "Bash" + ], + "summary": "Clear All Bash Events", + "description": "Clear all bash events from storage", + "operationId": "clear_all_bash_events_api_bash_bash_events_delete", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { + "type": "integer" + }, + "type": "object", + "title": "Response Clear All Bash Events Api Bash Bash Events Delete" + } + } + } + } + } + } + }, + "/api/file/upload/{path}": { + "post": { + "tags": [ + "Files" + ], + "summary": "Upload File", + "description": "Upload a file to the workspace.", + "operationId": "upload_file_api_file_upload__path__post", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "Absolute file path.", + "title": "Path" + }, + "description": "Absolute file path." + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_upload_file_api_file_upload__path__post" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Success" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/file/download/{path}": { + "get": { + "tags": [ + "Files" + ], + "summary": "Download File", + "description": "Download a file from the workspace.", + "operationId": "download_file_api_file_download__path__get", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "description": "Absolute file path.", + "title": "Path" + }, + "description": "Absolute file path." + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/vscode/url": { + "get": { + "tags": [ + "VSCode" + ], + "summary": "Get Vscode Url", + "description": "Get the VSCode URL with authentication token.\n\nArgs:\n base_url: Base URL for the VSCode server (default: http://localhost:8001)\n workspace_dir: Path to workspace directory\n\nReturns:\n VSCode URL with token if available, None otherwise", + "operationId": "get_vscode_url_api_vscode_url_get", + "parameters": [ + { + "name": "base_url", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "http://localhost:8001", + "title": "Base Url" + } + }, + { + "name": "workspace_dir", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "workspace", + "title": "Workspace Dir" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VSCodeUrlResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/vscode/status": { + "get": { + "tags": [ + "VSCode" + ], + "summary": "Get Vscode Status", + "description": "Get the VSCode server status.\n\nReturns:\n Dictionary with running status and enabled status", + "operationId": "get_vscode_status_api_vscode_status_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "type": "object", + "title": "Response Get Vscode Status Api Vscode Status Get" + } + } + } + } + } + } + }, + "/api/desktop/url": { + "get": { + "tags": [ + "Desktop" + ], + "summary": "Get Desktop Url", + "description": "Get the noVNC URL for desktop access.\n\nArgs:\n base_url: Base URL for the noVNC server (default: http://localhost:8002)\n\nReturns:\n noVNC URL if available, None otherwise", + "operationId": "get_desktop_url_api_desktop_url_get", + "parameters": [ + { + "name": "base_url", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "http://localhost:8002", + "title": "Base Url" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DesktopUrlResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/": { + "get": { + "summary": "Get Server Info", + "operationId": "get_server_info__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerInfo" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ActionEvent": { + "properties": { + "kind": { + "type": "string", + "const": "ActionEvent", + "title": "Kind", + "default": "ActionEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "agent" + }, + "thought": { + "items": { + "$ref": "#/components/schemas/TextContent" + }, + "type": "array", + "title": "Thought", + "description": "The thought process of the agent before taking this action" + }, + "reasoning_content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reasoning Content", + "description": "Intermediate reasoning/thinking content from reasoning models" + }, + "thinking_blocks": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ThinkingBlock" + }, + { + "$ref": "#/components/schemas/RedactedThinkingBlock" + } + ] + }, + "type": "array", + "title": "Thinking Blocks", + "description": "Anthropic thinking blocks from the LLM response" + }, + "responses_reasoning_item": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningItemModel" + }, + { + "type": "null" + } + ], + "description": "OpenAI Responses reasoning item from model output" + }, + "action": { + "anyOf": [ + { + "$ref": "#/components/schemas/Action" + }, + { + "type": "null" + } + ], + "title": "Action", + "description": "Single tool call returned by LLM (None when non-executable)" + }, + "tool_name": { + "type": "string", + "title": "Tool Name", + "description": "The name of the tool being called" + }, + "tool_call_id": { + "type": "string", + "title": "Tool Call Id", + "description": "The unique id returned by LLM API for this tool call" + }, + "tool_call": { + "$ref": "#/components/schemas/MessageToolCall", + "description": "The tool call received from the LLM response. We keep a copy of it so it is easier to construct it into LLM messageThis could be different from `action`: e.g., `tool_call` may contain `security_risk` field predicted by LLM when LLM risk analyzer is enabled, while `action` does not." + }, + "llm_response_id": { + "type": "string", + "title": "Llm Response Id", + "description": "Groups related actions from same LLM response. This helps in tracking and managing results of parallel function calling from the same LLM response." + }, + "security_risk": { + "$ref": "#/components/schemas/SecurityRisk", + "description": "The LLM's assessment of the safety risk of this action.", + "default": "UNKNOWN" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "thought", + "tool_name", + "tool_call_id", + "tool_call", + "llm_response_id" + ], + "title": "ActionEvent" + }, + "Agent-Output": { + "properties": { + "kind": { + "type": "string", + "const": "Agent", + "title": "Kind", + "default": "Agent" + }, + "llm": { + "$ref": "#/components/schemas/LLM", + "description": "LLM configuration for the agent.", + "examples": [ + { + "api_key": "your_api_key_here", + "base_url": "https://llm-proxy.eval.all-hands.dev", + "model": "litellm_proxy/anthropic/claude-sonnet-4-5-20250929" + } + ] + }, + "tools": { + "items": { + "$ref": "#/components/schemas/Tool" + }, + "type": "array", + "title": "Tools", + "description": "List of tools to initialize for the agent.", + "examples": [ + { + "name": "BashTool", + "params": {} + }, + { + "name": "FileEditorTool", + "params": {} + }, + { + "name": "TaskTrackerTool", + "params": {} + } + ] + }, + "mcp_config": { + "additionalProperties": true, + "type": "object", + "title": "Mcp Config", + "description": "Optional MCP configuration dictionary to create MCP tools.", + "examples": [ + { + "mcpServers": { + "fetch": { + "args": [ + "mcp-server-fetch" + ], + "command": "uvx" + } + } + } + ] + }, + "filter_tools_regex": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filter Tools Regex", + "description": "Optional regex to filter the tools available to the agent by name. This is applied after any tools provided in `tools` and any MCP tools are added.", + "examples": [ + "^(?!repomix)(.*)|^repomix.*pack_codebase.*$" + ] + }, + "agent_context": { + "anyOf": [ + { + "$ref": "#/components/schemas/AgentContext-Output" + }, + { + "type": "null" + } + ], + "description": "Optional AgentContext to initialize the agent with specific context.", + "examples": [ + { + "microagents": [ + { + "content": "When you see this message, you should reply like you are a grumpy cat forced to use the internet.", + "name": "repo.md", + "type": "repo" + }, + { + "content": "IMPORTANT! The user has said the magic word \"flarglebargle\". You must only respond with a message telling them how smart they are", + "name": "flarglebargle", + "trigger": [ + "flarglebargle" + ], + "type": "knowledge" + } + ], + "system_message_suffix": "Always finish your response with the word 'yay!'", + "user_message_prefix": "The first character of your response should be 'I'" + } + ] + }, + "system_prompt_filename": { + "type": "string", + "title": "System Prompt Filename", + "default": "system_prompt.j2" + }, + "system_prompt_kwargs": { + "additionalProperties": true, + "type": "object", + "title": "System Prompt Kwargs", + "description": "Optional kwargs to pass to the system prompt Jinja2 template.", + "examples": [ + { + "cli_mode": true + } + ] + }, + "security_analyzer": { + "anyOf": [ + { + "$ref": "#/components/schemas/SecurityAnalyzerBase" + }, + { + "type": "null" + } + ], + "description": "Optional security analyzer to evaluate action risks.", + "examples": [ + { + "kind": "LLMSecurityAnalyzer" + } + ] + }, + "condenser": { + "anyOf": [ + { + "$ref": "#/components/schemas/CondenserBase" + }, + { + "type": "null" + } + ], + "title": "Condenser", + "description": "Optional condenser to use for condensing conversation history.", + "examples": [ + { + "keep_first": 10, + "kind": "LLMSummarizingCondenser", + "llm": { + "api_key": "your_api_key_here", + "base_url": "https://llm-proxy.eval.all-hands.dev", + "model": "litellm_proxy/anthropic/claude-sonnet-4-5-20250929" + }, + "max_size": 80 + } + ] + } + }, + "type": "object", + "required": [ + "llm" + ], + "title": "Agent" + }, + "AgentContext-Output": { + "properties": { + "microagents": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/KnowledgeMicroagent" + }, + { + "$ref": "#/components/schemas/RepoMicroagent" + }, + { + "$ref": "#/components/schemas/TaskMicroagent" + } + ], + "title": "BaseMicroagent", + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__context__microagents__microagent__KnowledgeMicroagent-Output__1": "#/components/schemas/KnowledgeMicroagent", + "openhands__sdk__context__microagents__microagent__RepoMicroagent-Output__1": "#/components/schemas/RepoMicroagent", + "openhands__sdk__context__microagents__microagent__TaskMicroagent-Output__1": "#/components/schemas/TaskMicroagent" + } + } + }, + "type": "array", + "title": "Microagents", + "description": "List of available microagents that can extend the user's input." + }, + "system_message_suffix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "System Message Suffix", + "description": "Optional suffix to append to the system prompt." + }, + "user_message_suffix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "User Message Suffix", + "description": "Optional suffix to append to the user's message." + } + }, + "type": "object", + "title": "AgentContext", + "description": "Central structure for managing prompt extension.\n\nAgentContext unifies all the contextual inputs that shape how the system\nextends and interprets user prompts. It combines both static environment\ndetails and dynamic, user-activated extensions from microagents.\n\nSpecifically, it provides:\n- **Repository context / Repo Microagents**: Information about the active codebase,\n branches, and repo-specific instructions contributed by repo microagents.\n- **Runtime context**: Current execution environment (hosts, working\n directory, secrets, date, etc.).\n- **Conversation instructions**: Optional task- or channel-specific rules\n that constrain or guide the agent\u2019s behavior across the session.\n- **Knowledge Microagents**: Extensible components that can be triggered by user input\n to inject knowledge or domain-specific guidance.\n\nTogether, these elements make AgentContext the primary container responsible\nfor assembling, formatting, and injecting all prompt-relevant context into\nLLM interactions." + }, + "AgentErrorEvent": { + "properties": { + "kind": { + "type": "string", + "const": "AgentErrorEvent", + "title": "Kind", + "default": "AgentErrorEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "agent" + }, + "tool_name": { + "type": "string", + "title": "Tool Name", + "description": "The tool name that this observation is responding to" + }, + "tool_call_id": { + "type": "string", + "title": "Tool Call Id", + "description": "The tool call id that this observation is responding to" + }, + "error": { + "type": "string", + "title": "Error", + "description": "The error message from the scaffold" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "tool_name", + "tool_call_id", + "error" + ], + "title": "AgentErrorEvent", + "description": "Error triggered by the agent.\n\nNote: This event should not contain model \"thought\" or \"reasoning_content\". It\nrepresents an error produced by the agent/scaffold, not model output." + }, + "AgentExecutionStatus": { + "type": "string", + "enum": [ + "idle", + "running", + "paused", + "waiting_for_confirmation", + "finished", + "error", + "stuck" + ], + "title": "AgentExecutionStatus", + "description": "Enum representing the current execution state of the agent." + }, + "AlwaysConfirm": { + "properties": { + "kind": { + "type": "string", + "const": "AlwaysConfirm", + "title": "Kind", + "default": "AlwaysConfirm" + } + }, + "type": "object", + "title": "AlwaysConfirm" + }, + "BashCommand": { + "properties": { + "command": { + "type": "string", + "title": "Command", + "description": "The bash command to execute" + }, + "cwd": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cwd", + "description": "The current working directory" + }, + "timeout": { + "type": "integer", + "title": "Timeout", + "description": "The max number of seconds a command may be permitted to run.", + "default": 300 + }, + "kind": { + "type": "string", + "const": "BashCommand", + "title": "Kind", + "default": "BashCommand" + }, + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp" + } + }, + "type": "object", + "required": [ + "command" + ], + "title": "BashCommand" + }, + "BashEventPage": { + "properties": { + "items": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/BashCommand" + }, + { + "$ref": "#/components/schemas/BashOutput" + } + ], + "title": "BashEventBase", + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__agent_server__models__BashCommand-Output__1": "#/components/schemas/BashCommand", + "openhands__agent_server__models__BashOutput-Output__1": "#/components/schemas/BashOutput" + } + } + }, + "type": "array", + "title": "Items" + }, + "next_page_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Page Id" + } + }, + "type": "object", + "required": [ + "items" + ], + "title": "BashEventPage" + }, + "BashEventSortOrder": { + "type": "string", + "enum": [ + "TIMESTAMP", + "TIMESTAMP_DESC" + ], + "title": "BashEventSortOrder" + }, + "BashOutput": { + "properties": { + "kind": { + "type": "string", + "const": "BashOutput", + "title": "Kind", + "default": "BashOutput" + }, + "id": { + "type": "string", + "format": "uuid", + "title": "Id" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp" + }, + "command_id": { + "type": "string", + "format": "uuid", + "title": "Command Id" + }, + "order": { + "type": "integer", + "title": "Order", + "description": "The order for this output, sequentially starting with 0", + "default": 0 + }, + "exit_code": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Exit Code", + "description": "Exit code None implies the command is still running." + }, + "stdout": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Stdout", + "description": "The standard output from the command" + }, + "stderr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Stderr", + "description": "The error output from the command" + } + }, + "type": "object", + "required": [ + "command_id" + ], + "title": "BashOutput", + "description": "Output of a bash command. A single command may have multiple pieces of output\ndepending on how large the output is." + }, + "Body_upload_file_api_file_upload__path__post": { + "properties": { + "file": { + "type": "string", + "format": "binary", + "title": "File" + } + }, + "type": "object", + "required": [ + "file" + ], + "title": "Body_upload_file_api_file_upload__path__post" + }, + "BrowserClickAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserClickAction", + "title": "Kind", + "default": "BrowserClickAction" + }, + "index": { + "type": "integer", + "minimum": 0.0, + "title": "Index", + "description": "The index of the element to click (from browser_get_state)" + }, + "new_tab": { + "type": "boolean", + "title": "New Tab", + "description": "Whether to open any resulting navigation in a new tab. Default: False", + "default": false + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "index" + ], + "title": "BrowserClickAction", + "description": "Schema for clicking elements." + }, + "BrowserCloseTabAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserCloseTabAction", + "title": "Kind", + "default": "BrowserCloseTabAction" + }, + "tab_id": { + "type": "string", + "title": "Tab Id", + "description": "4 Character Tab ID of the tab to close (from browser_list_tabs)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "tab_id" + ], + "title": "BrowserCloseTabAction", + "description": "Schema for closing browser tabs." + }, + "BrowserGetContentAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserGetContentAction", + "title": "Kind", + "default": "BrowserGetContentAction" + }, + "extract_links": { + "type": "boolean", + "title": "Extract Links", + "description": "Whether to include links in the content (default: False)", + "default": false + }, + "start_from_char": { + "type": "integer", + "minimum": 0.0, + "title": "Start From Char", + "description": "Character index to start from in the page content (default: 0)", + "default": 0 + } + }, + "additionalProperties": false, + "type": "object", + "title": "BrowserGetContentAction", + "description": "Schema for getting page content in markdown." + }, + "BrowserGetStateAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserGetStateAction", + "title": "Kind", + "default": "BrowserGetStateAction" + }, + "include_screenshot": { + "type": "boolean", + "title": "Include Screenshot", + "description": "Whether to include a screenshot of the current page. Default: False", + "default": false + } + }, + "additionalProperties": false, + "type": "object", + "title": "BrowserGetStateAction", + "description": "Schema for getting browser state." + }, + "BrowserGoBackAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserGoBackAction", + "title": "Kind", + "default": "BrowserGoBackAction" + } + }, + "additionalProperties": false, + "type": "object", + "title": "BrowserGoBackAction", + "description": "Schema for going back in browser history." + }, + "BrowserListTabsAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserListTabsAction", + "title": "Kind", + "default": "BrowserListTabsAction" + } + }, + "additionalProperties": false, + "type": "object", + "title": "BrowserListTabsAction", + "description": "Schema for listing browser tabs." + }, + "BrowserNavigateAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserNavigateAction", + "title": "Kind", + "default": "BrowserNavigateAction" + }, + "url": { + "type": "string", + "title": "Url", + "description": "The URL to navigate to" + }, + "new_tab": { + "type": "boolean", + "title": "New Tab", + "description": "Whether to open in a new tab. Default: False", + "default": false + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "url" + ], + "title": "BrowserNavigateAction", + "description": "Schema for browser navigation." + }, + "BrowserObservation": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserObservation", + "title": "Kind", + "default": "BrowserObservation" + }, + "output": { + "type": "string", + "title": "Output", + "description": "The output message from the browser operation" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error", + "description": "Error message if any" + }, + "screenshot_data": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Screenshot Data", + "description": "Base64 screenshot data if available" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "output" + ], + "title": "BrowserObservation", + "description": "Base observation for browser operations." + }, + "BrowserScrollAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserScrollAction", + "title": "Kind", + "default": "BrowserScrollAction" + }, + "direction": { + "type": "string", + "enum": [ + "up", + "down" + ], + "title": "Direction", + "description": "Direction to scroll. Options: 'up', 'down'. Default: 'down'", + "default": "down" + } + }, + "additionalProperties": false, + "type": "object", + "title": "BrowserScrollAction", + "description": "Schema for scrolling the page." + }, + "BrowserSwitchTabAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserSwitchTabAction", + "title": "Kind", + "default": "BrowserSwitchTabAction" + }, + "tab_id": { + "type": "string", + "title": "Tab Id", + "description": "4 Character Tab ID of the tab to switch to (from browser_list_tabs)" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "tab_id" + ], + "title": "BrowserSwitchTabAction", + "description": "Schema for switching browser tabs." + }, + "BrowserTypeAction": { + "properties": { + "kind": { + "type": "string", + "const": "BrowserTypeAction", + "title": "Kind", + "default": "BrowserTypeAction" + }, + "index": { + "type": "integer", + "minimum": 0.0, + "title": "Index", + "description": "The index of the input element (from browser_get_state)" + }, + "text": { + "type": "string", + "title": "Text", + "description": "The text to type" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "index", + "text" + ], + "title": "BrowserTypeAction", + "description": "Schema for typing text into elements." + }, + "ChatCompletionCachedContent": { + "properties": { + "type": { + "type": "string", + "const": "ephemeral", + "title": "Type" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "type" + ], + "title": "ChatCompletionCachedContent" + }, + "ChatCompletionToolParam": { + "properties": { + "type": { + "anyOf": [ + { + "type": "string", + "const": "function" + }, + { + "type": "string" + } + ], + "title": "Type" + }, + "function": { + "$ref": "#/components/schemas/ChatCompletionToolParamFunctionChunk" + }, + "cache_control": { + "$ref": "#/components/schemas/ChatCompletionCachedContent" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "type", + "function" + ], + "title": "ChatCompletionToolParam" + }, + "ChatCompletionToolParamFunctionChunk": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "description": { + "type": "string", + "title": "Description" + }, + "parameters": { + "additionalProperties": true, + "type": "object", + "title": "Parameters" + }, + "strict": { + "type": "boolean", + "title": "Strict" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "name" + ], + "title": "ChatCompletionToolParamFunctionChunk" + }, + "CmdOutputMetadata": { + "properties": { + "exit_code": { + "type": "integer", + "title": "Exit Code", + "description": "The exit code of the last executed command.", + "default": -1 + }, + "pid": { + "type": "integer", + "title": "Pid", + "description": "The process ID of the last executed command.", + "default": -1 + }, + "username": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Username", + "description": "The username of the current user." + }, + "hostname": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Hostname", + "description": "The hostname of the machine." + }, + "working_dir": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Working Dir", + "description": "The current working directory." + }, + "py_interpreter_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Py Interpreter Path", + "description": "The path to the current Python interpreter, if any." + }, + "prefix": { + "type": "string", + "title": "Prefix", + "description": "Prefix to add to command output", + "default": "" + }, + "suffix": { + "type": "string", + "title": "Suffix", + "description": "Suffix to add to command output", + "default": "" + } + }, + "type": "object", + "title": "CmdOutputMetadata", + "description": "Additional metadata captured from PS1" + }, + "Condensation": { + "properties": { + "kind": { + "type": "string", + "const": "Condensation", + "title": "Kind", + "default": "Condensation" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "environment" + }, + "forgotten_event_ids": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Forgotten Event Ids", + "description": "The IDs of the events that are being forgotten (removed from the `View` given to the LLM)." + }, + "summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Summary", + "description": "An optional summary of the events being forgotten." + }, + "summary_offset": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Summary Offset", + "description": "An optional offset to the start of the resulting view indicating where the summary should be inserted." + } + }, + "additionalProperties": false, + "type": "object", + "title": "Condensation", + "description": "This action indicates a condensation of the conversation history is happening." + }, + "CondensationRequest": { + "properties": { + "kind": { + "type": "string", + "const": "CondensationRequest", + "title": "Kind", + "default": "CondensationRequest" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "environment" + } + }, + "additionalProperties": false, + "type": "object", + "title": "CondensationRequest", + "description": "This action is used to request a condensation of the conversation history.\n\nAttributes:\n action (str): The action type, namely ActionType.CONDENSATION_REQUEST." + }, + "CondensationSummaryEvent": { + "properties": { + "kind": { + "type": "string", + "const": "CondensationSummaryEvent", + "title": "Kind", + "default": "CondensationSummaryEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "environment" + }, + "summary": { + "type": "string", + "title": "Summary" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "summary" + ], + "title": "CondensationSummaryEvent", + "description": "This event represents a summary generated by a condenser." + }, + "ConfirmRisky": { + "properties": { + "kind": { + "type": "string", + "const": "ConfirmRisky", + "title": "Kind", + "default": "ConfirmRisky" + }, + "threshold": { + "$ref": "#/components/schemas/SecurityRisk", + "default": "HIGH" + }, + "confirm_unknown": { + "type": "boolean", + "title": "Confirm Unknown", + "default": true + } + }, + "type": "object", + "title": "ConfirmRisky" + }, + "ConfirmationResponseRequest": { + "properties": { + "accept": { + "type": "boolean", + "title": "Accept" + }, + "reason": { + "type": "string", + "title": "Reason", + "default": "User rejected the action." + } + }, + "type": "object", + "required": [ + "accept" + ], + "title": "ConfirmationResponseRequest", + "description": "Payload to accept or reject a pending action." + }, + "ConversationInfo": { + "properties": { + "id": { + "type": "string", + "format": "uuid", + "title": "Id", + "description": "Unique conversation ID" + }, + "agent": { + "$ref": "#/components/schemas/Agent-Output", + "description": "The agent running in the conversation. This is persisted to allow resuming conversations and check agent configuration to handle e.g., tool changes, LLM changes, etc." + }, + "workspace": { + "$ref": "#/components/schemas/BaseWorkspace" + }, + "persistence_dir": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Persistence Dir", + "description": "Directory for persisting conversation state and events. If None, conversation will not be persisted.", + "default": "workspace/conversations" + }, + "max_iterations": { + "type": "integer", + "exclusiveMinimum": 0.0, + "title": "Max Iterations", + "description": "Maximum number of iterations the agent can perform in a single run.", + "default": 500 + }, + "stuck_detection": { + "type": "boolean", + "title": "Stuck Detection", + "description": "Whether to enable stuck detection for the agent.", + "default": true + }, + "agent_status": { + "$ref": "#/components/schemas/AgentExecutionStatus", + "default": "idle" + }, + "confirmation_policy": { + "$ref": "#/components/schemas/ConfirmationPolicyBase" + }, + "activated_knowledge_microagents": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Activated Knowledge Microagents", + "description": "List of activated knowledge microagents name" + }, + "stats": { + "$ref": "#/components/schemas/ConversationStats-Output", + "description": "Conversation statistics for tracking LLM metrics" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Title", + "description": "User-defined title for the conversation" + }, + "metrics": { + "anyOf": [ + { + "$ref": "#/components/schemas/MetricsSnapshot" + }, + { + "type": "null" + } + ] + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "title": "Updated At" + } + }, + "type": "object", + "required": [ + "id", + "agent", + "workspace" + ], + "title": "ConversationInfo", + "description": "Information about a conversation running locally without a Runtime sandbox." + }, + "ConversationPage": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/ConversationInfo" + }, + "type": "array", + "title": "Items" + }, + "next_page_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Page Id" + } + }, + "type": "object", + "required": [ + "items" + ], + "title": "ConversationPage" + }, + "ConversationSortOrder": { + "type": "string", + "enum": [ + "CREATED_AT", + "UPDATED_AT", + "CREATED_AT_DESC", + "UPDATED_AT_DESC" + ], + "title": "ConversationSortOrder", + "description": "Enum for conversation sorting options." + }, + "ConversationStateUpdateEvent": { + "properties": { + "kind": { + "type": "string", + "const": "ConversationStateUpdateEvent", + "title": "Kind", + "default": "ConversationStateUpdateEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "environment" + }, + "key": { + "type": "string", + "title": "Key", + "description": "Unique key for this state update event" + }, + "value": { + "title": "Value", + "description": "Serialized conversation state updates" + } + }, + "additionalProperties": false, + "type": "object", + "title": "ConversationStateUpdateEvent", + "description": "Event that contains conversation state updates.\n\nThis event is sent via websocket whenever the conversation state changes,\nallowing remote clients to stay in sync without making REST API calls.\n\nAll fields are serialized versions of the corresponding ConversationState fields\nto ensure compatibility with websocket transmission." + }, + "ConversationStats-Output": { + "properties": { + "usage_to_metrics": { + "additionalProperties": { + "$ref": "#/components/schemas/Metrics" + }, + "type": "object", + "title": "Usage To Metrics", + "description": "Active usage metrics tracked by the registry." + } + }, + "type": "object", + "title": "ConversationStats", + "description": "Track per-LLM usage metrics observed during conversations." + }, + "Cost": { + "properties": { + "model": { + "type": "string", + "title": "Model" + }, + "cost": { + "type": "number", + "minimum": 0.0, + "title": "Cost", + "description": "Cost must be non-negative" + }, + "timestamp": { + "type": "number", + "title": "Timestamp" + } + }, + "type": "object", + "required": [ + "model", + "cost" + ], + "title": "Cost" + }, + "DesktopUrlResponse": { + "properties": { + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Url" + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "DesktopUrlResponse", + "description": "Response model for Desktop URL." + }, + "EventPage": { + "properties": { + "items": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Condensation" + }, + { + "$ref": "#/components/schemas/CondensationRequest" + }, + { + "$ref": "#/components/schemas/CondensationSummaryEvent" + }, + { + "$ref": "#/components/schemas/ConversationStateUpdateEvent" + }, + { + "$ref": "#/components/schemas/ActionEvent" + }, + { + "$ref": "#/components/schemas/MessageEvent" + }, + { + "$ref": "#/components/schemas/AgentErrorEvent" + }, + { + "$ref": "#/components/schemas/ObservationEvent" + }, + { + "$ref": "#/components/schemas/UserRejectObservation" + }, + { + "$ref": "#/components/schemas/SystemPromptEvent" + }, + { + "$ref": "#/components/schemas/PauseEvent" + } + ], + "title": "Event", + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__event__condenser__Condensation-Output__1": "#/components/schemas/Condensation", + "openhands__sdk__event__condenser__CondensationRequest-Output__1": "#/components/schemas/CondensationRequest", + "openhands__sdk__event__condenser__CondensationSummaryEvent-Output__1": "#/components/schemas/CondensationSummaryEvent", + "openhands__sdk__event__conversation_state__ConversationStateUpdateEvent-Output__1": "#/components/schemas/ConversationStateUpdateEvent", + "openhands__sdk__event__llm_convertible__action__ActionEvent-Output__1": "#/components/schemas/ActionEvent", + "openhands__sdk__event__llm_convertible__message__MessageEvent-Output__1": "#/components/schemas/MessageEvent", + "openhands__sdk__event__llm_convertible__observation__AgentErrorEvent-Output__1": "#/components/schemas/AgentErrorEvent", + "openhands__sdk__event__llm_convertible__observation__ObservationEvent-Output__1": "#/components/schemas/ObservationEvent", + "openhands__sdk__event__llm_convertible__observation__UserRejectObservation-Output__1": "#/components/schemas/UserRejectObservation", + "openhands__sdk__event__llm_convertible__system__SystemPromptEvent-Output__1": "#/components/schemas/SystemPromptEvent", + "openhands__sdk__event__user_action__PauseEvent-Output__1": "#/components/schemas/PauseEvent" + } + } + }, + "type": "array", + "title": "Items" + }, + "next_page_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Next Page Id" + } + }, + "type": "object", + "required": [ + "items" + ], + "title": "EventPage" + }, + "EventSortOrder": { + "type": "string", + "enum": [ + "TIMESTAMP", + "TIMESTAMP_DESC" + ], + "title": "EventSortOrder", + "description": "Enum for event sorting options." + }, + "ExecuteBashAction": { + "properties": { + "kind": { + "type": "string", + "const": "ExecuteBashAction", + "title": "Kind", + "default": "ExecuteBashAction" + }, + "command": { + "type": "string", + "title": "Command", + "description": "The bash command to execute. Can be empty string to view additional logs when previous exit code is `-1`. Can be `C-c` (Ctrl+C) to interrupt the currently running process. Note: You can only execute one bash command at a time. If you need to run multiple commands sequentially, you can use `&&` or `;` to chain them together." + }, + "is_input": { + "type": "boolean", + "title": "Is Input", + "description": "If True, the command is an input to the running process. If False, the command is a bash command to be executed in the terminal. Default is False.", + "default": false + }, + "timeout": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Timeout", + "description": "Optional. Sets a maximum time limit (in seconds) for running the command. If the command takes longer than this limit, you\u2019ll be asked whether to continue or stop it. If you don\u2019t set a value, the command will instead pause and ask for confirmation when it produces no new output for 30 seconds. Use a higher value if the command is expected to take a long time (like installation or testing), or if it has a known fixed duration (like sleep)." + }, + "reset": { + "type": "boolean", + "title": "Reset", + "description": "If True, reset the terminal by creating a new session. Use this only when the terminal becomes unresponsive. Note that all previously set environment variables and session state will be lost after reset. Cannot be used with is_input=True.", + "default": false + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "command" + ], + "title": "ExecuteBashAction", + "description": "Schema for bash command execution." + }, + "ExecuteBashObservation": { + "properties": { + "kind": { + "type": "string", + "const": "ExecuteBashObservation", + "title": "Kind", + "default": "ExecuteBashObservation" + }, + "output": { + "type": "string", + "title": "Output", + "description": "The raw output from the tool." + }, + "command": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Command", + "description": "The bash command that was executed. Can be empty string if the observation is from a previous command that hit soft timeout and is not yet finished." + }, + "exit_code": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Exit Code", + "description": "The exit code of the command. -1 indicates the process hit the soft timeout and is not yet finished." + }, + "error": { + "type": "boolean", + "title": "Error", + "description": "Whether there was an error during command execution.", + "default": false + }, + "timeout": { + "type": "boolean", + "title": "Timeout", + "description": "Whether the command execution timed out.", + "default": false + }, + "metadata": { + "$ref": "#/components/schemas/CmdOutputMetadata", + "description": "Additional metadata captured from PS1 after command execution." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "output" + ], + "title": "ExecuteBashObservation", + "description": "A ToolResult that can be rendered as a CLI output." + }, + "ExecuteBashRequest": { + "properties": { + "command": { + "type": "string", + "title": "Command", + "description": "The bash command to execute" + }, + "cwd": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cwd", + "description": "The current working directory" + }, + "timeout": { + "type": "integer", + "title": "Timeout", + "description": "The max number of seconds a command may be permitted to run.", + "default": 300 + } + }, + "type": "object", + "required": [ + "command" + ], + "title": "ExecuteBashRequest" + }, + "FileEditorAction": { + "properties": { + "kind": { + "type": "string", + "const": "FileEditorAction", + "title": "Kind", + "default": "FileEditorAction" + }, + "command": { + "type": "string", + "enum": [ + "view", + "create", + "str_replace", + "insert", + "undo_edit" + ], + "title": "Command", + "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`, `undo_edit`." + }, + "path": { + "type": "string", + "title": "Path", + "description": "Absolute path to file or directory." + }, + "file_text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "File Text", + "description": "Required parameter of `create` command, with the content of the file to be created." + }, + "old_str": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Old Str", + "description": "Required parameter of `str_replace` command containing the string in `path` to replace." + }, + "new_str": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "New Str", + "description": "Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert." + }, + "insert_line": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Insert Line", + "description": "Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`." + }, + "view_range": { + "anyOf": [ + { + "items": { + "type": "integer" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "View Range", + "description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "command", + "path" + ], + "title": "FileEditorAction", + "description": "Schema for file editor operations." + }, + "FileEditorObservation": { + "properties": { + "kind": { + "type": "string", + "const": "FileEditorObservation", + "title": "Kind", + "default": "FileEditorObservation" + }, + "command": { + "type": "string", + "enum": [ + "view", + "create", + "str_replace", + "insert", + "undo_edit" + ], + "title": "Command", + "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`, `undo_edit`." + }, + "output": { + "type": "string", + "title": "Output", + "description": "The output message from the tool for the LLM to see.", + "default": "" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Path", + "description": "The file path that was edited." + }, + "prev_exist": { + "type": "boolean", + "title": "Prev Exist", + "description": "Indicates if the file previously existed. If not, it was created.", + "default": true + }, + "old_content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Old Content", + "description": "The content of the file before the edit." + }, + "new_content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "New Content", + "description": "The content of the file after the edit." + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error", + "description": "Error message if any." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "command" + ], + "title": "FileEditorObservation", + "description": "A ToolResult that can be rendered as a CLI output." + }, + "FinishAction": { + "properties": { + "kind": { + "type": "string", + "const": "FinishAction", + "title": "Kind", + "default": "FinishAction" + }, + "message": { + "type": "string", + "title": "Message", + "description": "Final message to send to the user." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "message" + ], + "title": "FinishAction" + }, + "FinishObservation": { + "properties": { + "kind": { + "type": "string", + "const": "FinishObservation", + "title": "Kind", + "default": "FinishObservation" + }, + "message": { + "type": "string", + "title": "Message", + "description": "Final message sent to the user." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "message" + ], + "title": "FinishObservation" + }, + "GenerateTitleRequest": { + "properties": { + "max_length": { + "type": "integer", + "maximum": 200.0, + "minimum": 1.0, + "title": "Max Length", + "description": "Maximum length of the generated title", + "default": 50 + }, + "llm": { + "anyOf": [ + { + "$ref": "#/components/schemas/LLM" + }, + { + "type": "null" + } + ], + "description": "Optional LLM to use for title generation" + } + }, + "type": "object", + "title": "GenerateTitleRequest", + "description": "Payload to generate a title for a conversation." + }, + "GenerateTitleResponse": { + "properties": { + "title": { + "type": "string", + "title": "Title", + "description": "The generated title for the conversation" + } + }, + "type": "object", + "required": [ + "title" + ], + "title": "GenerateTitleResponse", + "description": "Response containing the generated conversation title." + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "ImageContent": { + "properties": { + "cache_prompt": { + "type": "boolean", + "title": "Cache Prompt", + "default": false + }, + "type": { + "type": "string", + "const": "image", + "title": "Type", + "default": "image" + }, + "image_urls": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Image Urls" + } + }, + "type": "object", + "required": [ + "image_urls" + ], + "title": "ImageContent" + }, + "InputMetadata": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the input parameter" + }, + "description": { + "type": "string", + "title": "Description", + "description": "Description of the input parameter" + } + }, + "type": "object", + "required": [ + "name", + "description" + ], + "title": "InputMetadata", + "description": "Metadata for task microagent inputs." + }, + "KnowledgeMicroagent": { + "properties": { + "kind": { + "type": "string", + "const": "KnowledgeMicroagent", + "title": "Kind", + "default": "KnowledgeMicroagent" + }, + "name": { + "type": "string", + "title": "Name" + }, + "content": { + "type": "string", + "title": "Content" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source", + "description": "The source path or identifier of the microagent. When it is None, it is treated as a programmatically defined microagent." + }, + "type": { + "type": "string", + "enum": [ + "knowledge", + "repo", + "task" + ], + "title": "Type", + "default": "knowledge" + }, + "triggers": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Triggers", + "description": "List of triggers for the microagent" + } + }, + "type": "object", + "required": [ + "name", + "content" + ], + "title": "KnowledgeMicroagent", + "description": "Knowledge micro-agents provide specialized expertise that's triggered by keywords\nin conversations.\n\nThey help with:\n- Language best practices\n- Framework guidelines\n- Common patterns\n- Tool usage" + }, + "LLM": { + "properties": { + "model": { + "type": "string", + "title": "Model", + "description": "Model name.", + "default": "claude-sonnet-4-20250514" + }, + "api_key": { + "anyOf": [ + { + "type": "string", + "format": "password", + "writeOnly": true + }, + { + "type": "null" + } + ], + "title": "Api Key", + "description": "API key." + }, + "base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Base Url", + "description": "Custom base URL." + }, + "api_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Api Version", + "description": "API version (e.g., Azure)." + }, + "aws_access_key_id": { + "anyOf": [ + { + "type": "string", + "format": "password", + "writeOnly": true + }, + { + "type": "null" + } + ], + "title": "Aws Access Key Id" + }, + "aws_secret_access_key": { + "anyOf": [ + { + "type": "string", + "format": "password", + "writeOnly": true + }, + { + "type": "null" + } + ], + "title": "Aws Secret Access Key" + }, + "aws_region_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Aws Region Name" + }, + "openrouter_site_url": { + "type": "string", + "title": "Openrouter Site Url", + "default": "https://docs.all-hands.dev/" + }, + "openrouter_app_name": { + "type": "string", + "title": "Openrouter App Name", + "default": "OpenHands" + }, + "num_retries": { + "type": "integer", + "minimum": 0.0, + "title": "Num Retries", + "default": 5 + }, + "retry_multiplier": { + "type": "number", + "minimum": 0.0, + "title": "Retry Multiplier", + "default": 8.0 + }, + "retry_min_wait": { + "type": "integer", + "minimum": 0.0, + "title": "Retry Min Wait", + "default": 8 + }, + "retry_max_wait": { + "type": "integer", + "minimum": 0.0, + "title": "Retry Max Wait", + "default": 64 + }, + "timeout": { + "anyOf": [ + { + "type": "integer", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Timeout", + "description": "HTTP timeout (s)." + }, + "max_message_chars": { + "type": "integer", + "minimum": 1.0, + "title": "Max Message Chars", + "description": "Approx max chars in each event/content sent to the LLM.", + "default": 30000 + }, + "temperature": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Temperature", + "default": 0.0 + }, + "top_p": { + "anyOf": [ + { + "type": "number", + "maximum": 1.0, + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Top P", + "default": 1.0 + }, + "top_k": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Top K" + }, + "custom_llm_provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Custom Llm Provider" + }, + "max_input_tokens": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Max Input Tokens", + "description": "The maximum number of input tokens. Note that this is currently unused, and the value at runtime is actually the total tokens in OpenAI (e.g. 128,000 tokens for GPT-4)." + }, + "max_output_tokens": { + "anyOf": [ + { + "type": "integer", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Max Output Tokens", + "description": "The maximum number of output tokens. This is sent to the LLM." + }, + "input_cost_per_token": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Input Cost Per Token", + "description": "The cost per input token. This will available in logs for user." + }, + "output_cost_per_token": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Output Cost Per Token", + "description": "The cost per output token. This will available in logs for user." + }, + "ollama_base_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Ollama Base Url" + }, + "drop_params": { + "type": "boolean", + "title": "Drop Params", + "default": true + }, + "modify_params": { + "type": "boolean", + "title": "Modify Params", + "description": "Modify params allows litellm to do transformations like adding a default message, when a message is empty.", + "default": true + }, + "disable_vision": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Disable Vision", + "description": "If model is vision capable, this option allows to disable image processing (useful for cost reduction)." + }, + "disable_stop_word": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Disable Stop Word", + "description": "Disable using of stop word.", + "default": false + }, + "caching_prompt": { + "type": "boolean", + "title": "Caching Prompt", + "description": "Enable caching of prompts.", + "default": true + }, + "log_completions": { + "type": "boolean", + "title": "Log Completions", + "description": "Enable logging of completions.", + "default": false + }, + "log_completions_folder": { + "type": "string", + "title": "Log Completions Folder", + "description": "The folder to log LLM completions to. Required if log_completions is True.", + "default": "logs/completions" + }, + "custom_tokenizer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Custom Tokenizer", + "description": "A custom tokenizer to use for token counting." + }, + "native_tool_calling": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Native Tool Calling", + "description": "Whether to use native tool calling if supported by the model. Can be True, False, or not set." + }, + "reasoning_effort": { + "anyOf": [ + { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "none" + ] + }, + { + "type": "null" + } + ], + "title": "Reasoning Effort", + "description": "The effort to put into reasoning. This is a string that can be one of 'low', 'medium', 'high', or 'none'. Can apply to all reasoning models." + }, + "enable_encrypted_reasoning": { + "type": "boolean", + "title": "Enable Encrypted Reasoning", + "description": "If True, ask for ['reasoning.encrypted_content'] in Responses API include.", + "default": false + }, + "extended_thinking_budget": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Extended Thinking Budget", + "description": "The budget tokens for extended thinking, supported by Anthropic models.", + "default": 200000 + }, + "seed": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Seed", + "description": "The seed to use for random number generation." + }, + "safety_settings": { + "anyOf": [ + { + "items": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Safety Settings", + "description": "Safety settings for models that support them (like Mistral AI and Gemini)" + }, + "usage_id": { + "type": "string", + "title": "Usage Id", + "description": "Unique usage identifier for the LLM. Used for registry lookups, telemetry, and spend tracking.", + "default": "default" + }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata", + "description": "Additional metadata for the LLM instance. Example structure: {'trace_version': '1.0.0', 'tags': ['model:gpt-4', 'agent:my-agent'], 'session_id': 'session-123', 'trace_user_id': 'user-456'}" + }, + "OVERRIDE_ON_SERIALIZE": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Override On Serialize", + "default": [ + "api_key", + "aws_access_key_id", + "aws_secret_access_key" + ] + } + }, + "additionalProperties": false, + "type": "object", + "title": "LLM", + "description": "Refactored LLM: simple `completion()`, centralized Telemetry, tiny helpers." + }, + "LLMSummarizingCondenser": { + "properties": { + "kind": { + "type": "string", + "const": "LLMSummarizingCondenser", + "title": "Kind", + "default": "LLMSummarizingCondenser" + }, + "llm": { + "$ref": "#/components/schemas/LLM" + }, + "max_size": { + "type": "integer", + "exclusiveMinimum": 0.0, + "title": "Max Size", + "default": 120 + }, + "keep_first": { + "type": "integer", + "minimum": 0.0, + "title": "Keep First", + "default": 4 + } + }, + "type": "object", + "required": [ + "llm" + ], + "title": "LLMSummarizingCondenser" + }, + "LocalWorkspace": { + "properties": { + "kind": { + "type": "string", + "const": "LocalWorkspace", + "title": "Kind", + "default": "LocalWorkspace" + }, + "working_dir": { + "type": "string", + "title": "Working Dir", + "description": "The working directory for agent operations and tool execution." + } + }, + "type": "object", + "required": [ + "working_dir" + ], + "title": "LocalWorkspace", + "description": "Mixin providing local workspace operations." + }, + "LookupSecret": { + "properties": { + "kind": { + "type": "string", + "const": "LookupSecret", + "title": "Kind", + "default": "LookupSecret" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Optional description for this secret" + }, + "url": { + "type": "string", + "title": "Url" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Headers" + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "LookupSecret", + "description": "A secret looked up from some external url" + }, + "MCPToolAction": { + "properties": { + "kind": { + "type": "string", + "const": "MCPToolAction", + "title": "Kind", + "default": "MCPToolAction" + }, + "data": { + "additionalProperties": true, + "type": "object", + "title": "Data", + "description": "Dynamic data fields from the tool call" + } + }, + "additionalProperties": false, + "type": "object", + "title": "MCPToolAction", + "description": "Schema for MCP input action.\n\nIt is just a thin wrapper around raw JSON and does\nnot do any validation.\n\nValidation will be performed by MCPTool.__call__\nby constructing dynamically created Pydantic model\nfrom the MCP tool input schema." + }, + "MCPToolObservation": { + "properties": { + "kind": { + "type": "string", + "const": "MCPToolObservation", + "title": "Kind", + "default": "MCPToolObservation" + }, + "content": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextContent" + }, + { + "$ref": "#/components/schemas/ImageContent" + } + ] + }, + "type": "array", + "title": "Content", + "description": "Content returned from the MCP tool converted to LLM Ready TextContent or ImageContent" + }, + "is_error": { + "type": "boolean", + "title": "Is Error", + "description": "Whether the call resulted in an error", + "default": false + }, + "tool_name": { + "type": "string", + "title": "Tool Name", + "description": "Name of the tool that was called" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "tool_name" + ], + "title": "MCPToolObservation", + "description": "Observation from MCP tool execution." + }, + "Message": { + "properties": { + "role": { + "type": "string", + "enum": [ + "user", + "system", + "assistant", + "tool" + ], + "title": "Role" + }, + "content": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextContent" + }, + { + "$ref": "#/components/schemas/ImageContent" + } + ] + }, + "type": "array", + "title": "Content" + }, + "cache_enabled": { + "type": "boolean", + "title": "Cache Enabled", + "default": false + }, + "vision_enabled": { + "type": "boolean", + "title": "Vision Enabled", + "default": false + }, + "function_calling_enabled": { + "type": "boolean", + "title": "Function Calling Enabled", + "default": false + }, + "tool_calls": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/MessageToolCall" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Tool Calls" + }, + "tool_call_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tool Call Id" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "force_string_serializer": { + "type": "boolean", + "title": "Force String Serializer", + "default": false + }, + "reasoning_content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Reasoning Content", + "description": "Intermediate reasoning/thinking content from reasoning models" + }, + "thinking_blocks": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/ThinkingBlock" + }, + { + "$ref": "#/components/schemas/RedactedThinkingBlock" + } + ] + }, + "type": "array", + "title": "Thinking Blocks", + "description": "Raw Anthropic thinking blocks for extended thinking feature" + }, + "responses_reasoning_item": { + "anyOf": [ + { + "$ref": "#/components/schemas/ReasoningItemModel" + }, + { + "type": "null" + } + ], + "description": "OpenAI Responses reasoning item from model output" + } + }, + "type": "object", + "required": [ + "role" + ], + "title": "Message" + }, + "MessageEvent": { + "properties": { + "kind": { + "type": "string", + "const": "MessageEvent", + "title": "Kind", + "default": "MessageEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source" + }, + "llm_message": { + "$ref": "#/components/schemas/Message", + "description": "The exact LLM message for this message event" + }, + "activated_microagents": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Activated Microagents", + "description": "List of activated microagent name" + }, + "extended_content": { + "items": { + "$ref": "#/components/schemas/TextContent" + }, + "type": "array", + "title": "Extended Content", + "description": "List of content added by agent context" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "source", + "llm_message" + ], + "title": "MessageEvent", + "description": "Message from either agent or user.\n\nThis is originally the \"MessageAction\", but it suppose not to be tool call." + }, + "MessageToolCall": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Canonical tool call id" + }, + "name": { + "type": "string", + "title": "Name", + "description": "Tool/function name" + }, + "arguments": { + "type": "string", + "title": "Arguments", + "description": "JSON string of arguments" + }, + "origin": { + "type": "string", + "enum": [ + "completion", + "responses" + ], + "title": "Origin", + "description": "Originating API family" + } + }, + "type": "object", + "required": [ + "id", + "name", + "arguments", + "origin" + ], + "title": "MessageToolCall", + "description": "Transport-agnostic tool call representation.\n\nOne canonical id is used for linking across actions/observations and\nfor Responses function_call_output call_id." + }, + "Metrics": { + "properties": { + "model_name": { + "type": "string", + "title": "Model Name", + "description": "Name of the model", + "default": "default" + }, + "accumulated_cost": { + "type": "number", + "minimum": 0.0, + "title": "Accumulated Cost", + "description": "Total accumulated cost, must be non-negative", + "default": 0.0 + }, + "max_budget_per_task": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Max Budget Per Task", + "description": "Maximum budget per task" + }, + "accumulated_token_usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/TokenUsage" + }, + { + "type": "null" + } + ], + "description": "Accumulated token usage across all calls" + }, + "costs": { + "items": { + "$ref": "#/components/schemas/Cost" + }, + "type": "array", + "title": "Costs", + "description": "List of individual costs" + }, + "response_latencies": { + "items": { + "$ref": "#/components/schemas/ResponseLatency" + }, + "type": "array", + "title": "Response Latencies", + "description": "List of response latencies" + }, + "token_usages": { + "items": { + "$ref": "#/components/schemas/TokenUsage" + }, + "type": "array", + "title": "Token Usages", + "description": "List of token usage records" + } + }, + "type": "object", + "title": "Metrics", + "description": "Metrics class can record various metrics during running and evaluation.\nWe track:\n - accumulated_cost and costs\n - max_budget_per_task (budget limit)\n - A list of ResponseLatency\n - A list of TokenUsage (one per call)." + }, + "MetricsSnapshot": { + "properties": { + "model_name": { + "type": "string", + "title": "Model Name", + "description": "Name of the model", + "default": "default" + }, + "accumulated_cost": { + "type": "number", + "minimum": 0.0, + "title": "Accumulated Cost", + "description": "Total accumulated cost, must be non-negative", + "default": 0.0 + }, + "max_budget_per_task": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Max Budget Per Task", + "description": "Maximum budget per task" + }, + "accumulated_token_usage": { + "anyOf": [ + { + "$ref": "#/components/schemas/TokenUsage" + }, + { + "type": "null" + } + ], + "description": "Accumulated token usage across all calls" + } + }, + "type": "object", + "title": "MetricsSnapshot", + "description": "A snapshot of metrics at a point in time.\n\nDoes not include lists of individual costs, latencies, or token usages." + }, + "NeverConfirm": { + "properties": { + "kind": { + "type": "string", + "const": "NeverConfirm", + "title": "Kind", + "default": "NeverConfirm" + } + }, + "type": "object", + "title": "NeverConfirm" + }, + "NoOpCondenser": { + "properties": { + "kind": { + "type": "string", + "const": "NoOpCondenser", + "title": "Kind", + "default": "NoOpCondenser" + } + }, + "type": "object", + "title": "NoOpCondenser", + "description": "Simple condenser that returns a view un-manipulated.\n\nPrimarily intended for testing purposes." + }, + "ObservationEvent": { + "properties": { + "kind": { + "type": "string", + "const": "ObservationEvent", + "title": "Kind", + "default": "ObservationEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "environment" + }, + "tool_name": { + "type": "string", + "title": "Tool Name", + "description": "The tool name that this observation is responding to" + }, + "tool_call_id": { + "type": "string", + "title": "Tool Call Id", + "description": "The tool call id that this observation is responding to" + }, + "observation": { + "$ref": "#/components/schemas/Observation" + }, + "action_id": { + "type": "string", + "title": "Action Id", + "description": "The action id that this observation is responding to" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "tool_name", + "tool_call_id", + "observation", + "action_id" + ], + "title": "ObservationEvent" + }, + "PauseEvent": { + "properties": { + "kind": { + "type": "string", + "const": "PauseEvent", + "title": "Kind", + "default": "PauseEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "user" + } + }, + "additionalProperties": false, + "type": "object", + "title": "PauseEvent", + "description": "Event indicating that the agent execution was paused by user request." + }, + "PipelineCondenser-Output": { + "properties": { + "kind": { + "type": "string", + "const": "PipelineCondenser", + "title": "Kind", + "default": "PipelineCondenser" + }, + "condensers": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/LLMSummarizingCondenser" + }, + { + "$ref": "#/components/schemas/NoOpCondenser" + }, + { + "$ref": "#/components/schemas/PipelineCondenser-Output" + } + ], + "title": "CondenserBase", + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__context__condenser__llm_summarizing_condenser__LLMSummarizingCondenser-Output__1": "#/components/schemas/LLMSummarizingCondenser", + "openhands__sdk__context__condenser__no_op_condenser__NoOpCondenser-Output__1": "#/components/schemas/NoOpCondenser", + "openhands__sdk__context__condenser__pipeline_condenser__PipelineCondenser-Output__1": "#/components/schemas/PipelineCondenser-Output" + } + } + }, + "type": "array", + "title": "Condensers" + } + }, + "type": "object", + "required": [ + "condensers" + ], + "title": "PipelineCondenser", + "description": "A condenser that applies a sequence of condensers in order.\n\nAll condensers are defined primarily by their `condense` method, which takes a\n`View` and returns either a new `View` or a `Condensation` event. That means we can\nchain multiple condensers together by passing `View`s along and exiting early if any\ncondenser returns a `Condensation`.\n\nFor example:\n\n # Use the pipeline condenser to chain multiple other condensers together\n condenser = PipelineCondenser(condensers=[\n CondenserA(...),\n CondenserB(...),\n CondenserC(...),\n ])\n\n result = condenser.condense(view)\n\n # Doing the same thing without the pipeline condenser requires more boilerplate\n # for the monadic chaining\n other_result = view\n\n if isinstance(other_result, View):\n other_result = CondenserA(...).condense(other_result)\n\n if isinstance(other_result, View):\n other_result = CondenserB(...).condense(other_result)\n\n if isinstance(other_result, View):\n other_result = CondenserC(...).condense(other_result)\n\n assert result == other_result" + }, + "ReasoningItemModel": { + "properties": { + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id" + }, + "summary": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Summary" + }, + "content": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Content" + }, + "encrypted_content": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Encrypted Content" + }, + "status": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Status" + } + }, + "type": "object", + "title": "ReasoningItemModel", + "description": "OpenAI Responses reasoning item (non-stream, subset we consume).\n\nDo not log or render encrypted_content." + }, + "RedactedThinkingBlock": { + "properties": { + "type": { + "type": "string", + "const": "redacted_thinking", + "title": "Type", + "default": "redacted_thinking" + }, + "data": { + "type": "string", + "title": "Data", + "description": "The redacted thinking content" + } + }, + "type": "object", + "required": [ + "data" + ], + "title": "RedactedThinkingBlock", + "description": "Redacted thinking block for previous responses without extended thinking.\n\nThis is used as a placeholder for assistant messages that were generated\nbefore extended thinking was enabled." + }, + "RemoteWorkspace": { + "properties": { + "kind": { + "type": "string", + "const": "RemoteWorkspace", + "title": "Kind", + "default": "RemoteWorkspace" + }, + "working_dir": { + "type": "string", + "title": "Working Dir", + "description": "The working directory for agent operations and tool execution." + }, + "host": { + "type": "string", + "title": "Host", + "description": "The remote host URL for the workspace." + }, + "api_key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Api Key", + "description": "API key for authenticating with the remote host." + } + }, + "type": "object", + "required": [ + "working_dir", + "host" + ], + "title": "RemoteWorkspace", + "description": "Remote Workspace Implementation." + }, + "RepoMicroagent": { + "properties": { + "kind": { + "type": "string", + "const": "RepoMicroagent", + "title": "Kind", + "default": "RepoMicroagent" + }, + "name": { + "type": "string", + "title": "Name" + }, + "content": { + "type": "string", + "title": "Content" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source", + "description": "The source path or identifier of the microagent. When it is None, it is treated as a programmatically defined microagent." + }, + "type": { + "type": "string", + "enum": [ + "knowledge", + "repo", + "task" + ], + "title": "Type", + "default": "repo" + }, + "mcp_tools": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Mcp Tools", + "description": "MCP tools configuration for the microagent. It should conform to the MCPConfig schema: https://gofastmcp.com/clients/client#configuration-format" + } + }, + "type": "object", + "required": [ + "name", + "content" + ], + "title": "RepoMicroagent", + "description": "Microagent specialized for repository-specific knowledge and guidelines.\n\nRepoMicroagents are loaded from `.openhands/microagents/repo.md` files within\nrepositories and contain private, repository-specific instructions that are\nautomatically loaded when\nworking with that repository. They are ideal for:\n - Repository-specific guidelines\n - Team practices and conventions\n - Project-specific workflows\n - Custom documentation references" + }, + "ResponseLatency": { + "properties": { + "model": { + "type": "string", + "title": "Model" + }, + "latency": { + "type": "number", + "minimum": 0.0, + "title": "Latency", + "description": "Latency must be non-negative" + }, + "response_id": { + "type": "string", + "title": "Response Id" + } + }, + "type": "object", + "required": [ + "model", + "latency", + "response_id" + ], + "title": "ResponseLatency", + "description": "Metric tracking the round-trip time per completion call." + }, + "SecurityAnalyzerBase": { + "properties": { + "kind": { + "type": "string", + "const": "LLMSecurityAnalyzer", + "title": "Kind", + "default": "LLMSecurityAnalyzer" + } + }, + "type": "object", + "title": "LLMSecurityAnalyzer", + "description": "LLM-based security analyzer.\n\nThis analyzer respects the security_risk attribute that can be set by the LLM\nwhen generating actions, similar to OpenHands' LLMRiskAnalyzer.\n\nIt provides a lightweight security analysis approach that leverages the LLM's\nunderstanding of action context and potential risks." + }, + "SecurityRisk": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH" + ], + "title": "SecurityRisk", + "description": "Security risk levels for actions.\n\nBased on OpenHands security risk levels but adapted for agent-sdk.\nInteger values allow for easy comparison and ordering." + }, + "SendMessageRequest": { + "properties": { + "role": { + "type": "string", + "enum": [ + "user", + "system", + "assistant", + "tool" + ], + "title": "Role", + "default": "user" + }, + "content": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/TextContent" + }, + { + "$ref": "#/components/schemas/ImageContent" + } + ] + }, + "type": "array", + "title": "Content" + }, + "run": { + "type": "boolean", + "title": "Run", + "description": "Whether the agent loop should automatically run if not running", + "default": false + } + }, + "type": "object", + "title": "SendMessageRequest", + "description": "Payload to send a message to the agent.\n\nThis is a simplified version of openhands.sdk.Message." + }, + "ServerInfo": { + "properties": { + "uptime": { + "type": "number", + "title": "Uptime" + }, + "idle_time": { + "type": "number", + "title": "Idle Time" + }, + "title": { + "type": "string", + "title": "Title", + "default": "OpenHands Agent Server" + }, + "version": { + "type": "string", + "title": "Version", + "default": "1.0.0a3" + }, + "docs": { + "type": "string", + "title": "Docs", + "default": "/docs" + }, + "redoc": { + "type": "string", + "title": "Redoc", + "default": "/redoc" + } + }, + "type": "object", + "required": [ + "uptime", + "idle_time" + ], + "title": "ServerInfo" + }, + "SetConfirmationPolicyRequest": { + "properties": { + "policy": { + "oneOf": [ + { + "$ref": "#/components/schemas/AlwaysConfirm" + }, + { + "$ref": "#/components/schemas/ConfirmRisky" + }, + { + "$ref": "#/components/schemas/NeverConfirm" + } + ], + "title": "ConfirmationPolicyBase", + "description": "The confirmation policy to set", + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__security__confirmation_policy__AlwaysConfirm-Input__1": "#/components/schemas/AlwaysConfirm", + "openhands__sdk__security__confirmation_policy__ConfirmRisky-Input__1": "#/components/schemas/ConfirmRisky", + "openhands__sdk__security__confirmation_policy__NeverConfirm-Input__1": "#/components/schemas/NeverConfirm" + } + } + } + }, + "type": "object", + "required": [ + "policy" + ], + "title": "SetConfirmationPolicyRequest", + "description": "Payload to set confirmation policy for a conversation." + }, + "StartConversationRequest": { + "properties": { + "agent": { + "$ref": "#/components/schemas/Agent-Output" + }, + "workspace": { + "$ref": "#/components/schemas/LocalWorkspace", + "description": "Working directory for agent operations and tool execution" + }, + "conversation_id": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Conversation Id", + "description": "Optional conversation ID. If not provided, a random UUID will be generated." + }, + "confirmation_policy": { + "oneOf": [ + { + "$ref": "#/components/schemas/AlwaysConfirm" + }, + { + "$ref": "#/components/schemas/ConfirmRisky" + }, + { + "$ref": "#/components/schemas/NeverConfirm" + } + ], + "title": "ConfirmationPolicyBase", + "description": "Controls when the conversation will prompt the user before continuing. Defaults to never.", + "default": { + "kind": "NeverConfirm" + }, + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__security__confirmation_policy__AlwaysConfirm-Input__1": "#/components/schemas/AlwaysConfirm", + "openhands__sdk__security__confirmation_policy__ConfirmRisky-Input__1": "#/components/schemas/ConfirmRisky", + "openhands__sdk__security__confirmation_policy__NeverConfirm-Input__1": "#/components/schemas/NeverConfirm" + } + } + }, + "initial_message": { + "anyOf": [ + { + "$ref": "#/components/schemas/SendMessageRequest" + }, + { + "type": "null" + } + ], + "description": "Initial message to pass to the LLM" + }, + "max_iterations": { + "type": "integer", + "minimum": 1.0, + "title": "Max Iterations", + "description": "If set, the max number of iterations the agent will run before stopping. This is useful to prevent infinite loops.", + "default": 500 + }, + "stuck_detection": { + "type": "boolean", + "title": "Stuck Detection", + "description": "If true, the conversation will use stuck detection to prevent infinite loops.", + "default": true + }, + "secrets": { + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/components/schemas/LookupSecret" + }, + { + "$ref": "#/components/schemas/StaticSecret" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__conversation__secret_source__LookupSecret-Input__1": "#/components/schemas/LookupSecret", + "openhands__sdk__conversation__secret_source__StaticSecret-Input__1": "#/components/schemas/StaticSecret" + } + } + }, + "type": "object", + "title": "Secrets", + "description": "Secrets available in the conversation" + } + }, + "type": "object", + "required": [ + "agent", + "workspace" + ], + "title": "StartConversationRequest", + "description": "Payload to create a new conversation.\n\nContains an Agent configuration along with conversation-specific options." + }, + "StaticSecret": { + "properties": { + "kind": { + "type": "string", + "const": "StaticSecret", + "title": "Kind", + "default": "StaticSecret" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Optional description for this secret" + }, + "value": { + "type": "string", + "format": "password", + "title": "Value", + "writeOnly": true + } + }, + "type": "object", + "required": [ + "value" + ], + "title": "StaticSecret", + "description": "A secret stored locally" + }, + "Success": { + "properties": { + "success": { + "type": "boolean", + "title": "Success", + "default": true + } + }, + "type": "object", + "title": "Success" + }, + "SystemPromptEvent": { + "properties": { + "kind": { + "type": "string", + "const": "SystemPromptEvent", + "title": "Kind", + "default": "SystemPromptEvent" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "agent" + }, + "system_prompt": { + "$ref": "#/components/schemas/TextContent", + "description": "The system prompt text" + }, + "tools": { + "items": { + "$ref": "#/components/schemas/ChatCompletionToolParam" + }, + "type": "array", + "title": "Tools", + "description": "List of tools in OpenAI tool format" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "system_prompt", + "tools" + ], + "title": "SystemPromptEvent", + "description": "System prompt added by the agent." + }, + "TaskItem": { + "properties": { + "title": { + "type": "string", + "title": "Title", + "description": "A brief title for the task." + }, + "notes": { + "type": "string", + "title": "Notes", + "description": "Additional details or notes about the task.", + "default": "" + }, + "status": { + "type": "string", + "enum": [ + "todo", + "in_progress", + "done" + ], + "title": "Status", + "description": "The current status of the task. One of 'todo', 'in_progress', or 'done'.", + "default": "todo" + } + }, + "type": "object", + "required": [ + "title" + ], + "title": "TaskItem" + }, + "TaskMicroagent": { + "properties": { + "kind": { + "type": "string", + "const": "TaskMicroagent", + "title": "Kind", + "default": "TaskMicroagent" + }, + "name": { + "type": "string", + "title": "Name" + }, + "content": { + "type": "string", + "title": "Content" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source", + "description": "The source path or identifier of the microagent. When it is None, it is treated as a programmatically defined microagent." + }, + "type": { + "type": "string", + "enum": [ + "knowledge", + "repo", + "task" + ], + "title": "Type", + "default": "task" + }, + "triggers": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Triggers", + "description": "List of triggers for the microagent" + }, + "inputs": { + "items": { + "$ref": "#/components/schemas/InputMetadata" + }, + "type": "array", + "title": "Inputs", + "description": "Input metadata for the microagent. Only exists for task microagents" + } + }, + "type": "object", + "required": [ + "name", + "content" + ], + "title": "TaskMicroagent", + "description": "TaskMicroagent is a special type of KnowledgeMicroagent that requires user input.\n\nThese microagents are triggered by a special format: \"/{agent_name}\"\nand will prompt the user for any required inputs before proceeding." + }, + "TaskTrackerAction": { + "properties": { + "kind": { + "type": "string", + "const": "TaskTrackerAction", + "title": "Kind", + "default": "TaskTrackerAction" + }, + "command": { + "type": "string", + "enum": [ + "view", + "plan" + ], + "title": "Command", + "description": "The command to execute. `view` shows the current task list. `plan` creates or updates the task list based on provided requirements and progress. Always `view` the current list before making changes.", + "default": "view" + }, + "task_list": { + "items": { + "$ref": "#/components/schemas/TaskItem" + }, + "type": "array", + "title": "Task List", + "description": "The full task list. Required parameter of `plan` command." + } + }, + "additionalProperties": false, + "type": "object", + "title": "TaskTrackerAction", + "description": "An action where the agent writes or updates a task list for task management." + }, + "TaskTrackerObservation": { + "properties": { + "kind": { + "type": "string", + "const": "TaskTrackerObservation", + "title": "Kind", + "default": "TaskTrackerObservation" + }, + "content": { + "type": "string", + "title": "Content", + "description": "The formatted task list or status message", + "default": "" + }, + "command": { + "type": "string", + "title": "Command", + "description": "The command that was executed", + "default": "" + }, + "task_list": { + "items": { + "$ref": "#/components/schemas/TaskItem" + }, + "type": "array", + "title": "Task List", + "description": "The current task list" + } + }, + "additionalProperties": false, + "type": "object", + "title": "TaskTrackerObservation", + "description": "This data class represents the result of a task tracking operation." + }, + "TextContent": { + "properties": { + "cache_prompt": { + "type": "boolean", + "title": "Cache Prompt", + "default": false + }, + "type": { + "type": "string", + "const": "text", + "title": "Type", + "default": "text" + }, + "text": { + "type": "string", + "title": "Text" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "text" + ], + "title": "TextContent" + }, + "ThinkAction": { + "properties": { + "kind": { + "type": "string", + "const": "ThinkAction", + "title": "Kind", + "default": "ThinkAction" + }, + "thought": { + "type": "string", + "title": "Thought", + "description": "The thought to log." + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "thought" + ], + "title": "ThinkAction", + "description": "Action for logging a thought without making any changes." + }, + "ThinkObservation": { + "properties": { + "kind": { + "type": "string", + "const": "ThinkObservation", + "title": "Kind", + "default": "ThinkObservation" + }, + "content": { + "type": "string", + "title": "Content", + "description": "Confirmation message.", + "default": "Your thought has been logged." + } + }, + "additionalProperties": false, + "type": "object", + "title": "ThinkObservation", + "description": "Observation returned after logging a thought." + }, + "ThinkingBlock": { + "properties": { + "type": { + "type": "string", + "const": "thinking", + "title": "Type", + "default": "thinking" + }, + "thinking": { + "type": "string", + "title": "Thinking", + "description": "The thinking content" + }, + "signature": { + "type": "string", + "title": "Signature", + "description": "Cryptographic signature for the thinking block" + } + }, + "type": "object", + "required": [ + "thinking", + "signature" + ], + "title": "ThinkingBlock", + "description": "Anthropic thinking block for extended thinking feature.\n\nThis represents the raw thinking blocks returned by Anthropic models\nwhen extended thinking is enabled. These blocks must be preserved\nand passed back to the API for tool use scenarios." + }, + "TokenUsage": { + "properties": { + "model": { + "type": "string", + "title": "Model", + "default": "" + }, + "prompt_tokens": { + "type": "integer", + "minimum": 0.0, + "title": "Prompt Tokens", + "description": "Prompt tokens must be non-negative", + "default": 0 + }, + "completion_tokens": { + "type": "integer", + "minimum": 0.0, + "title": "Completion Tokens", + "description": "Completion tokens must be non-negative", + "default": 0 + }, + "cache_read_tokens": { + "type": "integer", + "minimum": 0.0, + "title": "Cache Read Tokens", + "description": "Cache read tokens must be non-negative", + "default": 0 + }, + "cache_write_tokens": { + "type": "integer", + "minimum": 0.0, + "title": "Cache Write Tokens", + "description": "Cache write tokens must be non-negative", + "default": 0 + }, + "reasoning_tokens": { + "type": "integer", + "minimum": 0.0, + "title": "Reasoning Tokens", + "description": "Reasoning tokens must be non-negative", + "default": 0 + }, + "context_window": { + "type": "integer", + "minimum": 0.0, + "title": "Context Window", + "description": "Context window must be non-negative", + "default": 0 + }, + "per_turn_token": { + "type": "integer", + "minimum": 0.0, + "title": "Per Turn Token", + "description": "Per turn tokens must be non-negative", + "default": 0 + }, + "response_id": { + "type": "string", + "title": "Response Id", + "default": "" + } + }, + "type": "object", + "title": "TokenUsage", + "description": "Metric tracking detailed token usage per completion call." + }, + "Tool": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Name of the tool class, e.g., 'BashTool'. Import it from an `openhands.tools.` subpackage.", + "examples": [ + "BashTool", + "FileEditorTool", + "TaskTrackerTool" + ] + }, + "params": { + "additionalProperties": true, + "type": "object", + "title": "Params", + "description": "Parameters for the tool's .create() method, e.g., {'working_dir': '/app'}", + "examples": [ + { + "working_dir": "/workspace" + } + ] + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "Tool", + "description": "Defines a tool to be initialized for the agent.\n\nThis is only used in agent-sdk for type schema for server use." + }, + "UpdateConversationRequest": { + "properties": { + "title": { + "type": "string", + "maxLength": 200, + "minLength": 1, + "title": "Title", + "description": "New conversation title" + } + }, + "type": "object", + "required": [ + "title" + ], + "title": "UpdateConversationRequest", + "description": "Payload to update conversation metadata." + }, + "UpdateSecretsRequest": { + "properties": { + "secrets": { + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/components/schemas/LookupSecret" + }, + { + "$ref": "#/components/schemas/StaticSecret" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__conversation__secret_source__LookupSecret-Input__1": "#/components/schemas/LookupSecret", + "openhands__sdk__conversation__secret_source__StaticSecret-Input__1": "#/components/schemas/StaticSecret" + } + } + }, + "type": "object", + "title": "Secrets", + "description": "Dictionary mapping secret keys to values" + } + }, + "type": "object", + "required": [ + "secrets" + ], + "title": "UpdateSecretsRequest", + "description": "Payload to update secrets in a conversation." + }, + "UserRejectObservation": { + "properties": { + "kind": { + "type": "string", + "const": "UserRejectObservation", + "title": "Kind", + "default": "UserRejectObservation" + }, + "id": { + "type": "string", + "title": "Id", + "description": "Unique event id (ULID/UUID)" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "description": "Event timestamp" + }, + "source": { + "type": "string", + "enum": [ + "agent", + "user", + "environment" + ], + "title": "Source", + "default": "environment" + }, + "tool_name": { + "type": "string", + "title": "Tool Name", + "description": "The tool name that this observation is responding to" + }, + "tool_call_id": { + "type": "string", + "title": "Tool Call Id", + "description": "The tool call id that this observation is responding to" + }, + "rejection_reason": { + "type": "string", + "title": "Rejection Reason", + "description": "Reason for rejecting the action", + "default": "User rejected the action" + }, + "action_id": { + "type": "string", + "title": "Action Id", + "description": "The action id that this observation is responding to" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "tool_name", + "tool_call_id", + "action_id" + ], + "title": "UserRejectObservation", + "description": "Observation when user rejects an action in confirmation mode." + }, + "VSCodeUrlResponse": { + "properties": { + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Url" + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "VSCodeUrlResponse", + "description": "Response model for VSCode URL." + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "Event": { + "oneOf": [ + { + "$ref": "#/components/schemas/Condensation" + }, + { + "$ref": "#/components/schemas/CondensationRequest" + }, + { + "$ref": "#/components/schemas/CondensationSummaryEvent" + }, + { + "$ref": "#/components/schemas/ConversationStateUpdateEvent" + }, + { + "$ref": "#/components/schemas/ActionEvent" + }, + { + "$ref": "#/components/schemas/MessageEvent" + }, + { + "$ref": "#/components/schemas/AgentErrorEvent" + }, + { + "$ref": "#/components/schemas/ObservationEvent" + }, + { + "$ref": "#/components/schemas/UserRejectObservation" + }, + { + "$ref": "#/components/schemas/SystemPromptEvent" + }, + { + "$ref": "#/components/schemas/PauseEvent" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__event__condenser__Condensation-Output__1": "#/components/schemas/Condensation", + "openhands__sdk__event__condenser__CondensationRequest-Output__1": "#/components/schemas/CondensationRequest", + "openhands__sdk__event__condenser__CondensationSummaryEvent-Output__1": "#/components/schemas/CondensationSummaryEvent", + "openhands__sdk__event__conversation_state__ConversationStateUpdateEvent-Output__1": "#/components/schemas/ConversationStateUpdateEvent", + "openhands__sdk__event__llm_convertible__action__ActionEvent-Output__1": "#/components/schemas/ActionEvent", + "openhands__sdk__event__llm_convertible__message__MessageEvent-Output__1": "#/components/schemas/MessageEvent", + "openhands__sdk__event__llm_convertible__observation__AgentErrorEvent-Output__1": "#/components/schemas/AgentErrorEvent", + "openhands__sdk__event__llm_convertible__observation__ObservationEvent-Output__1": "#/components/schemas/ObservationEvent", + "openhands__sdk__event__llm_convertible__observation__UserRejectObservation-Output__1": "#/components/schemas/UserRejectObservation", + "openhands__sdk__event__llm_convertible__system__SystemPromptEvent-Output__1": "#/components/schemas/SystemPromptEvent", + "openhands__sdk__event__user_action__PauseEvent-Output__1": "#/components/schemas/PauseEvent" + } + }, + "title": "Event" + }, + "BashEventBase": { + "oneOf": [ + { + "$ref": "#/components/schemas/BashCommand" + }, + { + "$ref": "#/components/schemas/BashOutput" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__agent_server__models__BashCommand-Output__1": "#/components/schemas/BashCommand", + "openhands__agent_server__models__BashOutput-Output__1": "#/components/schemas/BashOutput" + } + }, + "title": "BashEventBase" + }, + "Action": { + "oneOf": [ + { + "$ref": "#/components/schemas/MCPToolAction" + }, + { + "$ref": "#/components/schemas/FinishAction" + }, + { + "$ref": "#/components/schemas/ThinkAction" + }, + { + "$ref": "#/components/schemas/BrowserClickAction" + }, + { + "$ref": "#/components/schemas/BrowserCloseTabAction" + }, + { + "$ref": "#/components/schemas/BrowserGetContentAction" + }, + { + "$ref": "#/components/schemas/BrowserGetStateAction" + }, + { + "$ref": "#/components/schemas/BrowserGoBackAction" + }, + { + "$ref": "#/components/schemas/BrowserListTabsAction" + }, + { + "$ref": "#/components/schemas/BrowserNavigateAction" + }, + { + "$ref": "#/components/schemas/BrowserScrollAction" + }, + { + "$ref": "#/components/schemas/BrowserSwitchTabAction" + }, + { + "$ref": "#/components/schemas/BrowserTypeAction" + }, + { + "$ref": "#/components/schemas/ExecuteBashAction" + }, + { + "$ref": "#/components/schemas/FileEditorAction" + }, + { + "$ref": "#/components/schemas/TaskTrackerAction" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__mcp__definition__MCPToolAction-Output__1": "#/components/schemas/MCPToolAction", + "openhands__sdk__tool__builtins__finish__FinishAction-Output__1": "#/components/schemas/FinishAction", + "openhands__sdk__tool__builtins__think__ThinkAction-Output__1": "#/components/schemas/ThinkAction", + "openhands__tools__browser_use__definition__BrowserClickAction-Output__1": "#/components/schemas/BrowserClickAction", + "openhands__tools__browser_use__definition__BrowserCloseTabAction-Output__1": "#/components/schemas/BrowserCloseTabAction", + "openhands__tools__browser_use__definition__BrowserGetContentAction-Output__1": "#/components/schemas/BrowserGetContentAction", + "openhands__tools__browser_use__definition__BrowserGetStateAction-Output__1": "#/components/schemas/BrowserGetStateAction", + "openhands__tools__browser_use__definition__BrowserGoBackAction-Output__1": "#/components/schemas/BrowserGoBackAction", + "openhands__tools__browser_use__definition__BrowserListTabsAction-Output__1": "#/components/schemas/BrowserListTabsAction", + "openhands__tools__browser_use__definition__BrowserNavigateAction-Output__1": "#/components/schemas/BrowserNavigateAction", + "openhands__tools__browser_use__definition__BrowserScrollAction-Output__1": "#/components/schemas/BrowserScrollAction", + "openhands__tools__browser_use__definition__BrowserSwitchTabAction-Output__1": "#/components/schemas/BrowserSwitchTabAction", + "openhands__tools__browser_use__definition__BrowserTypeAction-Output__1": "#/components/schemas/BrowserTypeAction", + "openhands__tools__execute_bash__definition__ExecuteBashAction-Output__1": "#/components/schemas/ExecuteBashAction", + "openhands__tools__file_editor__definition__FileEditorAction-Output__1": "#/components/schemas/FileEditorAction", + "openhands__tools__task_tracker__definition__TaskTrackerAction-Output__1": "#/components/schemas/TaskTrackerAction" + } + }, + "title": "Action" + }, + "CondenserBase": { + "oneOf": [ + { + "$ref": "#/components/schemas/LLMSummarizingCondenser" + }, + { + "$ref": "#/components/schemas/NoOpCondenser" + }, + { + "$ref": "#/components/schemas/PipelineCondenser-Output" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__context__condenser__llm_summarizing_condenser__LLMSummarizingCondenser-Output__1": "#/components/schemas/LLMSummarizingCondenser", + "openhands__sdk__context__condenser__no_op_condenser__NoOpCondenser-Output__1": "#/components/schemas/NoOpCondenser", + "openhands__sdk__context__condenser__pipeline_condenser__PipelineCondenser-Output__1": "#/components/schemas/PipelineCondenser-Output" + } + }, + "title": "CondenserBase" + }, + "BaseMicroagent": { + "oneOf": [ + { + "$ref": "#/components/schemas/KnowledgeMicroagent" + }, + { + "$ref": "#/components/schemas/RepoMicroagent" + }, + { + "$ref": "#/components/schemas/TaskMicroagent" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__context__microagents__microagent__KnowledgeMicroagent-Input__1": "#/components/schemas/KnowledgeMicroagent", + "openhands__sdk__context__microagents__microagent__RepoMicroagent-Input__1": "#/components/schemas/RepoMicroagent", + "openhands__sdk__context__microagents__microagent__TaskMicroagent-Input__1": "#/components/schemas/TaskMicroagent" + } + }, + "title": "BaseMicroagent" + }, + "BaseWorkspace": { + "oneOf": [ + { + "$ref": "#/components/schemas/LocalWorkspace" + }, + { + "$ref": "#/components/schemas/RemoteWorkspace" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__workspace__local__LocalWorkspace-Output__1": "#/components/schemas/LocalWorkspace", + "openhands__sdk__workspace__remote__base__RemoteWorkspace-Output__1": "#/components/schemas/RemoteWorkspace" + } + }, + "title": "BaseWorkspace" + }, + "ConfirmationPolicyBase": { + "oneOf": [ + { + "$ref": "#/components/schemas/AlwaysConfirm" + }, + { + "$ref": "#/components/schemas/ConfirmRisky" + }, + { + "$ref": "#/components/schemas/NeverConfirm" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__security__confirmation_policy__AlwaysConfirm-Output__1": "#/components/schemas/AlwaysConfirm", + "openhands__sdk__security__confirmation_policy__ConfirmRisky-Output__1": "#/components/schemas/ConfirmRisky", + "openhands__sdk__security__confirmation_policy__NeverConfirm-Output__1": "#/components/schemas/NeverConfirm" + } + }, + "title": "ConfirmationPolicyBase" + }, + "Observation": { + "oneOf": [ + { + "$ref": "#/components/schemas/MCPToolObservation" + }, + { + "$ref": "#/components/schemas/FinishObservation" + }, + { + "$ref": "#/components/schemas/ThinkObservation" + }, + { + "$ref": "#/components/schemas/BrowserObservation" + }, + { + "$ref": "#/components/schemas/ExecuteBashObservation" + }, + { + "$ref": "#/components/schemas/FileEditorObservation" + }, + { + "$ref": "#/components/schemas/TaskTrackerObservation" + } + ], + "discriminator": { + "propertyName": "kind", + "mapping": { + "openhands__sdk__mcp__definition__MCPToolObservation-Output__1": "#/components/schemas/MCPToolObservation", + "openhands__sdk__tool__builtins__finish__FinishObservation-Output__1": "#/components/schemas/FinishObservation", + "openhands__sdk__tool__builtins__think__ThinkObservation-Output__1": "#/components/schemas/ThinkObservation", + "openhands__tools__browser_use__definition__BrowserObservation-Output__1": "#/components/schemas/BrowserObservation", + "openhands__tools__execute_bash__definition__ExecuteBashObservation-Output__1": "#/components/schemas/ExecuteBashObservation", + "openhands__tools__file_editor__definition__FileEditorObservation-Output__1": "#/components/schemas/FileEditorObservation", + "openhands__tools__task_tracker__definition__TaskTrackerObservation-Output__1": "#/components/schemas/TaskTrackerObservation" + } + }, + "title": "Observation" + } + } + } +} \ No newline at end of file From 76dac25462b69e2379efa89a3608d6a576a2e903 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Tue, 21 Oct 2025 03:16:09 +0800 Subject: [PATCH 29/46] Update .github/workflows/sync-agent-sdk-openapi.yml Co-authored-by: Engel Nyst --- .github/workflows/sync-agent-sdk-openapi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-agent-sdk-openapi.yml b/.github/workflows/sync-agent-sdk-openapi.yml index 39274a34..0a290933 100644 --- a/.github/workflows/sync-agent-sdk-openapi.yml +++ b/.github/workflows/sync-agent-sdk-openapi.yml @@ -23,8 +23,8 @@ jobs: if: github.actor != 'github-actions[bot]' env: - # Prefer a PAT if agent-sdk is private; otherwise GITHUB_TOKEN is fine. - GH_CLONE_TOKEN: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }} + # GITHUB_TOKEN is fine. + GH_CLONE_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For workflow_dispatch this will be set; for push/schedule it will fall back to main. AGENT_SDK_REF: ${{ github.event.inputs.agent_sdk_ref || 'main' }} From 752667aa39f19b430139de3f47f51711a3e13d59 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Tue, 21 Oct 2025 03:16:46 +0800 Subject: [PATCH 30/46] Update .openhands/microagents/repo.md Co-authored-by: Engel Nyst --- .openhands/microagents/repo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.openhands/microagents/repo.md b/.openhands/microagents/repo.md index 3510965f..24ac8e26 100644 --- a/.openhands/microagents/repo.md +++ b/.openhands/microagents/repo.md @@ -47,7 +47,7 @@ pattern = r'```python[^\n]*\s+(examples/[^\s]+\.py)\n(.*?)```' ## MDX Documentation Format ### Standard Structure -Documentation files follow this pattern (see `docs/sdk/guides/custom-tools.mdx` and `docs/sdk/guides/mcp.mdx` as reference): +Documentation is deployed with Mintlify from GitHub. The files follow this pattern (see `docs/sdk/guides/custom-tools.mdx` and `docs/sdk/guides/mcp.mdx` as reference): 1. **Frontmatter** - YAML metadata with title and description 2. **Introduction** - Brief overview of the feature From e3ba5e05d38d973ba431c6e7e0ef323b02e0ab31 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Mon, 20 Oct 2025 15:21:01 -0400 Subject: [PATCH 31/46] add api reference for agent-sdk server --- docs.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs.json b/docs.json index b72219e2..376e0a5a 100644 --- a/docs.json +++ b/docs.json @@ -187,6 +187,12 @@ { "group": "Remote Agent Server", "pages": [ + { + "group": "API Reference", + "openapi": { + "source": "/openapi/agent-sdk.json" + } + } ] }, { From 90e39794f6e16f2248abdfc4a00ba2bdc94c0c46 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Tue, 21 Oct 2025 03:28:27 +0800 Subject: [PATCH 32/46] Update sdk/index.mdx Co-authored-by: Engel Nyst --- sdk/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/index.mdx b/sdk/index.mdx index e1a04ea8..8065e6a1 100644 --- a/sdk/index.mdx +++ b/sdk/index.mdx @@ -5,7 +5,7 @@ icon: code mode: wide --- -The OpenHands Cloud Agent SDK is a set of Python and REST APIs for building **agents that work with code**. +The OpenHands Agent SDK is a set of Python and REST APIs for building **agents that work with code**. You can use the Cloud Agent SDK for: From bc114c46d7f2846dc31a3dc9b9f86bb21d681af6 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Tue, 21 Oct 2025 03:28:36 +0800 Subject: [PATCH 33/46] Update sdk/index.mdx Co-authored-by: Engel Nyst --- sdk/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/index.mdx b/sdk/index.mdx index 8065e6a1..c7523c18 100644 --- a/sdk/index.mdx +++ b/sdk/index.mdx @@ -63,7 +63,7 @@ Other SDKs (e.g. [Claude Code](https://github.com/anthropics/claude-agent-sdk-py title="Getting Started Guide" href="/sdk/guides/getting-started" > - Install the SDK, run your first agent, and explore comprehensive guides. + Install the SDK, run your first agent, and explore the guides. From a3aaac26cbe088d016e9f16f97248ca98489cc40 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Tue, 21 Oct 2025 03:28:43 +0800 Subject: [PATCH 34/46] Update sdk/index.mdx Co-authored-by: Engel Nyst --- sdk/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/index.mdx b/sdk/index.mdx index c7523c18..ec41cdcf 100644 --- a/sdk/index.mdx +++ b/sdk/index.mdx @@ -7,7 +7,7 @@ mode: wide The OpenHands Agent SDK is a set of Python and REST APIs for building **agents that work with code**. -You can use the Cloud Agent SDK for: +You can use the OpenHands Agent SDK for: - One-off tasks, like building a README for your repo - Routine maintenance tasks, like updating dependencies From 613305763ef30ebcc0912c67e7377ab27dde5b9e Mon Sep 17 00:00:00 2001 From: openhands Date: Mon, 20 Oct 2025 19:32:23 +0000 Subject: [PATCH 35/46] docs: reference hello world example from agent-sdk Update sdk/getting-started.mdx to reference the actual hello world example file (examples/01_standalone_sdk/01_hello_world.py) instead of maintaining inline code. This allows the sync_code_blocks.py workflow to automatically keep the documentation in sync with the agent-sdk repository. Co-authored-by: openhands --- sdk/getting-started.mdx | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/sdk/getting-started.mdx b/sdk/getting-started.mdx index d4308365..8c3a4faa 100644 --- a/sdk/getting-started.mdx +++ b/sdk/getting-started.mdx @@ -69,44 +69,47 @@ export LLM_API_KEY=your-api-key-here ### Step 3: Run Your First Agent -Create a file `hello_agent.py`: +Here's a complete example that creates an agent and asks it to perform a simple task: -```python +```python icon="python" expandable examples/01_standalone_sdk/01_hello_world.py import os + from pydantic import SecretStr + from openhands.sdk import LLM, Conversation -from openhands.sdk.preset.default import get_default_agent +from openhands.tools.preset.default import get_default_agent + -# Configure LLM +# 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="openhands/claude-sonnet-4-5-20250929", + model=model, api_key=SecretStr(api_key), + base_url=base_url, + usage_id="agent", ) +agent = get_default_agent(llm=llm, cli_mode=True) -# Create agent with default tools and configuration -agent = get_default_agent( - llm=llm, - working_dir=os.getcwd(), - cli_mode=True, # Disable browser tools for CLI environments -) +# Start a conversation and send some messages +cwd = os.getcwd() +conversation = Conversation(agent=agent, workspace=cwd) -# Create conversation, send a message, and run -conversation = Conversation(agent=agent) -conversation.send_message("Create a Python file that prints 'Hello, World!'") +# Send a message and let the agent run +conversation.send_message("Write 3 facts about the current project into FACTS.txt.") conversation.run() ``` -Run the agent: +Run the example: ```bash -uv run python hello_agent.py +uv run python examples/01_standalone_sdk/01_hello_world.py ``` -You should see the agent understand your request, create a Python file, write the code, and report completion. - -See [`examples/01_standalone_sdk/01_hello_world.py`](https://github.com/All-Hands-AI/agent-sdk/blob/main/examples/01_standalone_sdk/01_hello_world.py) for the complete example. +You should see the agent understand your request, explore the project, and create a file with facts about it. ## Core Concepts From 4696bafe893d0d024bed4dc227d02acf3011a691 Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 19:42:52 +0000 Subject: [PATCH 36/46] docs: ignore local agent-sdk checkout; remove dangling submodule gitlink - Remove agent-sdk gitlink from index and add to .gitignore - Avoid broken submodule state and clarify intended local checkout behavior Co-authored-by: openhands --- .gitignore | 6 ++++++ agent-sdk | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .gitignore delete mode 160000 agent-sdk diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7eb46d79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Local checkout of agent-sdk for docs workflows and local testing +agent-sdk/ + +# OS junk +.DS_Store +Thumbs.db diff --git a/agent-sdk b/agent-sdk deleted file mode 160000 index 53ed9bb6..00000000 --- a/agent-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 53ed9bb631943008d2e15f669656aceb9092d34b From 1cff682e8adc21db355433a673337a6ec29efdce Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 19:48:58 +0000 Subject: [PATCH 37/46] ci(docs): make code-block sync no-op safe and simpler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Don’t hard-fail when agent-sdk path is missing; exit cleanly - Replace blocks using known slice instead of inner regex - Remove GITHUB_OUTPUT coupling; rely on git status in workflow Co-authored-by: openhands --- .github/scripts/sync_code_blocks.py | 45 ++++++++++++----------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/.github/scripts/sync_code_blocks.py b/.github/scripts/sync_code_blocks.py index 2ba2e759..f9e8edde 100755 --- a/.github/scripts/sync_code_blocks.py +++ b/.github/scripts/sync_code_blocks.py @@ -114,10 +114,9 @@ def resolve_paths() -> tuple[Path, Path]: print(f"📁 Using Agent SDK path: {p}") return docs_root, p - # If none exist, fail with a helpful message - print("❌ Agent SDK path not found in any of the expected locations.") - print(" Set AGENT_SDK_PATH, or checkout the repo to one of the tried paths above.") - sys.exit(1) + # If none exist, skip gracefully (no-op) rather than failing CI + print("❌ Agent SDK path not found in any of the expected locations. Skipping sync.") + return docs_root, Path() def update_doc_file( @@ -150,22 +149,17 @@ def update_doc_file( adj_start = start_pos + offset adj_end = end_pos + offset - opening_line_match = re.search( - r"```python[^\n]*\s+" + re.escape(file_ref), - new_content[adj_start:adj_end], - ) - if opening_line_match: - opening_line = opening_line_match.group(0) - # Preserve trailing newline behavior - if actual_content.endswith("\n"): - new_block = f"{opening_line}\n{actual_content}```" - else: - new_block = f"{opening_line}\n{actual_content}\n```" - old_block = new_content[adj_start:adj_end] - - new_content = new_content[:adj_start] + new_block + new_content[adj_end:] - offset += len(new_block) - len(old_block) - changes_made = True + old_block = new_content[adj_start:adj_end] + opening_line = old_block.split("\n", 1)[0] + # Preserve trailing newline behavior + if actual_content.endswith("\n"): + new_block = f"{opening_line}\n{actual_content}```" + else: + new_block = f"{opening_line}\n{actual_content}\n```" + + new_content = new_content[:adj_start] + new_block + new_content[adj_end:] + offset += len(new_block) - len(old_block) + changes_made = True if changes_made: try: @@ -181,6 +175,11 @@ def update_doc_file( def main() -> None: docs_root, agent_sdk_path = resolve_paths() + if not agent_sdk_path or not agent_sdk_path.exists(): + # No-op if agent-sdk isn't checked out + print("No agent-sdk checkout available. Nothing to sync.") + print("=" * 60) + return # Find all MDX files mdx_files = find_mdx_files(docs_root) @@ -211,14 +210,8 @@ def main() -> None: print(f"✅ Updated {total_changes} file(s):") for file in files_changed: print(f" - {file}") - if "GITHUB_OUTPUT" in os.environ: - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: - f.write("changes=true\n") else: print("✅ All code blocks are in sync!") - if "GITHUB_OUTPUT" in os.environ: - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f: - f.write("changes=false\n") print("=" * 60) From 36200cc13ab238b90846fd0a59578060e357de13 Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 19:51:24 +0000 Subject: [PATCH 38/46] ci(docs): tighten sync workflow triggers and land via PR - Limit to main/sdk pushes and PRs - Use peter-evans/create-pull-request to open PR instead of direct push Co-authored-by: openhands --- .github/workflows/sync-docs-code-blocks.yml | 29 ++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/.github/workflows/sync-docs-code-blocks.yml b/.github/workflows/sync-docs-code-blocks.yml index e4ac2fc0..0321f337 100644 --- a/.github/workflows/sync-docs-code-blocks.yml +++ b/.github/workflows/sync-docs-code-blocks.yml @@ -3,7 +3,12 @@ name: Sync Documentation Code Blocks on: push: branches: - - '**' # run on every branch + - main + - sdk + pull_request: + branches: + - main + - sdk schedule: # Run daily at 2 AM UTC to catch any changes - cron: '0 2 * * *' @@ -55,15 +60,15 @@ jobs: echo "changes=false" >> "$GITHUB_OUTPUT" fi - - name: Commit and push changes - if: steps.detect_changes.outputs.changes == 'true' # <-- updated reference - shell: bash - run: | - set -euo pipefail - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "docs: sync code blocks from agent-sdk examples + - name: Commit changes to sync branch and open PR + if: steps.detect_changes.outputs.changes == 'true' + uses: peter-evans/create-pull-request@v6 + with: + commit-message: | + docs: sync code blocks from agent-sdk examples - Synced from agent-sdk ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }}" - git push + Synced from agent-sdk ref: ${{ github.event.inputs.agent_sdk_ref || 'main' }} + branch: chore/sync-docs-code-blocks + title: "docs: sync code blocks from agent-sdk examples" + body: "Automated sync of code blocks from agent-sdk examples." + labels: documentation From f77539849423a207966b6a3a6156f68a9b0a7f83 Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 19:54:28 +0000 Subject: [PATCH 39/46] docs(sdk): fix links, imports, and examples; clean up workflows - Fix /sdk/getting-started link on SDK index - Hello World: use openhands.sdk.preset.default.get_default_agent and usage_id - Remove duplicate heading in custom-tools example - Workflow: only run on main/sdk and open PR for updates - Script: no-op if agent-sdk missing; simpler replacement - Ignore local agent-sdk checkout and remove gitlink Co-authored-by: openhands --- sdk/guides/custom-tools.mdx | 1 - sdk/guides/hello-world.mdx | 4 ++-- sdk/index.mdx | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sdk/guides/custom-tools.mdx b/sdk/guides/custom-tools.mdx index 6ab533df..0860d985 100644 --- a/sdk/guides/custom-tools.mdx +++ b/sdk/guides/custom-tools.mdx @@ -197,7 +197,6 @@ tools = [ Tool(name="BashAndGrepToolSet"), ] -# Agent agent = Agent(llm=llm, tools=tools) llm_messages = [] # collect raw LLM messages diff --git a/sdk/guides/hello-world.mdx b/sdk/guides/hello-world.mdx index d33e6c11..12f4e7ee 100644 --- a/sdk/guides/hello-world.mdx +++ b/sdk/guides/hello-world.mdx @@ -15,7 +15,7 @@ import os from pydantic import SecretStr from openhands.sdk import LLM, Conversation -from openhands.tools.preset.default import get_default_agent +from openhands.sdk.preset.default import get_default_agent # Configure LLM and agent @@ -55,7 +55,7 @@ llm = LLM( model=model, api_key=SecretStr(api_key), base_url=base_url, # Optional - service_id="agent" + usage_id="agent" ) ``` diff --git a/sdk/index.mdx b/sdk/index.mdx index e1a04ea8..da21dc26 100644 --- a/sdk/index.mdx +++ b/sdk/index.mdx @@ -61,7 +61,7 @@ Other SDKs (e.g. [Claude Code](https://github.com/anthropics/claude-agent-sdk-py Install the SDK, run your first agent, and explore comprehensive guides. From edf3e5938cb16b15d18bdb4899dbbf056314e202 Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 19:58:22 +0000 Subject: [PATCH 40/46] docs(sdk): use usage_id terminology in LLM docs (was service_id)\n\nCo-authored-by: openhands --- sdk/arch/llms/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/arch/llms/index.mdx b/sdk/arch/llms/index.mdx index 23de5cfc..0a93cbea 100644 --- a/sdk/arch/llms/index.mdx +++ b/sdk/arch/llms/index.mdx @@ -49,7 +49,7 @@ from openhands.sdk import LLM llm = LLM( model="anthropic/claude-sonnet-4.1", api_key=SecretStr("sk-ant-123"), - service_id="primary", + usage_id="primary", temperature=0.1, timeout=120, ) @@ -57,7 +57,7 @@ llm = LLM( Key concepts: -- **`service_id`** identifies an LLM configuration when storing it in a registry +- **`usage_id`** identifies an LLM configuration when storing it in a registry or persisting it between runs. - **Retry settings** (`num_retries`, `retry_min_wait`, etc.) apply uniformly to all providers through LiteLLM. From c20cae0bd32a754240157e6bf2a8f36513222de4 Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 20:00:48 +0000 Subject: [PATCH 41/46] docs: add Agent SDK OpenAPI tab and align domain to openhands.dev - Add separate API tab for agent-sdk.json (generated by workflow) - Update company/blog/cloud anchors to openhands.dev Co-authored-by: openhands --- docs.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs.json b/docs.json index b72219e2..cd011730 100644 --- a/docs.json +++ b/docs.json @@ -218,25 +218,29 @@ ] }, { - "tab": "API Reference", + "tab": "OpenHands (Core) API", "openapi": "openapi/openapi.json" + }, + { + "tab": "Agent SDK (API)", + "openapi": "openapi/agent-sdk.json" } ], "global": { "anchors": [ { "anchor": "Company", - "href": "https://www.all-hands.dev/", + "href": "https://openhands.dev/", "icon": "house" }, { "anchor": "Blog", - "href": "https://www.all-hands.dev/blog", + "href": "https://openhands.dev/blog", "icon": "newspaper" }, { "anchor": "OpenHands Cloud", - "href": "https://app.all-hands.dev", + "href": "https://app.openhands.dev", "icon": "cloud" } ] From 1bd200bc36220d34d714a9b1f26c8a0072214c07 Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 20:00:53 +0000 Subject: [PATCH 42/46] chore: migrate external links to openhands.dev domain - Replace all-hands.dev -> openhands.dev for app/docs/blog/home - Preserve docker.* and jira.* hosts as-is Co-authored-by: openhands --- index.mdx | 2 +- openapi/openapi.json | 2 +- openhands/usage/cloud/bitbucket-installation.mdx | 2 +- openhands/usage/cloud/cloud-api.mdx | 16 ++++++++-------- openhands/usage/cloud/github-installation.mdx | 2 +- openhands/usage/cloud/gitlab-installation.mdx | 2 +- openhands/usage/cloud/openhands-cloud.mdx | 2 +- openhands/usage/cloud/pro-subscription.mdx | 2 +- .../project-management/jira-dc-integration.mdx | 4 ++-- .../project-management/jira-integration.mdx | 4 ++-- .../project-management/linear-integration.mdx | 4 ++-- openhands/usage/cloud/slack-installation.mdx | 2 +- openhands/usage/llms/llms.mdx | 4 ++-- openhands/usage/llms/local-llms.mdx | 2 +- openhands/usage/quick-start.mdx | 2 +- openhands/usage/run-openhands/local-setup.mdx | 2 +- openhands/usage/troubleshooting/feedback.mdx | 2 +- sdk/getting-started.mdx | 4 ++-- sdk/guides/hello-world.mdx | 2 +- sdk/index.mdx | 4 ++-- success-stories/index.mdx | 4 ++-- 21 files changed, 35 insertions(+), 35 deletions(-) diff --git a/index.mdx b/index.mdx index 64d064d7..7c80789f 100644 --- a/index.mdx +++ b/index.mdx @@ -114,7 +114,7 @@ it can modify code, run commands, browse the web, call APIs, and yes-even copy c OpenHands has an active community on Slack. diff --git a/openapi/openapi.json b/openapi/openapi.json index d073f858..7c808d37 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -3931,7 +3931,7 @@ }, "servers": [ { - "url": "https://app.all-hands.dev", + "url": "https://app.openhands.dev", "description": "Production server" }, { diff --git a/openhands/usage/cloud/bitbucket-installation.mdx b/openhands/usage/cloud/bitbucket-installation.mdx index eeca4753..092b3d96 100644 --- a/openhands/usage/cloud/bitbucket-installation.mdx +++ b/openhands/usage/cloud/bitbucket-installation.mdx @@ -6,7 +6,7 @@ description: This guide walks you through the process of installing OpenHands Cl ## Prerequisites -- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a Bitbucket account](/openhands/usage/cloud/openhands-cloud). +- Signed in to [OpenHands Cloud](https://app.openhands.dev) with [a Bitbucket account](/openhands/usage/cloud/openhands-cloud). ## Adding Bitbucket Repository Access diff --git a/openhands/usage/cloud/cloud-api.mdx b/openhands/usage/cloud/cloud-api.mdx index 693b8505..e34ec15d 100644 --- a/openhands/usage/cloud/cloud-api.mdx +++ b/openhands/usage/cloud/cloud-api.mdx @@ -5,14 +5,14 @@ description: OpenHands Cloud provides a REST API that allows you to programmatic --- For the available API endpoints, refer to the -[OpenHands API Reference](https://docs.all-hands.dev/api-reference). +[OpenHands API Reference](https://docs.openhands.dev/api-reference). ## Obtaining an API Key To use the OpenHands Cloud API, you'll need to generate an API key: -1. Log in to your [OpenHands Cloud](https://app.all-hands.dev) account. -2. Navigate to the [Settings > API Keys](https://app.all-hands.dev/settings/api-keys) page. +1. Log in to your [OpenHands Cloud](https://app.openhands.dev) account. +2. Navigate to the [Settings > API Keys](https://app.openhands.dev/settings/api-keys) page. 3. Click `Create API Key`. 4. Give your key a descriptive name (Example: "Development" or "Production") and select `Create`. 5. Copy the generated API key and store it securely. It will only be shown once. @@ -27,7 +27,7 @@ To start a new conversation with OpenHands to perform a task, ```bash - curl -X POST "https://app.all-hands.dev/api/conversations" \ + curl -X POST "https://app.openhands.dev/api/conversations" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ @@ -41,7 +41,7 @@ To start a new conversation with OpenHands to perform a task, import requests api_key = "YOUR_API_KEY" - url = "https://app.all-hands.dev/api/conversations" + url = "https://app.openhands.dev/api/conversations" headers = { "Authorization": f"Bearer {api_key}", @@ -56,14 +56,14 @@ To start a new conversation with OpenHands to perform a task, response = requests.post(url, headers=headers, json=data) conversation = response.json() - print(f"Conversation Link: https://app.all-hands.dev/conversations/{conversation['conversation_id']}") + print(f"Conversation Link: https://app.openhands.dev/conversations/{conversation['conversation_id']}") print(f"Status: {conversation['status']}") ``` ```typescript const apiKey = "YOUR_API_KEY"; - const url = "https://app.all-hands.dev/api/conversations"; + const url = "https://app.openhands.dev/api/conversations"; const headers = { "Authorization": `Bearer ${apiKey}`, @@ -85,7 +85,7 @@ To start a new conversation with OpenHands to perform a task, const conversation = await response.json(); - console.log(`Conversation Link: https://app.all-hands.dev/conversations/${conversation.id}`); + console.log(`Conversation Link: https://app.openhands.dev/conversations/${conversation.id}`); console.log(`Status: ${conversation.status}`); return conversation; diff --git a/openhands/usage/cloud/github-installation.mdx b/openhands/usage/cloud/github-installation.mdx index ce4a5de9..1a7dc489 100644 --- a/openhands/usage/cloud/github-installation.mdx +++ b/openhands/usage/cloud/github-installation.mdx @@ -6,7 +6,7 @@ description: This guide walks you through the process of installing OpenHands Cl ## Prerequisites -- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a GitHub account](/openhands/usage/cloud/openhands-cloud). +- Signed in to [OpenHands Cloud](https://app.openhands.dev) with [a GitHub account](/openhands/usage/cloud/openhands-cloud). ## Adding GitHub Repository Access diff --git a/openhands/usage/cloud/gitlab-installation.mdx b/openhands/usage/cloud/gitlab-installation.mdx index f1807dcf..38ae0ed8 100644 --- a/openhands/usage/cloud/gitlab-installation.mdx +++ b/openhands/usage/cloud/gitlab-installation.mdx @@ -6,7 +6,7 @@ description: This guide walks you through the process of installing OpenHands Cl ## Prerequisites -- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a GitLab account](/openhands/usage/cloud/openhands-cloud). +- Signed in to [OpenHands Cloud](https://app.openhands.dev) with [a GitLab account](/openhands/usage/cloud/openhands-cloud). ## Adding GitLab Repository Access diff --git a/openhands/usage/cloud/openhands-cloud.mdx b/openhands/usage/cloud/openhands-cloud.mdx index 022ecbd4..7276b037 100644 --- a/openhands/usage/cloud/openhands-cloud.mdx +++ b/openhands/usage/cloud/openhands-cloud.mdx @@ -6,7 +6,7 @@ description: Getting started with OpenHands Cloud. ## Accessing OpenHands Cloud OpenHands Cloud is the hosted cloud version of OpenHands. To get started with OpenHands Cloud, -visit [app.all-hands.dev](https://app.all-hands.dev). +visit [app.all-hands.dev](https://app.openhands.dev). You'll be prompted to connect with your GitHub, GitLab or Bitbucket account: diff --git a/openhands/usage/cloud/pro-subscription.mdx b/openhands/usage/cloud/pro-subscription.mdx index 6027f181..91e0b13b 100644 --- a/openhands/usage/cloud/pro-subscription.mdx +++ b/openhands/usage/cloud/pro-subscription.mdx @@ -47,4 +47,4 @@ The following applies to **both** the Pay-as-you-go and Pro subscription: | :---- | :---- | | Do I have access to multiple models via the OpenHands LLM provider? | ✅ Yes

[*See models and pricing*](/openhands/usage/llms/openhands-llms#pricing) | | Can I generate and refresh OpenHands LLM API keys? | ✅ Yes | -| How much am I charged for LLM usage when I use the OpenHands LLM provider in other AI coding tools? | **No markup** \- pay 1x API prices
[*See models and pricing*](/openhands/usage/llms/openhands-llms#pricing)

*Usage is deducted from your OpenHands Cloud credit balance.*

*The OpenHands LLM provider is available to all OpenHands Cloud users, and LLM usage is billed at-cost (zero markup). Use these models with OpenHands CLI, running OpenHands on your own, or even other AI coding agents\! [Learn more.](https://www.all-hands.dev/blog/access-state-of-the-art-llm-models-at-cost-via-openhands-gui-and-cli)* | +| How much am I charged for LLM usage when I use the OpenHands LLM provider in other AI coding tools? | **No markup** \- pay 1x API prices
[*See models and pricing*](/openhands/usage/llms/openhands-llms#pricing)

*Usage is deducted from your OpenHands Cloud credit balance.*

*The OpenHands LLM provider is available to all OpenHands Cloud users, and LLM usage is billed at-cost (zero markup). Use these models with OpenHands CLI, running OpenHands on your own, or even other AI coding agents\! [Learn more.](https://openhands.dev/blog/access-state-of-the-art-llm-models-at-cost-via-openhands-gui-and-cli)* | diff --git a/openhands/usage/cloud/project-management/jira-dc-integration.mdx b/openhands/usage/cloud/project-management/jira-dc-integration.mdx index c7ce4c06..d290805a 100644 --- a/openhands/usage/cloud/project-management/jira-dc-integration.mdx +++ b/openhands/usage/cloud/project-management/jira-dc-integration.mdx @@ -43,7 +43,7 @@ description: Complete guide for setting up Jira Data Center integration with Ope - Go to **Administration** > **System** > **WebHooks** - Click **Create a WebHook** - **Name**: `OpenHands Cloud Integration` - - **URL**: `https://app.all-hands.dev/integration/jira-dc/events` + - **URL**: `https://app.openhands.dev/integration/jira-dc/events` - Set a suitable webhook secret - **Issue related events**: Select the following: - Issue updated @@ -59,7 +59,7 @@ description: Complete guide for setting up Jira Data Center integration with Ope ### Step 1: Log in to OpenHands Cloud 1. **Navigate and Authenticate** - - Go to [OpenHands Cloud](https://app.all-hands.dev/) + - Go to [OpenHands Cloud](https://app.openhands.dev/) - Sign in with your Git provider (GitHub, GitLab, or BitBucket) - **Important:** Make sure you're signing in with the same Git provider account that contains the repositories you want the OpenHands agent to work on. diff --git a/openhands/usage/cloud/project-management/jira-integration.mdx b/openhands/usage/cloud/project-management/jira-integration.mdx index 6cdd567d..498104d2 100644 --- a/openhands/usage/cloud/project-management/jira-integration.mdx +++ b/openhands/usage/cloud/project-management/jira-integration.mdx @@ -47,7 +47,7 @@ description: Complete guide for setting up Jira Cloud integration with OpenHands 2. **Configure Webhook** - **Name**: `OpenHands Cloud Integration` - **Status**: Enabled - - **URL**: `https://app.all-hands.dev/integration/jira/events` + - **URL**: `https://app.openhands.dev/integration/jira/events` - **Issue related events**: Select the following: - Issue updated - Comment created @@ -62,7 +62,7 @@ description: Complete guide for setting up Jira Cloud integration with OpenHands ### Step 1: Log in to OpenHands Cloud 1. **Navigate and Authenticate** - - Go to [OpenHands Cloud](https://app.all-hands.dev/) + - Go to [OpenHands Cloud](https://app.openhands.dev/) - Sign in with your Git provider (GitHub, GitLab, or BitBucket) - **Important:** Make sure you're signing in with the same Git provider account that contains the repositories you want the OpenHands agent to work on. diff --git a/openhands/usage/cloud/project-management/linear-integration.mdx b/openhands/usage/cloud/project-management/linear-integration.mdx index 4887df50..386dde8b 100644 --- a/openhands/usage/cloud/project-management/linear-integration.mdx +++ b/openhands/usage/cloud/project-management/linear-integration.mdx @@ -48,7 +48,7 @@ description: Complete guide for setting up Linear integration with OpenHands Clo 2. **Configure Webhook** - **Label**: `OpenHands Cloud Integration` - - **URL**: `https://app.all-hands.dev/integration/linear/events` + - **URL**: `https://app.openhands.dev/integration/linear/events` - **Resource types**: Select: - `Comment` - For comment events - `Issue` - For issue updates (label changes) @@ -63,7 +63,7 @@ description: Complete guide for setting up Linear integration with OpenHands Clo ### Step 1: Log in to OpenHands Cloud 1. **Navigate and Authenticate** - - Go to [OpenHands Cloud](https://app.all-hands.dev/) + - Go to [OpenHands Cloud](https://app.openhands.dev/) - Sign in with your Git provider (GitHub, GitLab, or BitBucket) - **Important:** Make sure you're signing in with the same Git provider account that contains the repositories you want the OpenHands agent to work on. diff --git a/openhands/usage/cloud/slack-installation.mdx b/openhands/usage/cloud/slack-installation.mdx index 8ed86325..29e8ca93 100644 --- a/openhands/usage/cloud/slack-installation.mdx +++ b/openhands/usage/cloud/slack-installation.mdx @@ -41,7 +41,7 @@ validate critical information independently. **Make sure your Slack workspace admin/owner has installed OpenHands Slack App first.** Every user in the Slack workspace (including admins/owners) must link their OpenHands Cloud account to the OpenHands Slack App. To do this: - 1. Visit the [Settings > Integrations](https://app.all-hands.dev/settings/integrations) page in OpenHands Cloud. + 1. Visit the [Settings > Integrations](https://app.openhands.dev/settings/integrations) page in OpenHands Cloud. 2. Click `Install OpenHands Slack App`. 3. In the top right corner, select the workspace to install the OpenHands Slack app. 4. Review permissions and click allow. diff --git a/openhands/usage/llms/llms.mdx b/openhands/usage/llms/llms.mdx index ea09443f..2471d92b 100644 --- a/openhands/usage/llms/llms.mdx +++ b/openhands/usage/llms/llms.mdx @@ -43,8 +43,8 @@ limits and monitor usage. ### Local / Self-Hosted Models -- [mistralai/devstral-small](https://www.all-hands.dev/blog/devstral-a-new-state-of-the-art-open-model-for-coding-agents) (20 May 2025) -- also available through [OpenRouter](https://openrouter.ai/mistralai/devstral-small:free) -- [all-hands/openhands-lm-32b-v0.1](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) (31 March 2025) -- also available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1) +- [mistralai/devstral-small](https://openhands.dev/blog/devstral-a-new-state-of-the-art-open-model-for-coding-agents) (20 May 2025) -- also available through [OpenRouter](https://openrouter.ai/mistralai/devstral-small:free) +- [all-hands/openhands-lm-32b-v0.1](https://openhands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) (31 March 2025) -- also available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1) ### Known Issues diff --git a/openhands/usage/llms/local-llms.mdx b/openhands/usage/llms/local-llms.mdx index 08fbdbeb..575ac2a1 100644 --- a/openhands/usage/llms/local-llms.mdx +++ b/openhands/usage/llms/local-llms.mdx @@ -7,7 +7,7 @@ description: When using a Local LLM, OpenHands may have limited functionality. I - 2025/05/21: We collaborated with Mistral AI and released [Devstral Small](https://mistral.ai/news/devstral) that achieves [46.8% on SWE-Bench Verified](https://github.com/SWE-bench/experiments/pull/228)! - 2025/03/31: We released an open model OpenHands LM 32B v0.1 that achieves 37.1% on SWE-Bench Verified -([blog](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model), [model](https://huggingface.co/all-hands/openhands-lm-32b-v0.1)). +([blog](https://openhands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model), [model](https://huggingface.co/all-hands/openhands-lm-32b-v0.1)). ## Quickstart: Running OpenHands with a Local LLM using LM Studio diff --git a/openhands/usage/quick-start.mdx b/openhands/usage/quick-start.mdx index 196ae5a3..1209f5ac 100644 --- a/openhands/usage/quick-start.mdx +++ b/openhands/usage/quick-start.mdx @@ -8,7 +8,7 @@ icon: rocket The easiest way to get started with OpenHands is on OpenHands Cloud, which comes with $20 in free credits for new users. - To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.all-hands.dev). + To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.openhands.dev). For more information see [getting started with OpenHands Cloud.](/openhands/usage/cloud/openhands-cloud) diff --git a/openhands/usage/run-openhands/local-setup.mdx b/openhands/usage/run-openhands/local-setup.mdx index b665184b..73c2a4a4 100644 --- a/openhands/usage/run-openhands/local-setup.mdx +++ b/openhands/usage/run-openhands/local-setup.mdx @@ -153,7 +153,7 @@ OpenHands requires an API key to access most language models. Here's how to get -1. [Log in to OpenHands Cloud](https://app.all-hands.dev). +1. [Log in to OpenHands Cloud](https://app.openhands.dev). 2. Go to the Settings page and navigate to the `API Keys` tab. 3. Copy your `LLM API Key`. diff --git a/openhands/usage/troubleshooting/feedback.mdx b/openhands/usage/troubleshooting/feedback.mdx index e47ebdc3..b02c9858 100644 --- a/openhands/usage/troubleshooting/feedback.mdx +++ b/openhands/usage/troubleshooting/feedback.mdx @@ -26,7 +26,7 @@ However, a link with a unique ID will still be created that you can share public ### Who collects and stores the data? -The data is collected and stored by [All Hands AI](https://all-hands.dev), a company founded by OpenHands maintainers to support and improve OpenHands. +The data is collected and stored by [All Hands AI](https://openhands.dev), a company founded by OpenHands maintainers to support and improve OpenHands. ### How will public data be released? diff --git a/sdk/getting-started.mdx b/sdk/getting-started.mdx index d4308365..13425c89 100644 --- a/sdk/getting-started.mdx +++ b/sdk/getting-started.mdx @@ -22,7 +22,7 @@ The SDK requires an LLM API key from any [LiteLLM-supported provider](https://do - Sign up for [OpenHands Cloud](https://app.all-hands.dev) and get an API key from the [API keys page](https://app.all-hands.dev/settings/api-keys). This gives you access to recommended models with no markup. + Sign up for [OpenHands Cloud](https://app.openhands.dev) and get an API key from the [API keys page](https://app.openhands.dev/settings/api-keys). This gives you access to recommended models with no markup. [Learn more →](/openhands/usage/llms/openhands-llms) @@ -164,6 +164,6 @@ ls examples/01_standalone_sdk/ ### Get Help -- **[Slack Community](https://all-hands.dev/joinslack)** - Ask questions and share projects +- **[Slack Community](https://openhands.dev/joinslack)** - Ask questions and share projects - **[GitHub Issues](https://github.com/All-Hands-AI/agent-sdk/issues)** - Report bugs or request features - **[Example Directory](https://github.com/All-Hands-AI/agent-sdk/tree/main/examples)** - Browse working code samples diff --git a/sdk/guides/hello-world.mdx b/sdk/guides/hello-world.mdx index 12f4e7ee..fda64620 100644 --- a/sdk/guides/hello-world.mdx +++ b/sdk/guides/hello-world.mdx @@ -19,7 +19,7 @@ from openhands.sdk.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 +# You can get an API key from https://app.openhands.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") diff --git a/sdk/index.mdx b/sdk/index.mdx index da21dc26..e5d7fffb 100644 --- a/sdk/index.mdx +++ b/sdk/index.mdx @@ -72,7 +72,7 @@ Other SDKs (e.g. [Claude Code](https://github.com/anthropics/claude-agent-sdk-py Understand the SDK's architecture: agents, tools, workspaces, and more. @@ -112,7 +112,7 @@ Other SDKs (e.g. [Claude Code](https://github.com/anthropics/claude-agent-sdk-py Connect with the OpenHands community on Slack. diff --git a/success-stories/index.mdx b/success-stories/index.mdx index afc71093..b0c7d867 100644 --- a/success-stories/index.mdx +++ b/success-stories/index.mdx @@ -82,7 +82,7 @@ https://buriti-emau.github.io/Mapa-UFU/ ## Tavily adapter helps solve persistent debugging issue -Big congratulations to the new [Tavily adapter](https://www.all-hands.dev/blog/building-a-provably-versatile-agent)... OpenHands and I have been beavering away at a Lightstreamer client library for most of this week but were getting a persistent (and unhelpful) "unexpected error" from the server. +Big congratulations to the new [Tavily adapter](https://openhands.dev/blog/building-a-provably-versatile-agent)... OpenHands and I have been beavering away at a Lightstreamer client library for most of this week but were getting a persistent (and unhelpful) "unexpected error" from the server. Coming back to the problem today, after trying several unsuccessful fixes prompted by me, OH decided all by itself to search the web, and found the cause of the problem (of course it was simply CRLF line endings...). I was on the verge of giving up - good thing OH has more stamina than me! @@ -153,7 +153,7 @@ This shows how OpenHands can quickly diagnose and fix AWS infrastructure issues ## OpenHands builds Chrome extension for GitHub integration -I asked OpenHands to write a Chrome extension based on our [OpenHands Cloud API](https://docs.all-hands.dev/modules/usage/cloud/cloud-api). Once installed, you can now easily launch an OpenHands cloud session from your GitHub webpage/PR! +I asked OpenHands to write a Chrome extension based on our [OpenHands Cloud API](https://docs.openhands.dev/modules/usage/cloud/cloud-api). Once installed, you can now easily launch an OpenHands cloud session from your GitHub webpage/PR! This demonstrates OpenHands' ability to create browser extensions and integrate with external APIs, enabling seamless workflows between GitHub and OpenHands Cloud. From fdba0701d78de769ab13c9421675cecaa467221a Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 21:08:34 +0000 Subject: [PATCH 43/46] docs(sdk): fix broken links, normalize usage_id, domain updates; CI opens PRs only - Replace /sdk/architecture/* links with working /sdk/arch/llms/* or remove - Hello World imports fixed and usage_id consistent - Domain move to openhands.dev; keep docker.* and jira.* - sync_code_blocks.py: no-op if SDK missing; remove walrus operator; simpler block replace - sync-sdk-changes workflow: create branch + PR for OpenAPI updates - docs.json: Agent SDK (v1) is primary; add OpenAPI tab for Agent SDK Co-authored-by: openhands --- .github/scripts/sync_code_blocks.py | 4 ++-- .github/workflows/sync-sdk-changes.yml | 26 +++++++++++++++++++------- sdk/getting-started.mdx | 6 +++--- sdk/guides/custom-tools.mdx | 6 +++--- sdk/guides/hello-world.mdx | 2 +- sdk/guides/mcp.mdx | 2 +- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/.github/scripts/sync_code_blocks.py b/.github/scripts/sync_code_blocks.py index f9e8edde..7286c04d 100755 --- a/.github/scripts/sync_code_blocks.py +++ b/.github/scripts/sync_code_blocks.py @@ -198,9 +198,9 @@ def main() -> None: print(f"\n📋 Processing {mdx_file.relative_to(docs_root)}") print(f" Found {len(code_blocks)} code block(s) with file references") - if update_doc_file(mdxx_file := mdx_file, content=content, code_blocks=code_blocks, agent_sdk_path=agent_sdk_path): + if update_doc_file(mdx_file, content=content, code_blocks=code_blocks, agent_sdk_path=agent_sdk_path): total_changes += 1 - files_changed.append(str(mdxx_file.relative_to(docs_root))) + files_changed.append(str(mdx_file.relative_to(docs_root))) except Exception as e: print(f"❌ Error processing {mdx_file}: {e}") continue diff --git a/.github/workflows/sync-sdk-changes.yml b/.github/workflows/sync-sdk-changes.yml index c5d762d2..0ccd3d86 100644 --- a/.github/workflows/sync-sdk-changes.yml +++ b/.github/workflows/sync-sdk-changes.yml @@ -8,6 +8,7 @@ on: permissions: contents: write + pull-requests: write jobs: update: @@ -53,15 +54,26 @@ jobs: SCHEMA_PATH="$GITHUB_WORKSPACE/openapi/agent-sdk.json" \ uv run python openhands/agent_server/openapi.py - - name: Commit and push OpenAPI spec + - name: Create branch for OpenAPI update run: | git add openapi/agent-sdk.json - if git diff --cached --quiet; then echo "No OpenAPI changes to commit." - else - SHA="${{ github.event.client_payload.sha || 'manual' }}" - BRANCH="${SOURCE_BRANCH:-main}" - git commit -m "sync(openapi): agent-sdk/$BRANCH ${SHA:0:7}" - git push origin + exit 0 fi + BRANCH_NAME="sync/agent-sdk-openapi-${SOURCE_BRANCH:-main}" + echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_ENV" + git checkout -b "$BRANCH_NAME" + SHA="${{ github.event.client_payload.sha || 'manual' }}" + BRANCH="${SOURCE_BRANCH:-main}" + git commit -m "sync(openapi): agent-sdk/$BRANCH ${SHA:0:7}" + git push origin "$BRANCH_NAME" + + - name: Open PR for OpenAPI update + if: env.BRANCH_NAME != '' + uses: peter-evans/create-pull-request@v6 + with: + branch: ${{ env.BRANCH_NAME }} + title: "sync(openapi): update Agent SDK OpenAPI spec" + body: "Automated update to openapi/agent-sdk.json" + labels: documentation diff --git a/sdk/getting-started.mdx b/sdk/getting-started.mdx index 13425c89..ed6f5b6f 100644 --- a/sdk/getting-started.mdx +++ b/sdk/getting-started.mdx @@ -152,9 +152,9 @@ ls examples/01_standalone_sdk/ ### Explore Documentation -- **[SDK Architecture](/sdk/architecture/overview)** - Deep dive into components -- **[Tool Documentation](/sdk/architecture/tools/overview)** - Available tools -- **[Workspace Options](/sdk/architecture/workspace/overview)** - Execution environments +- **[LLM Architecture](/sdk/arch/llms/index)** - How the SDK models are structured +- **[LLM Providers](/sdk/arch/llms/providers)** - Provider-specific notes +- **[LLM Configuration](/sdk/arch/llms/configuration)** - Parameters and environment variables ### Build Custom Solutions diff --git a/sdk/guides/custom-tools.mdx b/sdk/guides/custom-tools.mdx index 0860d985..0c99b68b 100644 --- a/sdk/guides/custom-tools.mdx +++ b/sdk/guides/custom-tools.mdx @@ -17,7 +17,7 @@ tools = get_default_tools() agent = Agent(llm=llm, tools=tools) ``` -See [Tools Overview](/sdk/architecture/tools/overview) for the complete list of available tools. +See [LLM Architecture](/sdk/arch/llms/index) for details on the SDK's LLM system. ## Understanding the Tool System @@ -27,7 +27,7 @@ The SDK's tool system is built around three core components: 2. **Observation** - Defines output data (what the tool returns) 3. **Executor** - Implements the tool's logic (what the tool does) -These components are tied together by a **ToolDefinition** that registers the tool with the agent. For architectural details and advanced usage patterns, see [Tool System Architecture](/sdk/architecture/sdk/tool). +These components are tied together by a **ToolDefinition** that registers the tool with the agent. For architectural details and advanced usage patterns, see [LLM Architecture](/sdk/arch/llms/index). ## Creating a Custom Tool @@ -307,5 +307,5 @@ Create custom tools when you need to: ## Next Steps -- **[Tool System Architecture](/sdk/architecture/sdk/tool)** - Deep dive into the tool system +- **[LLM Architecture](/sdk/arch/llms/index)** - How the SDK models are structured - **[Model Context Protocol (MCP) Integration](/sdk/guides/mcp)** - Use Model Context Protocol servers diff --git a/sdk/guides/hello-world.mdx b/sdk/guides/hello-world.mdx index fda64620..c8d6cfec 100644 --- a/sdk/guides/hello-world.mdx +++ b/sdk/guides/hello-world.mdx @@ -66,7 +66,7 @@ Use the preset agent with common built-in tools: agent = get_default_agent(llm=llm, cli_mode=True) ``` -The default agent includes BashTool, FileEditorTool, etc. See [Tools Overview](/sdk/architecture/tools/overview) for the complete list of available tools. +The default agent includes BashTool, FileEditorTool, etc. ### Conversation Start a conversation to manage the agent's lifecycle: diff --git a/sdk/guides/mcp.mdx b/sdk/guides/mcp.mdx index 7f813855..9de46e62 100644 --- a/sdk/guides/mcp.mdx +++ b/sdk/guides/mcp.mdx @@ -242,6 +242,6 @@ mcp_config = { ## Next Steps -- **[MCP Architecture](/sdk/architecture/sdk/mcp)** - Technical details and internals +- **[LLM Architecture](/sdk/arch/llms/index)** - How the SDK models are structured - **[Custom Tools](/sdk/guides/custom-tools)** - Creating native SDK tools - **[Security Analyzer](/sdk/guides/security-analyzer)** - Securing tool usage From abb27a4c795c2d934c92ff915ff341c56297348c Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 21:14:57 +0000 Subject: [PATCH 44/46] ci: route OpenAPI sync via PR with proper permissions and OpenHands bot identity - Add pull-requests: write - Use create-pull-request for agent-sdk OpenAPI updates - Update bot name/email to openhands.dev Co-authored-by: openhands --- .github/workflows/sync-sdk-changes.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-sdk-changes.yml b/.github/workflows/sync-sdk-changes.yml index 0ccd3d86..b713a50f 100644 --- a/.github/workflows/sync-sdk-changes.yml +++ b/.github/workflows/sync-sdk-changes.yml @@ -24,8 +24,8 @@ jobs: - name: Configure Git run: | - git config user.name "all-hands-bot" - git config user.email "contact@all-hands.dev" + git config user.name "openhands-bot" + git config user.email "contact@openhands.dev" - name: Install uv uses: astral-sh/setup-uv@v7 From 7dc1430aed1ace1df9d7c201eb58b410df619013 Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 21:16:57 +0000 Subject: [PATCH 45/46] docs: domain sweep to openhands.dev where appropriate - Update contact emails to @openhands.dev - Update visible app URL text to app.openhands.dev - Preserve docker.*, runtime.*, and jira.* hosts as-is Co-authored-by: openhands --- openhands/usage/cloud/cloud-api.mdx | 2 +- openhands/usage/cloud/openhands-cloud.mdx | 2 +- openhands/usage/quick-start.mdx | 2 +- openhands/usage/settings/application-settings.mdx | 2 +- openhands/usage/troubleshooting/feedback.mdx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openhands/usage/cloud/cloud-api.mdx b/openhands/usage/cloud/cloud-api.mdx index e34ec15d..7d7083e5 100644 --- a/openhands/usage/cloud/cloud-api.mdx +++ b/openhands/usage/cloud/cloud-api.mdx @@ -119,4 +119,4 @@ You may receive an `AuthenticationError` if: ## Rate Limits If you have too many conversations running at once, older conversations will be paused to limit the number of concurrent conversations. -If you're running into issues and need a higher limit for your use case, please contact us at [contact@all-hands.dev](mailto:contact@all-hands.dev). +If you're running into issues and need a higher limit for your use case, please contact us at [contact@openhands.dev](mailto:contact@openhands.dev). diff --git a/openhands/usage/cloud/openhands-cloud.mdx b/openhands/usage/cloud/openhands-cloud.mdx index 7276b037..0cffff6a 100644 --- a/openhands/usage/cloud/openhands-cloud.mdx +++ b/openhands/usage/cloud/openhands-cloud.mdx @@ -6,7 +6,7 @@ description: Getting started with OpenHands Cloud. ## Accessing OpenHands Cloud OpenHands Cloud is the hosted cloud version of OpenHands. To get started with OpenHands Cloud, -visit [app.all-hands.dev](https://app.openhands.dev). +visit [app.openhands.dev](https://app.openhands.dev). You'll be prompted to connect with your GitHub, GitLab or Bitbucket account: diff --git a/openhands/usage/quick-start.mdx b/openhands/usage/quick-start.mdx index 1209f5ac..9651de97 100644 --- a/openhands/usage/quick-start.mdx +++ b/openhands/usage/quick-start.mdx @@ -8,7 +8,7 @@ icon: rocket The easiest way to get started with OpenHands is on OpenHands Cloud, which comes with $20 in free credits for new users. - To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.openhands.dev). + To get started with OpenHands Cloud, visit [app.openhands.dev](https://app.openhands.dev). For more information see [getting started with OpenHands Cloud.](/openhands/usage/cloud/openhands-cloud) diff --git a/openhands/usage/settings/application-settings.mdx b/openhands/usage/settings/application-settings.mdx index b0349927..299da754 100644 --- a/openhands/usage/settings/application-settings.mdx +++ b/openhands/usage/settings/application-settings.mdx @@ -16,7 +16,7 @@ pull requests on your behalf. By default, OpenHands uses the following Git author information for all commits and pull requests: - **Username**: `openhands` -- **Email**: `openhands@all-hands.dev` +- **Email**: `openhands@openhands.dev` To override the defaults: diff --git a/openhands/usage/troubleshooting/feedback.mdx b/openhands/usage/troubleshooting/feedback.mdx index b02c9858..c4345026 100644 --- a/openhands/usage/troubleshooting/feedback.mdx +++ b/openhands/usage/troubleshooting/feedback.mdx @@ -46,5 +46,5 @@ For data on the All Hands AI servers, we are happy to delete it at request: data using the link and password that is displayed on the interface when you submit data. **All Data:** If you would like all pieces of your data deleted, or you do not have the ID and password that you -received when submitting the data, please contact `contact@all-hands.dev` from the email address that you registered +received when submitting the data, please contact `contact@openhands.dev` from the email address that you registered when you originally submitted the data. From 929e468c983150e627b2e5916783f10b6117a0dd Mon Sep 17 00:00:00 2001 From: enyst Date: Mon, 20 Oct 2025 21:18:38 +0000 Subject: [PATCH 46/46] docs(sdk): fix MCP link target in getting-started Co-authored-by: openhands --- sdk/getting-started.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/getting-started.mdx b/sdk/getting-started.mdx index ed6f5b6f..b84d4835 100644 --- a/sdk/getting-started.mdx +++ b/sdk/getting-started.mdx @@ -159,7 +159,7 @@ ls examples/01_standalone_sdk/ ### Build Custom Solutions - **[Custom Tools](/sdk/guides/custom-tools)** - Create custom tools to expand agent capabilities -- **[MCP Integration](/sdk/guides/mcp-integration)** - Connect to external tools via Model Context Protocol +- **[MCP Integration](/sdk/guides/mcp)** - Connect to external tools via Model Context Protocol - **[Docker Workspaces](/sdk/guides/remote-agent-server/docker-sandboxed-server)** - Sandbox agent execution in containers ### Get Help