# OpenHands

OpenHands runs an agent with local file and shell tools.
Choose the Python SDK for your application or the CLI for a terminal session.
Both paths send OpenAI-format requests to the exchange.

## Before you start

- A workspace with a balance and a key with the `spend` scope. See [API keys](https://inference.ai.exchange/docs/keys.md).
- A model, router model, or preset from `GET /v1/models`. See [Market data](https://inference.ai.exchange/docs/market.md). The selected offer must support text, tool calls, and an output limit of at least 8,192 tokens.
- `tmux`, for the terminal tool. On macOS, use `brew install tmux`. On Debian or Ubuntu, use `sudo apt-get install tmux`.

## Use the Liquid CLI

The [Liquid CLI](https://inference.ai.exchange/docs/cli.md) starts the OpenHands CLI with the exchange as its provider. It sets the base URL, the key and the model for that process only. For the SDK, it prints the setup with your model filled in.

```bash
curl -fsSL https://inference.ai.exchange/cli/install.sh | bash
liquid login
liquid openhands
```

For the SDK, run `liquid connect openhands-sdk` and not the last command. To set up OpenHands yourself, follow the sections below.

## SDK

Use the SDK to create an agent in your Python application.
See the [official SDK guide](https://docs.openhands.dev/sdk/getting-started).

### SDK setup

1. Install the packages in a Python environment with Python 3.12 or later.

```bash
# Python 3.12 or later
pip install openhands-sdk openhands-tools
```

2. Export your buyer key in the shell that starts the application.

```bash
export LIQUID_API_KEY=sk-liquid-...
```

| Setting          | Purpose                                          |
| ---------------- | ------------------------------------------------ |
| `LIQUID_API_KEY` | The buyer key that the SDK reads from the shell. |

3. Save this code as `agent.py` in your project directory.

```python
import os
from pathlib import Path

from openhands.sdk import Agent, Conversation, LLM, Tool
from openhands.tools.file_editor import FileEditorTool
from openhands.tools.terminal import TerminalTool

def create_agent() -> Agent:
    return Agent(
        llm=LLM(
            model="openai/liquid.auto",
            base_url="https://router.inference.ai.exchange/v1",
            api_key=os.environ["LIQUID_API_KEY"],
            extra_headers={"X-Title": "OpenHands SDK", "x-liquid-cap-usd": "1.00"},
            max_output_tokens=16384,
            native_tool_calling=True,
            reasoning_effort=None,
        ),
        tools=[Tool(name=TerminalTool.name), Tool(name=FileEditorTool.name)],
    )

def main() -> None:
    conversation = Conversation(agent=create_agent(), workspace=str(Path.cwd()))
    try:
        conversation.send_message("List the files here.")
        conversation.run()
    finally:
        conversation.close()

if __name__ == "__main__":
    main()
```

4. Run `python agent.py` from the same shell.

Notes on the code:

- The model name has the `openai/` prefix. LiteLLM uses this prefix to select the OpenAI API format.
- The base URL ends in `/v1`. The SDK sends requests to `/chat/completions`.
- The terminal and file editor tools run in your current directory. Use a project directory where the agent can change files and run shell commands.

### SDK purchasing rules

The `x-liquid-cap-usd` header caps each model request at $1.00.
The output limit is 8,192 tokens for each request.
A conversation can send several requests. The cap does not limit the total cost of the conversation.
To limit total spend, use a separate key with a spend limit.

For other purchasing rules, add `litellm_extra_body` to the `LLM` constructor:

```python
litellm_extra_body={"exchange": {"max_ttft_ms": 5000}},
```

Keep the cap in the header. Do not add a second cap in the request body.
See [Purchasing rules](https://inference.ai.exchange/docs/rules.md) for the fields and their units.

### SDK verification

Check the receipts on the **Credits** page. Requests show **OpenHands SDK** as the client.
Use the receipt for the settled charge. The SDK's token usage does not state the exchange's charge.

## CLI

Use the CLI for a terminal session.
The CLI requires Python 3.12 and installs its own SDK version. Use a separate Python environment from the SDK example above.
See the [official CLI guide](https://docs.openhands.dev/openhands/usage/cli).

### CLI setup

1. Create a separate Python environment and install the CLI.

```bash
# Python 3.12; use a separate environment for the CLI.
python3.12 -m venv .venv-openhands
source .venv-openhands/bin/activate
pip install openhands
```

2. Export your buyer key and the model settings in the same shell.

```bash
export LIQUID_API_KEY=sk-liquid-...
export LLM_API_KEY=$LIQUID_API_KEY
export LLM_BASE_URL=https://router.inference.ai.exchange/v1
export LLM_MODEL=openai/liquid.auto
```

| Setting          | Purpose                                       |
| ---------------- | --------------------------------------------- |
| `LIQUID_API_KEY` | Your buyer key.                               |
| `LLM_API_KEY`    | Supplies the buyer key to the CLI.            |
| `LLM_MODEL`      | The selected model with the `openai/` prefix. |
| `LLM_BASE_URL`   | The exchange base URL with `/v1`.             |

3. Apply the exchange settings to the default agent file.

```bash
python3 - <<'PY'
import json
from pathlib import Path

path = Path.home() / ".openhands" / "agent_settings.json"
settings = json.loads(path.read_text()) if path.exists() else {}
llm = settings.setdefault("llm", {})
for config in [llm, (settings.get("condenser") or {}).get("llm")]:
    if config is None:
        continue
    config.update({
        "model": "openai/liquid.auto",
        "base_url": "https://router.inference.ai.exchange/v1",
        "api_key": "env-overrides-key",
        "max_output_tokens": 16384,
        "native_tool_calling": True,
        "reasoning_effort": None,
    })
    headers = config.get("extra_headers") or {}
    headers.update({
        "X-Title": "OpenHands CLI",
        "x-liquid-cap-usd": "1.00",
    })
    config["extra_headers"] = headers
    body = config.get("litellm_extra_body") or {}
    exchange = body.get("exchange") or {}
    exchange.pop("cap_usd", None)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(settings, indent=2) + "\n")
PY
```

The command edits `~/.openhands/agent_settings.json` and preserves unrelated settings. It:

- Sets a dummy API key in the file. The environment supplies the buyer key at runtime.
- Sets the request headers, native tool calls, and an output limit of 8,192 tokens.
- Removes a body cap so the header supplies the price cap. Other purchasing rules stay in the file.

4. Send a test request from a project directory.

```bash
openhands --headless --json --override-with-envs --task "Reply with the word ready"
```

5. Start an interactive session from the same shell.

```bash
openhands --override-with-envs
```

Use `--override-with-envs` on every command. Without this flag, the CLI uses the dummy key from the file.

- The flag replaces the file's model, API key, and base URL with the environment values.
- The CLI keeps the file's headers and output limit.

The CLI's local tools can change files and run shell commands in your project directory.

### CLI purchasing rules

The `x-liquid-cap-usd` header caps each model request at $1.00.
A session can send several requests. Use a separate key with a spend limit to limit total spend.

For other purchasing rules, add `litellm_extra_body` inside the file's `llm` object:

```json
"litellm_extra_body": {"exchange": {"max_ttft_ms": 5000}}
```

Keep the cap in the header. Do not add a second cap in the request body.
See [Purchasing rules](https://inference.ai.exchange/docs/rules.md) for the fields and their units.

### CLI verification

Check the receipts on the **Credits** page. Requests show **OpenHands CLI** as the client.

### CLI troubleshooting

| Symptom                              | Action                                                              |
| ------------------------------------ | ------------------------------------------------------------------- |
| `401`                                | Confirm the buyer key and add `--override-with-envs`.               |
| `model_not_found`                    | Choose a name from `GET /v1/models` and keep the `openai/` prefix.  |
| The request exceeds the cap          | Reduce the output limit or increase the cap in `llm.extra_headers`. |
| The workspace has insufficient funds | Add funds on the **Credits** page.                                  |
