# Wrapping Claude Code CLI in an OpenAI-Compatible API

*2026-11-04*

> Wrap Claude Code CLI in an OpenAI-compatible API with cc-proxy for Android Studio, including fixes for streaming tool calls and developer messages.


## Introduction <span class="f5 text-normal">· [Skip to setup](#set-up-cc-proxy)</span> {#introduction}

I run local AI models about 80% of the time. I occasionally use Codex for tougher software development tasks or to break larger jobs into smaller tasks for my local models.

My setup includes a dedicated AI machine with two NVIDIA Quadro P5000 GPUs (32 GB of VRAM in total), running [Ornith 1.5 35B A3B](https://huggingface.co/ornith-ai/Ornith-1.5-35B-A3B-GGUF) at `Q5_K_M` through llama.cpp server, with a 256k context window. It's quite speedy, too: I get around 60-70 tokens/s even with a nearly full context.

I also run Open WebUI for general chats and expose the llama.cpp API through a small PHP wrapper for integrations with Android Studio, VS Code, OpenCode, and the other IDEs I use daily. Ornith can access a couple of local MCP servers that expose my Forgejo instance and Jenkins CI/CD.

The company I work for has a Claude Team subscription. I'm not much of a Claude user, and I'm not particularly fond of it (*it gets offended too easily*), but as they say, *sometimes you just have to deal with it*. So, deal with it I did.

## Finding a working integration

### Attempt 1: Proper integration with Android Studio

As a sane person using the latest and *"greatest"* Android Studio Rabbit 2:

```text
Android Studio Rabbit 2 | 2026.2.2
Build #AI-262.10968.63.2622.16730706, built on October 27, 2026
Runtime version: 25.0.4+-16339643-b508.27 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Toolkit: sun.awt.wl.WLToolkit
Linux 6.12.113+deb13-amd64
Debian GNU/Linux 13 (trixie); glibc: 2.41; desktop: KDE (plasmashell 6.3.6)
```

I went to **Settings > Tools > AI > Agents** and tried installing **Claude Agent**, hoping to get back to actual work. If it had been that simple, this post wouldn't exist.

The installation got stuck without a useful explanation, even in the logs. Installing the Claude Code CLI and making sure Android Studio could find `claude` in my `$PATH` resolved that part.

Then came the next problem: the integrated agent wanted an Anthropic API key, which I didn't have.

### Attempt 2: Asking for an API key

This was a short one. API keys weren't included in our company's plan.

### Attempt 3: Claude Code CLI and the Android Studio plugin

The **Claude Code** plugin for Android Studio is essentially a terminal running the CLI inside the IDE. It works, but it feels like using a terminal from 20 years ago. I wanted a proper Agent chat with IDE tools, not a terminal pretending to be an integration.

### Attempt 4: Using my local LLM as a proxy for Claude Code

After completing two tasks through the Claude Code plugin, I started looking for alternatives that were a little more on the *hacky* side.

My first idea was a small MCP server that would let Ornith delegate requests to `claude -p`. Building an MCP bridge was the easy part.

The awkward part was keeping Ornith in the middle: it would still have to decide when to call Claude and how to handle tool calls. I wanted Claude behind Android Studio's Agent interface, not another agent supervising it. That made the MCP approach a poor fit.

### Attempt 5: Wrapping Claude Code CLI in an API

That led to a better question: could I wrap `claude -p` in an OpenAI-compatible endpoint and connect it to the tools I already use, including Android Studio and VS Code?

Before building yet another service myself, I did some searching and found [cc-sdk-go](https://github.com/codewandler/cc-sdk-go).

The repository describes itself as:

> Turn your Claude Code subscription into an OpenAI-compatible API. One binary. Zero config. Pure stdlib.

In short, it exposes the authenticated Claude Code CLI through an OpenAI-compatible HTTP API.

The installation was straightforward, apart from two Android Studio compatibility bugs I'll get to later.

## Set up `cc-proxy`

> **Compatibility:** `cc-proxy` provides `POST /v1/chat/completions` (streaming and non-streaming) and `GET /v1/models`. For tool calls, it adds instructions to the prompt and parses Claude's `<tool_call>` output into OpenAI-style `tool_calls`. Parameters such as `temperature`, `top_p`, `stop`, and `n` are accepted but aren't forwarded to the Claude Code CLI.

Versions used for this setup:

| Component | Version |
| --- | --- |
| Go toolchain used to build `cc-proxy` | `1.25.6` |
| Claude Code CLI | `2.1.318` |
| `cc-sdk-go` base commit | [`7ee389b12a4f`](https://github.com/codewandler/cc-sdk-go/commit/7ee389b12a4fc8988d31590f5413c51fc1747085), with the two patches described below |

### 1. Clone, patch, and install `cc-proxy`

You'll need Go **1.25.6 or newer** and a working, authenticated Claude Code CLI.

```sh
git clone https://github.com/codewandler/cc-sdk-go.git
cd cc-sdk-go
git checkout --detach 7ee389b12a4fc8988d31590f5413c51fc1747085

curl -fL -o cc-sdk.patch https://paskov.vmsoft-bg.com/downloads/cc-sdk.patch
git apply --check cc-sdk.patch
git apply cc-sdk.patch
```

Install the patched proxy:

```sh
go install ./cmd/cc-proxy
export PATH="$(go env GOPATH)/bin:$PATH"
```

[My patch](https://paskov.vmsoft-bg.com/downloads/cc-sdk.patch) fixes missing streaming tool-call indices and adds support for OpenAI `developer` messages. The commands above pin the repository to the base commit used in my setup. If you choose a newer revision that already includes both fixes, skip the patch download and the two `git apply` commands.

### 2. Generate a local API key

Generate the key and print its value:

```sh
export CC_PROXY_API_KEY="$(openssl rand -hex 24)"
echo "$CC_PROXY_API_KEY"
```

Copy the value printed by `echo "$CC_PROXY_API_KEY"` before starting the proxy. You'll use this same key in the `curl` requests and in Android Studio.

This key authenticates clients to your **local proxy**. It is not an Anthropic API key.

### 3. Launch the proxy

Run the following command in the same terminal where you generated the key, so the proxy can read `CC_PROXY_API_KEY`:

```sh
cc-proxy \
    -addr=127.0.0.1:8080 \
    -model=sonnet \
    -claude-path="$(command -v claude)" \
    -max-concurrent=2 \
    -timeout=10m
```

> **Note:** Run `cc-proxy --help` to see all available options. Binding to `127.0.0.1` keeps the endpoint accessible only from the local machine.

### 4. Test that everything works

Leave the proxy running and open a **second terminal** for the test requests. The new terminal won't automatically inherit `CC_PROXY_API_KEY` from the first one, so use the copied key directly in the Bearer authorization header.

In **both** commands below, replace `YOUR_LOCAL_API_KEY` with the exact value printed by `echo "$CC_PROXY_API_KEY"` in step 2.

First, list the available models:

```sh
curl http://127.0.0.1:8080/v1/models \
    -H "Authorization: Bearer YOUR_LOCAL_API_KEY"
```

The response should list the available models, including `sonnet`, `haiku`, and `opus` in my setup.

Next, try a normal chat completion:

```sh
curl http://127.0.0.1:8080/v1/chat/completions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer YOUR_LOCAL_API_KEY" \
    -d '{
      "model": "sonnet",
      "messages": [
        {
          "role": "user",
          "content": "Reply with exactly: Hello from Claude"
        }
      ],
      "stream": false
    }'
```

If all goes well, the response should contain `Hello from Claude`.

### 5. Configure Android Studio

Go to **Settings > Tools > AI > Model Providers**, add a **Third-Party Remote Provider**, and configure it as follows:

| Setting | Value |
| --- | --- |
| Provider Name | Claude Local |
| URL | `http://127.0.0.1:8080/v1` |
| URL Schema | OpenAI-compatible |
| API Key | Same key copied in step 2 |

Under **Available Models**, select the models you want. Then open Android Studio's **Agent** chat and select one of the models exposed by **Claude Local**.

![Claude Local selected in Android Studio’s Agent chat, with a successful response.](/blog/claude-local-android-studio.png)

## Fixing two bugs in `cc-proxy`

Getting Android Studio working required two small compatibility fixes. Tracking them down turned out to be the most interesting part of the experiment.

### Patch 1: `Model query failed: index is not set`

My first `curl` chat request worked, but Android Studio's Agent immediately failed with:

```text
Model query failed: index is not set
```

That turned out to be a mismatch in OpenAI's **streaming tool-call format**. The proxy emitted a tool call with an ID, name, and arguments, but without the required `index` field. To confirm it, I sent a streaming chat request with a dummy `ping` function and inspected the returned `delta.tool_calls`.

The response was effectively:

```json
{
  "id": "call_123",
  "type": "function",
  "function": {
    "name": "ping",
    "arguments": "{\"value\":\"hello\"}"
  }
}
```

Android Studio expected that same tool call to include `"index": 0`.

The fix was in two places. In `oai/request.go`, we added an index to `ToolCall`:

```go
Index *int `json:"index,omitempty"`
```

Using `*int` matters: a plain integer with `omitempty` would drop the first call's index because its value is zero. In `oai/bridge_stream.go`, `FinishChunk()` now assigns an index to each tool call before serializing the streaming chunk:

```go
for i := range toolCalls {
    index := i
    toolCalls[i].Index = &index
}
```

After rebuilding the proxy, the same test returned `"index": 0`, and Android Studio's Agent started accepting tool calls.

### Patch 2: Every conversation title started with "I can't..."

With tool calls working, I noticed another strange bug: Android Studio's conversation titles almost all started with *"I can't..."*. The conversations themselves worked fine; only their titles were nonsense.

To see what Android Studio was sending, I captured the local HTTP requests to the proxy using `tcpdump`. That revealed a separate title-generation request.

Its `developer` message instructed the model to turn the user's question into a short, 5-10-word title, while its `user` message contained the actual question, such as *"List all gradle modules in this project"*.

The culprit was in `oai/bridge_request.go`: `RequestToQuery()` recognized `system` messages but ignored `developer` messages entirely. So Claude never saw the summarization instruction and tried to answer the question instead. Hence the *"I can't..."* titles.

The fix was small:

```go
// Before
case "system":
    systemParts = append(systemParts, msg.StringContent())

// After
case "system", "developer":
    systemParts = append(systemParts, msg.StringContent())
```

Android Studio also uses `developer` messages for agent instructions, IDE tool descriptions, and project context. The proxy had been dropping those along with the title-generation instructions. The same fix takes care of both.

## The result

I now have Claude running in Android Studio's native Agent chat through a local OpenAI-compatible endpoint. The proxy launches the authenticated Claude Code CLI, so there's no separate Anthropic API key involved. The same endpoint can also be configured in other clients that support the OpenAI chat-completions API.

