From 56e0aaf173fa025d136f87694ff64de581ff4efb Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 25 Aug 2026 14:47:54 +0000 Subject: [PATCH 1/3] docs(enterprise): add ACP agents with LiteLLM configuration guide Document how to route Claude Code, Codex, and Gemini CLI ACP agents through a customer-managed LiteLLM/Bedrock proxy on OpenHands Enterprise. Covers where the ACP subprocess runs (inside the sandbox), the per-provider API key + base URL settings, which precedence credentials to leave blank, and the model-identifier handshake between each CLI and the proxy. Co-authored-by: openhands --- docs.json | 3 +- enterprise/integrations/acp-litellm.mdx | 234 ++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 enterprise/integrations/acp-litellm.mdx diff --git a/docs.json b/docs.json index f1b5b6fa..3bb8620a 100644 --- a/docs.json +++ b/docs.json @@ -546,7 +546,8 @@ "enterprise/integrations/bitbucket-data-center", "enterprise/integrations/jira-data-center", "enterprise/integrations/slack", - "enterprise/integrations/external-llm-gateways" + "enterprise/integrations/external-llm-gateways", + "enterprise/integrations/acp-litellm" ] }, { diff --git a/enterprise/integrations/acp-litellm.mdx b/enterprise/integrations/acp-litellm.mdx new file mode 100644 index 00000000..b9e58cb9 --- /dev/null +++ b/enterprise/integrations/acp-litellm.mdx @@ -0,0 +1,234 @@ +--- +title: ACP Agents with LiteLLM +description: Route Claude Code, Codex, and Gemini CLI ACP agents through your existing LiteLLM or Bedrock proxy so they use your managed models instead of external provider API keys. +icon: robot +--- + +OpenHands Enterprise (OHE) can drive a conversation with the built-in +**OpenHands** agent or with a third-party **ACP agent** — Claude Code, Codex, or +Gemini CLI — on the same sandbox. This guide is for operators who already run a +LiteLLM proxy (including one that fronts AWS Bedrock) and want the ACP agents to +call models through that proxy instead of reaching out to Anthropic, OpenAI, or +Google directly with external API keys. + + + This guide covers routing the **ACP agent CLIs** through a LiteLLM proxy. It is + distinct from chaining OHE's built-in LiteLLM to an external gateway for the + OpenHands agent — for that, see + [External LLM Gateways](/enterprise/integrations/external-llm-gateways). For a + conceptual overview of ACP agents themselves, see + [ACP Agents](/openhands/usage/agent-canvas/acp-agents). + + +## Architecture: where the ACP agent runs + +The ACP agent CLI runs **inside the sandbox runtime**, not on the OpenHands +server pod. When you select an ACP agent, the Agent Server that manages the +sandbox spawns the provider's CLI (for example `claude-agent-acp`) as a +subprocess and relays each conversation turn to it over JSON-RPC on stdio. The +CLI has native filesystem access to the workspace, and it makes its own LLM +calls directly. + +```text +OpenHands Server pod + │ (conversation turns) + ▼ +Agent Server ──spawns──► ACP agent CLI ──LLM calls──► Your LiteLLM proxy +(inside sandbox pod) (inside sandbox pod) (LiteLLM / Bedrock) +``` + +This has two consequences that matter for the LiteLLM setup: + +- **The sandbox already has what the CLI needs.** Node and `npx` are present, the + workspace is mounted, and the CLI runs next to it — no round-trip to the server + pod for filesystem work. +- **The LLM call originates from the sandbox.** Each ACP CLI resolves its own + model alias locally and sends its vendor's native model ID over its vendor's + native wire format straight to the endpoint you configure. Nothing in OHE sits + in the middle rewriting the request. That is why the base-URL override below is + all that is needed: point the CLI at your proxy and the CLI does the rest. + + + Because each CLI talks its own vendor dialect directly to the proxy, your + LiteLLM proxy must expose models under the identifiers that each CLI sends + (Anthropic-style names for Claude Code, OpenAI-style for Codex, Gemini names + for Gemini CLI), and accept each vendor's native API shape. LiteLLM handles + all three natively. This path is exercised by the OpenHands ACP evals, which + run Codex, Claude Code, and Gemini CLI through a LiteLLM proxy. + + +## Prerequisites + +- **OHE is installed and reachable**, and you can sign in at + `https://app.`. +- **A LiteLLM proxy reachable from the sandbox network**, with the models you + want to use registered under the vendor-native names each CLI expects. If the + proxy fronts Bedrock, that mapping is configured on the proxy side; OHE does + not need Bedrock credentials. +- **A LiteLLM virtual key** the CLIs can authenticate with. + + + The proxy URL is your deployment's LiteLLM URL. On OpenHands SaaS this is + `https://llm-proxy.app.all-hands.dev`; on a self-hosted OHE cluster it is your + own LiteLLM endpoint (for example the in-cluster `http://openhands-litellm:4000` + or whatever hostname your proxy is exposed on). + + +## How ACP credentials reach the CLI + +Every field you set for an ACP agent is stored as a **global secret** whose name +is exactly the environment variable the Agent Server exports into the ACP +subprocess. Keeping the secret name identical to the env var is what makes the +value actually reach the CLI. You can set these during onboarding or at any time +under **Settings → Secrets**; the **Settings → Agent** page surfaces the same +fields per provider. + +The two fields that matter for LiteLLM routing are the provider's **API key** +env var (set to your LiteLLM virtual key) and its **base URL** env var (set to +your proxy URL): + +| Provider | API key env var | Base URL env var | +|---|---|---| +| **Claude Code** | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` | +| **Codex** | `OPENAI_API_KEY` | `OPENAI_BASE_URL` | +| **Gemini CLI** | `GEMINI_API_KEY` | `GEMINI_BASE_URL` | + + + Each provider also exposes a subscription/OAuth or file-based credential + (Claude Code's `CLAUDE_CODE_OAUTH_TOKEN`, Codex's `CODEX_AUTH_JSON`, Gemini's + Vertex/OAuth fields). **Those take precedence over the API key + base URL and + bypass your proxy.** Leave them blank when routing through LiteLLM. Each + section below calls out exactly which fields to leave empty. + + +## Configure each provider + +All steps happen on **Settings → Agent**. Choose **Agent = ACP**, pick the +**Preset**, then set the credentials as global secrets. + + + + 1. **Agent = ACP**, **Preset = Claude Code**, and pick a **Model**. + 2. Set `ANTHROPIC_API_KEY` to your LiteLLM virtual key. + 3. Set `ANTHROPIC_BASE_URL` to your proxy URL, then **Save**. + + This works because `claude-agent-acp` reads `ANTHROPIC_BASE_URL` directly and + sends Anthropic-native requests to it. + + + Leave `CLAUDE_CODE_OAUTH_TOKEN` **blank**. When it is set, the SDK strips + both `ANTHROPIC_API_KEY` and `ANTHROPIC_BASE_URL` (the OAuth bearer + authenticates against Anthropic directly, and a base URL would misroute + it), so the CLI bypasses your proxy entirely. + + + + + 1. **Agent = ACP**, **Preset = Codex**, and pick a **Model** (for example + `gpt-5.5`). + 2. Set `OPENAI_API_KEY` to your LiteLLM virtual key. + 3. Set `OPENAI_BASE_URL` to your proxy URL, then **Save**. + + Codex does not read `OPENAI_BASE_URL` from the environment itself — its + base-URL setting lives in its own config. OHE handles this for you: it + rewrites `OPENAI_BASE_URL` into the config key the Codex ACP adapter reads, + so the value you set takes effect. + + + Leave `CODEX_AUTH_JSON` **blank**. The subscription auth file takes + precedence over the key + base URL and bypasses your proxy. + + + + + 1. **Agent = ACP**, **Preset = Gemini CLI**, and pick a **Model** (for + example `gemini-2.5-pro`, or `auto`). + 2. Set `GEMINI_API_KEY` to your LiteLLM virtual key. This is **required** — it + selects the routable API-key auth path. + 3. Set `GEMINI_BASE_URL` to your proxy URL, then **Save**. + + The SDK passes the base URL to the CLI as its authenticate **gateway** target, + which is how Gemini CLI is routed through LiteLLM. + + + Leave the Vertex/OAuth fields **blank**: + `GOOGLE_APPLICATION_CREDENTIALS_JSON`, `GOOGLE_CLOUD_PROJECT`, + `GOOGLE_CLOUD_LOCATION`, and `GOOGLE_GENAI_USE_VERTEXAI`. They take + precedence and bypass the proxy. + + + + +## The model-identifier handshake + +A common concern when routing agent CLIs through a proxy is whether the model +name and provider "dialect" survive the trip. With ACP agents there is no +translation layer to get wrong: each CLI resolves its configured model alias +locally and issues the LLM call itself, in its own vendor's API format, directly +to the base URL you set. OHE does not rewrite the model name or the request +shape. + +The practical requirement is on the **proxy** side, not OHE: + +- The model you select for each ACP agent must be registered on your LiteLLM + proxy under the identifier that CLI sends. +- The proxy must accept that CLI's native request shape (Anthropic Messages for + Claude Code, OpenAI Chat Completions for Codex, the Gemini API for Gemini CLI). + LiteLLM supports all three, including when the upstream is Bedrock. + +If a call fails with a model-not-found or 4xx error, it is almost always a proxy +mapping issue — the model alias the CLI sent is not registered on the proxy under +that exact name — rather than something OHE is doing to the request. + +## Verify + +1. Start a new conversation with the ACP agent configured above and send a simple + prompt (for example, "list the files in the workspace"). +2. Confirm the turn completes and, if your proxy has spend/usage logging, that + the call appears on **your** LiteLLM proxy — not on the provider's own + dashboard. +3. Switch the selected **Model** and repeat to confirm your other proxy-hosted + models are reachable. + +## Troubleshooting + + + + A subscription/OAuth or file credential is set and taking precedence. Clear + the provider's precedence field: `CLAUDE_CODE_OAUTH_TOKEN` (Claude Code), + `CODEX_AUTH_JSON` (Codex), or the `GOOGLE_APPLICATION_CREDENTIALS_JSON` / + `GOOGLE_CLOUD_*` / `GOOGLE_GENAI_USE_VERTEXAI` fields (Gemini CLI). Then + re-save the API key + base URL and start a new conversation. + + + + Confirm `CLAUDE_CODE_OAUTH_TOKEN` is blank. When it is present the SDK strips + `ANTHROPIC_API_KEY` and `ANTHROPIC_BASE_URL`, so the base URL never reaches + the CLI. + + + + This means the base URL did not take effect and Codex fell back to the real + OpenAI API. Verify `OPENAI_BASE_URL` is set as a global secret (exact name) + and that `CODEX_AUTH_JSON` is blank, then start a new conversation. + + + + The alias the CLI sent is not registered on the LiteLLM proxy under that + name. Register the model on the proxy using the vendor-native identifier the + CLI uses, or select a model in OHE that is already registered. + + + + Agent and credential changes apply to conversations started **after** the + change. A conversation keeps the agent and settings it started with — start a + new conversation to pick up the new configuration. + + + +## Reference + +- [ACP Agents](/openhands/usage/agent-canvas/acp-agents) — concepts, providers, and authentication +- [External LLM Gateways](/enterprise/integrations/external-llm-gateways) — chain OHE's built-in LiteLLM to an external gateway (OpenHands agent) +- [ACP Agent (SDK)](/sdk/guides/agent-acp) — the SDK-level `ACPAgent` and its base-URL/credential handling +- [Agent Client Protocol](https://agentclientprotocol.com/protocol/overview) From 51ff7b3b52b46600cf69a66dd0d7ab074055863a Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 25 Aug 2026 15:05:49 +0000 Subject: [PATCH 2/3] docs(enterprise): rework ACP guide to be user-facing and UI-grounded Refocus the guide around the Settings -> Agent page and real user scenarios instead of architecture internals: - Rename to enterprise/integrations/acp-agents.mdx (broader than LiteLLM) - Add OpenHands-agent vs ACP-agent comparison and a plain-language explanation of what the ACP agent is and that it runs in the sandbox - Describe the actual Settings -> Agent controls (Preset, Command, Model, Credentials) using the real UI labels - Present three configuration scenarios as tabs: provider subscription, provider API key, and your own LiteLLM/Bedrock proxy - Add a 'Which credential wins' section and an FAQ covering the common questions (no external keys with LiteLLM, per-agent model routing, model-identifier handshake, model-not-found, ignored proxy) Co-authored-by: openhands --- docs.json | 2 +- enterprise/integrations/acp-agents.mdx | 212 +++++++++++++++++++++ enterprise/integrations/acp-litellm.mdx | 234 ------------------------ 3 files changed, 213 insertions(+), 235 deletions(-) create mode 100644 enterprise/integrations/acp-agents.mdx delete mode 100644 enterprise/integrations/acp-litellm.mdx diff --git a/docs.json b/docs.json index 3bb8620a..554df52b 100644 --- a/docs.json +++ b/docs.json @@ -547,7 +547,7 @@ "enterprise/integrations/jira-data-center", "enterprise/integrations/slack", "enterprise/integrations/external-llm-gateways", - "enterprise/integrations/acp-litellm" + "enterprise/integrations/acp-agents" ] }, { diff --git a/enterprise/integrations/acp-agents.mdx b/enterprise/integrations/acp-agents.mdx new file mode 100644 index 00000000..ce5b39f5 --- /dev/null +++ b/enterprise/integrations/acp-agents.mdx @@ -0,0 +1,212 @@ +--- +title: Configuring ACP Agents +description: Set up Claude Code, Codex, and Gemini CLI on OpenHands Enterprise — with a provider subscription, a provider API key, or your own LiteLLM/Bedrock proxy. +icon: robot +--- + +OpenHands Enterprise (OHE) can run your conversations with the built-in +**OpenHands** agent or with a third-party **ACP agent** — Claude Code, Codex, or +Gemini CLI. You choose and configure this on the **Settings → Agent** page. This +guide explains what an ACP agent is, walks through the three ways to give it +model access, and answers the questions we hear most often. + +## OpenHands agent vs. ACP agent + +| | OpenHands agent | ACP agent | +|---|---|---| +| Who runs the loop | OpenHands | The provider's own CLI (Claude Code, Codex, Gemini CLI) | +| Model configuration | OHE's central LLM settings | Configured per agent on **Settings → Agent** | +| Tools & prompts | OpenHands tools and skills | The provider CLI's own tools and behavior | + +An **ACP agent** uses the [Agent Client Protocol](https://agentclientprotocol.com/protocol/overview) +to run a coding-agent CLI as the backend for your conversation. Instead of OHE +calling an LLM directly, it launches the provider's CLI, which manages its own +model calls, tools, and execution. You send messages and see the results in the +same OHE conversation view. + + + **Where does it run?** The ACP agent CLI runs **inside your sandbox**, next to + your workspace — the same place your code and terminal live. It is not a + separate service you have to host. Because it runs in the sandbox, it has + direct access to the workspace files. + + + + **Model routing is per agent.** OHE's central model configuration applies to + the OpenHands agent. Each ACP agent manages its own model routing through the + settings on this page — so you point each one at the model access you want + (subscription, API key, or your proxy). + + +## Before you start + +- The **Settings → Agent** page must show an **Agent** dropdown with an **ACP** + option. If you only see OpenHands settings, ACP is not enabled for your + deployment — ask your administrator to enable it. +- Changes on this page apply to **new conversations**. A conversation keeps the + agent and settings it started with. + +## The Settings → Agent page + +When you set **Agent** to **ACP**, the page shows: + +- **Preset** — Claude Code, Codex, Gemini CLI, or **Custom**. +- **Command** — the command that launches the agent. Picking a preset fills this + in for you; leave it as-is unless you are running a custom server. +- **Model** — pick a suggested model for the provider, or choose **Custom** to + enter your own model ID. +- **Credentials** — API key, optional base URL, and any provider-specific + sign-in fields. Everything here is optional and is saved as a **global secret** + (the same secrets you can manage under **Settings → Secrets**). + +Fill in what you need, then click **Save** once — it saves the agent choice and +the credentials together. + +## Choose how the agent gets model access + +There are three ways to give an ACP agent access to a model. Pick the one that +matches your situation. + + + + Use this when your team has a provider subscription — Claude Pro/Max, a + ChatGPT plan for Codex, or a Google login for Gemini. No LLM API key needed. + + On **Settings → Agent**, choose **Agent = ACP**, pick the **Preset**, pick a + **Model**, and fill in that provider's sign-in credential: + + | Provider | Sign-in field | How to get it | + |---|---|---| + | **Claude Code** | `CLAUDE_CODE_OAUTH_TOKEN` | Run `claude setup-token` in a terminal | + | **Codex** | `CODEX_AUTH_JSON` | Paste the contents of `~/.codex/auth.json` from a ChatGPT‑logged‑in machine | + | **Gemini CLI** | Vertex AI fields (`GOOGLE_APPLICATION_CREDENTIALS_JSON`, `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION`, `GOOGLE_GENAI_USE_VERTEXAI`) | From your Google Cloud project | + + Leave the API key and base URL blank in this mode. **Save**, then start a new + conversation. + + + + Use this to point the agent straight at Anthropic, OpenAI, or Google with an + API key from that provider. + + On **Settings → Agent**, choose **Agent = ACP**, pick the **Preset**, pick a + **Model**, and set the provider's **API key**. Leave the base URL blank so the + CLI uses the provider's default endpoint. + + | Provider | API key field | + |---|---| + | **Claude Code** | `ANTHROPIC_API_KEY` | + | **Codex** | `OPENAI_API_KEY` | + | **Gemini CLI** | `GEMINI_API_KEY` | + + Leave the subscription/sign-in fields blank in this mode (see + [Which credential wins](#which-credential-wins)). **Save**, then start a new + conversation. + + + + Use this when you already run a LiteLLM proxy (including one that fronts AWS + Bedrock) and want ACP agents to use your managed models — no external + provider API keys required. This is the recommended setup for self-hosted + deployments that already centralize model access. + + On **Settings → Agent**, choose **Agent = ACP**, pick the **Preset**, pick a + **Model**, then set the provider's **API key** to your LiteLLM key and its + **base URL** to your proxy URL: + + | Provider | Set API key to your LiteLLM key | Set base URL to your proxy | + |---|---|---| + | **Claude Code** | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` | + | **Codex** | `OPENAI_API_KEY` | `OPENAI_BASE_URL` | + | **Gemini CLI** | `GEMINI_API_KEY` (required) | `GEMINI_BASE_URL` | + + Leave the subscription/sign-in fields blank so they don't override the proxy + (see [Which credential wins](#which-credential-wins)). **Save**, then start a + new conversation. + + + Your proxy URL is your deployment's LiteLLM URL. On OpenHands SaaS this is + `https://llm-proxy.app.all-hands.dev`; on a self-hosted cluster it's your + own LiteLLM endpoint. The model you pick must be registered on your proxy — + see [Why do I get "model not found"](#faq). + + + + +## Which credential wins + +Each provider can authenticate two ways, and its **sign-in credential takes +priority over an API key**. If you set both, the API key and base URL are +ignored — which is the usual reason a proxy or key "does nothing." Set only the +credentials for the mode you want: + +| Provider | Sign-in credential (wins if set) | API key + base URL (used only when sign-in is blank) | +|---|---|---| +| **Claude Code** | `CLAUDE_CODE_OAUTH_TOKEN` | `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL` | +| **Codex** | `CODEX_AUTH_JSON` | `OPENAI_API_KEY`, `OPENAI_BASE_URL` | +| **Gemini CLI** | Vertex/OAuth fields (`GOOGLE_APPLICATION_CREDENTIALS_JSON`, `GOOGLE_CLOUD_*`, `GOOGLE_GENAI_USE_VERTEXAI`) | `GEMINI_API_KEY`, `GEMINI_BASE_URL` | + + + To route through a proxy or a direct API key, leave the sign-in credential for + that provider **blank**. For Claude Code in particular, a + `CLAUDE_CODE_OAUTH_TOKEN` causes both `ANTHROPIC_API_KEY` and + `ANTHROPIC_BASE_URL` to be dropped. + + +## FAQ + + + + No. Point each ACP agent at your proxy using its API key and base URL fields + (the **Your LiteLLM / Bedrock proxy** tab above). The agent then uses your + managed models, and no Anthropic/OpenAI/Google key is required. + + + + No. OHE's central LLM settings apply to the OpenHands agent. Each ACP agent + manages its own model routing through the credentials on the + **Settings → Agent** page. That's why you configure the model access per agent + here. + + + + No — and that's what makes this reliable. Each CLI resolves its own model and + calls your proxy directly in that provider's native format; OHE does not sit + in the middle rewriting the request. The only requirement is on your proxy: + register each model under the name that CLI uses and accept that provider's + API format (LiteLLM supports all three, including with Bedrock upstream). + + + + The model you selected isn't registered on your proxy under the name the CLI + sent. Register that model on the proxy, or pick a model in OHE that your proxy + already exposes. + + + + A sign-in credential is set and taking priority. Clear the provider's sign-in + field (`CLAUDE_CODE_OAUTH_TOKEN`, `CODEX_AUTH_JSON`, or Gemini's Vertex/OAuth + fields), re-save the API key and base URL, and start a new conversation. See + [Which credential wins](#which-credential-wins). + + + + Agent and credential changes apply to **new** conversations. Start a new one + to pick up the change. + + + + ACP is not enabled for your deployment. Ask your administrator to enable it. + + + + Yes. Choose the **Custom** preset and enter the launch command for any + stdio ACP server, then add whatever credentials it expects as secrets. + + + +## Related + +- [ACP Agents](/openhands/usage/agent-canvas/acp-agents) — how ACP agents work and how authentication is detected +- [External LLM Gateways](/enterprise/integrations/external-llm-gateways) — route the **OpenHands** agent through an external gateway +- [Agent Client Protocol](https://agentclientprotocol.com/protocol/overview) diff --git a/enterprise/integrations/acp-litellm.mdx b/enterprise/integrations/acp-litellm.mdx deleted file mode 100644 index b9e58cb9..00000000 --- a/enterprise/integrations/acp-litellm.mdx +++ /dev/null @@ -1,234 +0,0 @@ ---- -title: ACP Agents with LiteLLM -description: Route Claude Code, Codex, and Gemini CLI ACP agents through your existing LiteLLM or Bedrock proxy so they use your managed models instead of external provider API keys. -icon: robot ---- - -OpenHands Enterprise (OHE) can drive a conversation with the built-in -**OpenHands** agent or with a third-party **ACP agent** — Claude Code, Codex, or -Gemini CLI — on the same sandbox. This guide is for operators who already run a -LiteLLM proxy (including one that fronts AWS Bedrock) and want the ACP agents to -call models through that proxy instead of reaching out to Anthropic, OpenAI, or -Google directly with external API keys. - - - This guide covers routing the **ACP agent CLIs** through a LiteLLM proxy. It is - distinct from chaining OHE's built-in LiteLLM to an external gateway for the - OpenHands agent — for that, see - [External LLM Gateways](/enterprise/integrations/external-llm-gateways). For a - conceptual overview of ACP agents themselves, see - [ACP Agents](/openhands/usage/agent-canvas/acp-agents). - - -## Architecture: where the ACP agent runs - -The ACP agent CLI runs **inside the sandbox runtime**, not on the OpenHands -server pod. When you select an ACP agent, the Agent Server that manages the -sandbox spawns the provider's CLI (for example `claude-agent-acp`) as a -subprocess and relays each conversation turn to it over JSON-RPC on stdio. The -CLI has native filesystem access to the workspace, and it makes its own LLM -calls directly. - -```text -OpenHands Server pod - │ (conversation turns) - ▼ -Agent Server ──spawns──► ACP agent CLI ──LLM calls──► Your LiteLLM proxy -(inside sandbox pod) (inside sandbox pod) (LiteLLM / Bedrock) -``` - -This has two consequences that matter for the LiteLLM setup: - -- **The sandbox already has what the CLI needs.** Node and `npx` are present, the - workspace is mounted, and the CLI runs next to it — no round-trip to the server - pod for filesystem work. -- **The LLM call originates from the sandbox.** Each ACP CLI resolves its own - model alias locally and sends its vendor's native model ID over its vendor's - native wire format straight to the endpoint you configure. Nothing in OHE sits - in the middle rewriting the request. That is why the base-URL override below is - all that is needed: point the CLI at your proxy and the CLI does the rest. - - - Because each CLI talks its own vendor dialect directly to the proxy, your - LiteLLM proxy must expose models under the identifiers that each CLI sends - (Anthropic-style names for Claude Code, OpenAI-style for Codex, Gemini names - for Gemini CLI), and accept each vendor's native API shape. LiteLLM handles - all three natively. This path is exercised by the OpenHands ACP evals, which - run Codex, Claude Code, and Gemini CLI through a LiteLLM proxy. - - -## Prerequisites - -- **OHE is installed and reachable**, and you can sign in at - `https://app.`. -- **A LiteLLM proxy reachable from the sandbox network**, with the models you - want to use registered under the vendor-native names each CLI expects. If the - proxy fronts Bedrock, that mapping is configured on the proxy side; OHE does - not need Bedrock credentials. -- **A LiteLLM virtual key** the CLIs can authenticate with. - - - The proxy URL is your deployment's LiteLLM URL. On OpenHands SaaS this is - `https://llm-proxy.app.all-hands.dev`; on a self-hosted OHE cluster it is your - own LiteLLM endpoint (for example the in-cluster `http://openhands-litellm:4000` - or whatever hostname your proxy is exposed on). - - -## How ACP credentials reach the CLI - -Every field you set for an ACP agent is stored as a **global secret** whose name -is exactly the environment variable the Agent Server exports into the ACP -subprocess. Keeping the secret name identical to the env var is what makes the -value actually reach the CLI. You can set these during onboarding or at any time -under **Settings → Secrets**; the **Settings → Agent** page surfaces the same -fields per provider. - -The two fields that matter for LiteLLM routing are the provider's **API key** -env var (set to your LiteLLM virtual key) and its **base URL** env var (set to -your proxy URL): - -| Provider | API key env var | Base URL env var | -|---|---|---| -| **Claude Code** | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` | -| **Codex** | `OPENAI_API_KEY` | `OPENAI_BASE_URL` | -| **Gemini CLI** | `GEMINI_API_KEY` | `GEMINI_BASE_URL` | - - - Each provider also exposes a subscription/OAuth or file-based credential - (Claude Code's `CLAUDE_CODE_OAUTH_TOKEN`, Codex's `CODEX_AUTH_JSON`, Gemini's - Vertex/OAuth fields). **Those take precedence over the API key + base URL and - bypass your proxy.** Leave them blank when routing through LiteLLM. Each - section below calls out exactly which fields to leave empty. - - -## Configure each provider - -All steps happen on **Settings → Agent**. Choose **Agent = ACP**, pick the -**Preset**, then set the credentials as global secrets. - - - - 1. **Agent = ACP**, **Preset = Claude Code**, and pick a **Model**. - 2. Set `ANTHROPIC_API_KEY` to your LiteLLM virtual key. - 3. Set `ANTHROPIC_BASE_URL` to your proxy URL, then **Save**. - - This works because `claude-agent-acp` reads `ANTHROPIC_BASE_URL` directly and - sends Anthropic-native requests to it. - - - Leave `CLAUDE_CODE_OAUTH_TOKEN` **blank**. When it is set, the SDK strips - both `ANTHROPIC_API_KEY` and `ANTHROPIC_BASE_URL` (the OAuth bearer - authenticates against Anthropic directly, and a base URL would misroute - it), so the CLI bypasses your proxy entirely. - - - - - 1. **Agent = ACP**, **Preset = Codex**, and pick a **Model** (for example - `gpt-5.5`). - 2. Set `OPENAI_API_KEY` to your LiteLLM virtual key. - 3. Set `OPENAI_BASE_URL` to your proxy URL, then **Save**. - - Codex does not read `OPENAI_BASE_URL` from the environment itself — its - base-URL setting lives in its own config. OHE handles this for you: it - rewrites `OPENAI_BASE_URL` into the config key the Codex ACP adapter reads, - so the value you set takes effect. - - - Leave `CODEX_AUTH_JSON` **blank**. The subscription auth file takes - precedence over the key + base URL and bypasses your proxy. - - - - - 1. **Agent = ACP**, **Preset = Gemini CLI**, and pick a **Model** (for - example `gemini-2.5-pro`, or `auto`). - 2. Set `GEMINI_API_KEY` to your LiteLLM virtual key. This is **required** — it - selects the routable API-key auth path. - 3. Set `GEMINI_BASE_URL` to your proxy URL, then **Save**. - - The SDK passes the base URL to the CLI as its authenticate **gateway** target, - which is how Gemini CLI is routed through LiteLLM. - - - Leave the Vertex/OAuth fields **blank**: - `GOOGLE_APPLICATION_CREDENTIALS_JSON`, `GOOGLE_CLOUD_PROJECT`, - `GOOGLE_CLOUD_LOCATION`, and `GOOGLE_GENAI_USE_VERTEXAI`. They take - precedence and bypass the proxy. - - - - -## The model-identifier handshake - -A common concern when routing agent CLIs through a proxy is whether the model -name and provider "dialect" survive the trip. With ACP agents there is no -translation layer to get wrong: each CLI resolves its configured model alias -locally and issues the LLM call itself, in its own vendor's API format, directly -to the base URL you set. OHE does not rewrite the model name or the request -shape. - -The practical requirement is on the **proxy** side, not OHE: - -- The model you select for each ACP agent must be registered on your LiteLLM - proxy under the identifier that CLI sends. -- The proxy must accept that CLI's native request shape (Anthropic Messages for - Claude Code, OpenAI Chat Completions for Codex, the Gemini API for Gemini CLI). - LiteLLM supports all three, including when the upstream is Bedrock. - -If a call fails with a model-not-found or 4xx error, it is almost always a proxy -mapping issue — the model alias the CLI sent is not registered on the proxy under -that exact name — rather than something OHE is doing to the request. - -## Verify - -1. Start a new conversation with the ACP agent configured above and send a simple - prompt (for example, "list the files in the workspace"). -2. Confirm the turn completes and, if your proxy has spend/usage logging, that - the call appears on **your** LiteLLM proxy — not on the provider's own - dashboard. -3. Switch the selected **Model** and repeat to confirm your other proxy-hosted - models are reachable. - -## Troubleshooting - - - - A subscription/OAuth or file credential is set and taking precedence. Clear - the provider's precedence field: `CLAUDE_CODE_OAUTH_TOKEN` (Claude Code), - `CODEX_AUTH_JSON` (Codex), or the `GOOGLE_APPLICATION_CREDENTIALS_JSON` / - `GOOGLE_CLOUD_*` / `GOOGLE_GENAI_USE_VERTEXAI` fields (Gemini CLI). Then - re-save the API key + base URL and start a new conversation. - - - - Confirm `CLAUDE_CODE_OAUTH_TOKEN` is blank. When it is present the SDK strips - `ANTHROPIC_API_KEY` and `ANTHROPIC_BASE_URL`, so the base URL never reaches - the CLI. - - - - This means the base URL did not take effect and Codex fell back to the real - OpenAI API. Verify `OPENAI_BASE_URL` is set as a global secret (exact name) - and that `CODEX_AUTH_JSON` is blank, then start a new conversation. - - - - The alias the CLI sent is not registered on the LiteLLM proxy under that - name. Register the model on the proxy using the vendor-native identifier the - CLI uses, or select a model in OHE that is already registered. - - - - Agent and credential changes apply to conversations started **after** the - change. A conversation keeps the agent and settings it started with — start a - new conversation to pick up the new configuration. - - - -## Reference - -- [ACP Agents](/openhands/usage/agent-canvas/acp-agents) — concepts, providers, and authentication -- [External LLM Gateways](/enterprise/integrations/external-llm-gateways) — chain OHE's built-in LiteLLM to an external gateway (OpenHands agent) -- [ACP Agent (SDK)](/sdk/guides/agent-acp) — the SDK-level `ACPAgent` and its base-URL/credential handling -- [Agent Client Protocol](https://agentclientprotocol.com/protocol/overview) From 2273a7105b86b1362198eb1fb95fd749e3ffccd3 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 25 Aug 2026 15:15:41 +0000 Subject: [PATCH 3/3] docs(enterprise): add macOS clipboard one-liner for Codex auth paste Make the CODEX_AUTH_JSON sign-in step more consumable: copy the file straight to the clipboard with 'cat ~/.codex/auth.json | pbcopy' and paste, with a Linux equivalent. Also applies to Gemini's SA JSON blob. Co-authored-by: openhands --- enterprise/integrations/acp-agents.mdx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/enterprise/integrations/acp-agents.mdx b/enterprise/integrations/acp-agents.mdx index ce5b39f5..9c19fca5 100644 --- a/enterprise/integrations/acp-agents.mdx +++ b/enterprise/integrations/acp-agents.mdx @@ -81,6 +81,19 @@ matches your situation. | **Codex** | `CODEX_AUTH_JSON` | Paste the contents of `~/.codex/auth.json` from a ChatGPT‑logged‑in machine | | **Gemini CLI** | Vertex AI fields (`GOOGLE_APPLICATION_CREDENTIALS_JSON`, `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION`, `GOOGLE_GENAI_USE_VERTEXAI`) | From your Google Cloud project | + + For the paste-the-file-contents fields (`CODEX_AUTH_JSON` and Gemini's + `GOOGLE_APPLICATION_CREDENTIALS_JSON`), copy the file straight to your + clipboard instead of opening it. On macOS, from a terminal window: + + ```bash + cat ~/.codex/auth.json | pbcopy + ``` + + Then paste into the `CODEX_AUTH_JSON` field. (On Linux, use `xclip + -selection clipboard` or `wl-copy` in place of `pbcopy`.) + + Leave the API key and base URL blank in this mode. **Save**, then start a new conversation.