agentrelay-cli 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentrelay_cli-0.5.1/.github/workflows/ci.yml +20 -0
- agentrelay_cli-0.5.1/.github/workflows/publish.yml +68 -0
- agentrelay_cli-0.5.1/.gitignore +8 -0
- agentrelay_cli-0.5.1/AGENTS.md +111 -0
- agentrelay_cli-0.5.1/LICENSE +21 -0
- agentrelay_cli-0.5.1/PKG-INFO +233 -0
- agentrelay_cli-0.5.1/README.md +222 -0
- agentrelay_cli-0.5.1/pyproject.toml +32 -0
- agentrelay_cli-0.5.1/src/claude_relay/__init__.py +3 -0
- agentrelay_cli-0.5.1/src/claude_relay/__main__.py +93 -0
- agentrelay_cli-0.5.1/src/claude_relay/server.py +856 -0
- agentrelay_cli-0.5.1/src/claude_relay/service.py +233 -0
- agentrelay_cli-0.5.1/tests/__init__.py +0 -0
- agentrelay_cli-0.5.1/tests/test_server.py +942 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
19
|
+
- run: uv sync --python ${{ matrix.python-version }}
|
|
20
|
+
- run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish-agentrelay-cli:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
- run: uv build
|
|
19
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
20
|
+
|
|
21
|
+
publish-claude-relay-compat:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
needs: publish-agentrelay-cli
|
|
24
|
+
environment: pypi
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v5
|
|
28
|
+
- name: Build compatibility package
|
|
29
|
+
run: |
|
|
30
|
+
python - <<'PY'
|
|
31
|
+
import pathlib
|
|
32
|
+
import tomllib
|
|
33
|
+
|
|
34
|
+
root = pathlib.Path(".")
|
|
35
|
+
version = tomllib.loads((root / "pyproject.toml").read_text())["project"]["version"]
|
|
36
|
+
|
|
37
|
+
compat_root = root / "compat" / "claude-relay"
|
|
38
|
+
pkg_dir = compat_root / "src" / "claude_relay_compat"
|
|
39
|
+
pkg_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
(pkg_dir / "__init__.py").write_text('__all__ = ["__version__"]\n__version__ = "' + version + '"\n')
|
|
41
|
+
|
|
42
|
+
(compat_root / "README.md").write_text(
|
|
43
|
+
"# claude-relay (compatibility package)\n\n"
|
|
44
|
+
"Deprecated compatibility package. Install `agentrelay-cli` for canonical updates.\n"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
(compat_root / "pyproject.toml").write_text(
|
|
48
|
+
f'''[project]
|
|
49
|
+
name = "claude-relay"
|
|
50
|
+
version = "{version}"
|
|
51
|
+
description = "Compatibility package; use agentrelay-cli"
|
|
52
|
+
readme = "README.md"
|
|
53
|
+
requires-python = ">=3.10"
|
|
54
|
+
dependencies = ["agentrelay-cli=={version}"]
|
|
55
|
+
|
|
56
|
+
[build-system]
|
|
57
|
+
requires = ["hatchling"]
|
|
58
|
+
build-backend = "hatchling.build"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["src/claude_relay_compat"]
|
|
62
|
+
'''
|
|
63
|
+
)
|
|
64
|
+
PY
|
|
65
|
+
uv build compat/claude-relay --out-dir compat/claude-relay/dist
|
|
66
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
67
|
+
with:
|
|
68
|
+
packages-dir: compat/claude-relay/dist
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# AGENTS.md — Agent Relay
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
Drop-in OpenAI & Anthropic API-compatible server that routes requests through `claude -p` CLI.
|
|
6
|
+
|
|
7
|
+
## Quick setup
|
|
8
|
+
|
|
9
|
+
**Prerequisites:** `claude` CLI installed and on PATH, Python 3.10+, `uv`
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install and run
|
|
13
|
+
uvx agent-relay serve
|
|
14
|
+
|
|
15
|
+
# Or from source
|
|
16
|
+
git clone https://github.com/npow/claude-relay.git
|
|
17
|
+
cd claude-relay && uv sync && uv run agent-relay serve
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Default: `http://0.0.0.0:8082`. Override with `--host` / `--port`.
|
|
21
|
+
|
|
22
|
+
## Configuring clients
|
|
23
|
+
|
|
24
|
+
**OpenAI SDK (Python):**
|
|
25
|
+
```python
|
|
26
|
+
from openai import OpenAI
|
|
27
|
+
client = OpenAI(base_url="http://localhost:8082/v1", api_key="unused")
|
|
28
|
+
response = client.chat.completions.create(
|
|
29
|
+
model="sonnet", messages=[{"role": "user", "content": "Hello"}], stream=True
|
|
30
|
+
)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Anthropic SDK (Python):**
|
|
34
|
+
```python
|
|
35
|
+
from anthropic import Anthropic
|
|
36
|
+
client = Anthropic(base_url="http://localhost:8082", api_key="unused")
|
|
37
|
+
response = client.messages.create(
|
|
38
|
+
model="sonnet", max_tokens=1024, messages=[{"role": "user", "content": "Hello"}]
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**LangChain:**
|
|
43
|
+
```python
|
|
44
|
+
from langchain_openai import ChatOpenAI
|
|
45
|
+
llm = ChatOpenAI(base_url="http://localhost:8082/v1", api_key="unused", model="sonnet")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**curl:**
|
|
49
|
+
```bash
|
|
50
|
+
curl http://localhost:8082/v1/chat/completions \
|
|
51
|
+
-H "Content-Type: application/json" \
|
|
52
|
+
-d '{"model":"sonnet","messages":[{"role":"user","content":"Hello"}]}'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API endpoints
|
|
56
|
+
|
|
57
|
+
| Method | Path | Description |
|
|
58
|
+
|--------|------|-------------|
|
|
59
|
+
| GET | `/health` | Server + CLI status |
|
|
60
|
+
| GET | `/v1/models` | List available models |
|
|
61
|
+
| POST | `/v1/chat/completions` | OpenAI-compatible chat |
|
|
62
|
+
| POST | `/v1/messages` | Anthropic-compatible messages |
|
|
63
|
+
|
|
64
|
+
All endpoints also available without `/v1` prefix (e.g. `/models`, `/chat/completions`, `/messages`).
|
|
65
|
+
|
|
66
|
+
## Models
|
|
67
|
+
|
|
68
|
+
| Model | Notes |
|
|
69
|
+
|-------|-------|
|
|
70
|
+
| `opus` | Most capable |
|
|
71
|
+
| `sonnet` | **Default** if omitted |
|
|
72
|
+
| `haiku` | Fastest |
|
|
73
|
+
|
|
74
|
+
Passed directly to `claude --model`.
|
|
75
|
+
|
|
76
|
+
## Ignored parameters
|
|
77
|
+
|
|
78
|
+
These are silently discarded — Claude Code CLI does not expose them:
|
|
79
|
+
|
|
80
|
+
`temperature`, `max_tokens`, `top_p`, `top_k`, `n`, `tools`, `tool_choice`, `functions`, `function_call`, `response_format`
|
|
81
|
+
|
|
82
|
+
Image/audio content blocks are stripped to text-only.
|
|
83
|
+
|
|
84
|
+
## Troubleshooting
|
|
85
|
+
|
|
86
|
+
| Issue | Fix |
|
|
87
|
+
|-------|-----|
|
|
88
|
+
| Health check fails | `curl http://localhost:8082/health` — verify `claude` is on PATH |
|
|
89
|
+
| `claude` not found | Ensure CLI installed: `which claude`. Restart shell if just installed |
|
|
90
|
+
| Slow responses (~2-3s overhead) | Expected — each request spawns a `claude -p` subprocess |
|
|
91
|
+
| Images/audio ignored | Only text content is extracted from multimodal messages |
|
|
92
|
+
| No tool calling | Claude Code uses its own tools internally; tool parameters are ignored |
|
|
93
|
+
| CORS errors | CORS is enabled for all origins by default |
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
**Source layout:**
|
|
98
|
+
```
|
|
99
|
+
src/claude_relay/
|
|
100
|
+
├── __init__.py # Version
|
|
101
|
+
├── __main__.py # CLI entry point (argparse)
|
|
102
|
+
└── server.py # FastAPI app, all endpoints and logic
|
|
103
|
+
tests/
|
|
104
|
+
└── test_server.py # Unit + integration tests
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Run tests:**
|
|
108
|
+
```bash
|
|
109
|
+
uv sync
|
|
110
|
+
uv run pytest tests/ -v
|
|
111
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 npow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentrelay-cli
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: OpenAI- and Anthropic-compatible API server that routes through agent CLIs
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: fastapi>=0.115
|
|
9
|
+
Requires-Dist: uvicorn>=0.34
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# agent-relay
|
|
13
|
+
|
|
14
|
+
[](https://github.com/npow/claude-relay/actions/workflows/ci.yml)
|
|
15
|
+
[](https://pypi.org/project/agentrelay-cli/)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](https://www.python.org/downloads/)
|
|
18
|
+
|
|
19
|
+
Drop-in OpenAI **and Anthropic** API server that routes through agent CLIs (currently [Claude Code](https://docs.anthropic.com/en/docs/claude-code)).
|
|
20
|
+
|
|
21
|
+
> Compatibility note: `claude-relay` remains available as a compatibility package/command alias.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
You have tools that speak the OpenAI or Anthropic API. You have Claude Code with its tools, MCP servers, and agentic capabilities. **agent-relay** bridges the two — point any compatible client at it and every request flows through `claude -p` under the hood.
|
|
26
|
+
|
|
27
|
+
- **Use Claude Code from any OpenAI or Anthropic client** — Cursor, Continue, aider, LangChain, custom scripts
|
|
28
|
+
- **Keep Claude Code's superpowers** — tool use, MCP servers, file access, shell execution
|
|
29
|
+
- **Zero config** — if `claude` works on your machine, so does this
|
|
30
|
+
- **Real token usage** — reports actual token counts from Claude (not zeros)
|
|
31
|
+
- **Token-level streaming** — uses `--include-partial-messages` for true real-time deltas
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# With uv (recommended)
|
|
37
|
+
uvx agent-relay serve
|
|
38
|
+
|
|
39
|
+
# Or install globally
|
|
40
|
+
uv tool install agentrelay-cli
|
|
41
|
+
agent-relay serve
|
|
42
|
+
|
|
43
|
+
# Or from source
|
|
44
|
+
git clone https://github.com/npow/claude-relay.git
|
|
45
|
+
cd claude-relay
|
|
46
|
+
uv sync
|
|
47
|
+
uv run agent-relay serve
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agent-relay serve
|
|
54
|
+
# Server starts on http://localhost:18082
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Run as background service (macOS)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Install and auto-start on login
|
|
61
|
+
agent-relay service install
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The installer will offer to add these to your `~/.zshrc` (or `~/.bashrc`) so every SDK and agent picks up the relay automatically:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
export ANTHROPIC_BASE_URL="http://127.0.0.1:18082"
|
|
68
|
+
export OPENAI_BASE_URL="http://127.0.0.1:18082/v1"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Check status
|
|
73
|
+
agent-relay service status
|
|
74
|
+
|
|
75
|
+
# Update
|
|
76
|
+
uv tool upgrade agentrelay-cli
|
|
77
|
+
agent-relay service restart
|
|
78
|
+
|
|
79
|
+
# Stop and remove
|
|
80
|
+
agent-relay service uninstall
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Point any OpenAI-compatible client at it:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from openai import OpenAI
|
|
87
|
+
|
|
88
|
+
client = OpenAI(base_url="http://localhost:18082/v1", api_key="unused")
|
|
89
|
+
|
|
90
|
+
# Streaming
|
|
91
|
+
for chunk in client.chat.completions.create(
|
|
92
|
+
model="sonnet",
|
|
93
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
94
|
+
stream=True,
|
|
95
|
+
):
|
|
96
|
+
print(chunk.choices[0].delta.content or "", end="")
|
|
97
|
+
|
|
98
|
+
# Non-streaming
|
|
99
|
+
resp = client.chat.completions.create(
|
|
100
|
+
model="sonnet",
|
|
101
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
102
|
+
)
|
|
103
|
+
print(resp.choices[0].message.content)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Anthropic SDK
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import anthropic
|
|
110
|
+
|
|
111
|
+
# Just set the base URL — the SDK reads ANTHROPIC_BASE_URL automatically
|
|
112
|
+
# export ANTHROPIC_BASE_URL=http://localhost:18082
|
|
113
|
+
client = anthropic.Anthropic(base_url="http://localhost:18082")
|
|
114
|
+
|
|
115
|
+
# Streaming
|
|
116
|
+
with client.messages.stream(
|
|
117
|
+
model="sonnet",
|
|
118
|
+
max_tokens=1024,
|
|
119
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
120
|
+
) as stream:
|
|
121
|
+
for text in stream.text_stream:
|
|
122
|
+
print(text, end="")
|
|
123
|
+
|
|
124
|
+
# Non-streaming
|
|
125
|
+
resp = client.messages.create(
|
|
126
|
+
model="sonnet",
|
|
127
|
+
max_tokens=1024,
|
|
128
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
129
|
+
)
|
|
130
|
+
print(resp.content[0].text)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### LangChain
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from langchain_anthropic import ChatAnthropic
|
|
137
|
+
|
|
138
|
+
# export ANTHROPIC_BASE_URL=http://localhost:18082
|
|
139
|
+
llm = ChatAnthropic(model="sonnet")
|
|
140
|
+
print(llm.invoke("Hello!").content)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### curl
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# OpenAI format
|
|
147
|
+
curl http://localhost:18082/v1/chat/completions \
|
|
148
|
+
-H "Content-Type: application/json" \
|
|
149
|
+
-d '{"model":"sonnet","messages":[{"role":"user","content":"Hello"}],"stream":true}'
|
|
150
|
+
|
|
151
|
+
# OpenAI Responses format
|
|
152
|
+
curl http://localhost:18082/v1/responses \
|
|
153
|
+
-H "Content-Type: application/json" \
|
|
154
|
+
-d '{"model":"sonnet","input":"Hello"}'
|
|
155
|
+
|
|
156
|
+
# Anthropic format
|
|
157
|
+
curl http://localhost:18082/v1/messages \
|
|
158
|
+
-H "Content-Type: application/json" \
|
|
159
|
+
-d '{"model":"sonnet","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Configuration
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
agent-relay serve [--host HOST] [--port PORT]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Flag | Default | Description |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `--host` | `0.0.0.0` | Bind address |
|
|
171
|
+
| `--port` | `18082` | Bind port |
|
|
172
|
+
|
|
173
|
+
## API
|
|
174
|
+
|
|
175
|
+
| Endpoint | Method | Description |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `/v1/chat/completions` | POST | Chat completions (OpenAI-compatible) |
|
|
178
|
+
| `/v1/responses` | POST | Responses API (OpenAI-compatible) |
|
|
179
|
+
| `/v1/messages` | POST | Messages (Anthropic-compatible) |
|
|
180
|
+
| `/v1/models` | GET | List available models |
|
|
181
|
+
| `/health` | GET | Server and CLI status |
|
|
182
|
+
|
|
183
|
+
All endpoints also work without the `/v1` prefix. CORS is enabled for all origins.
|
|
184
|
+
|
|
185
|
+
### Supported features
|
|
186
|
+
|
|
187
|
+
| Feature | Status |
|
|
188
|
+
|---|---|
|
|
189
|
+
| Streaming (SSE) | Yes |
|
|
190
|
+
| System messages | Yes (via `--system-prompt`) |
|
|
191
|
+
| Multi-turn conversations | Yes |
|
|
192
|
+
| Multimodal (text parts) | Yes |
|
|
193
|
+
| Model selection | Yes |
|
|
194
|
+
| Token usage reporting | Yes |
|
|
195
|
+
| CORS | Yes |
|
|
196
|
+
|
|
197
|
+
### Models
|
|
198
|
+
|
|
199
|
+
Pass any model name — it goes directly to `claude --model`:
|
|
200
|
+
|
|
201
|
+
| Model | Description |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `opus` | Most capable |
|
|
204
|
+
| `sonnet` | Balanced (default) |
|
|
205
|
+
| `haiku` | Fastest |
|
|
206
|
+
|
|
207
|
+
## Limitations
|
|
208
|
+
|
|
209
|
+
- `temperature`, `max_tokens`, `top_p`, and other sampling parameters are ignored (Claude Code CLI does not expose them)
|
|
210
|
+
- No tool/function calling passthrough (Claude Code uses its own tools internally, but they aren't exposed via the OpenAI tool-calling protocol)
|
|
211
|
+
- Each request spawns a new `claude` process (~2-3s overhead on top of API latency)
|
|
212
|
+
- No image/audio content forwarding — only text parts of multimodal messages are extracted
|
|
213
|
+
|
|
214
|
+
## How it works
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
OpenAI client ─┐
|
|
218
|
+
├→ claude-relay → claude -p → Anthropic API
|
|
219
|
+
Anthropic client ─┘ (FastAPI) (stream-json)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Each request spawns a `claude -p` process with `--output-format stream-json --include-partial-messages`. The proxy translates between the OpenAI or Anthropic wire format and Claude Code's streaming JSON protocol. Requests are stateless — no conversation history bleeds between calls.
|
|
223
|
+
|
|
224
|
+
## Development
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
uv sync
|
|
228
|
+
uv run pytest tests/ -v
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# agent-relay
|
|
2
|
+
|
|
3
|
+
[](https://github.com/npow/claude-relay/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/agentrelay-cli/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
|
|
8
|
+
Drop-in OpenAI **and Anthropic** API server that routes through agent CLIs (currently [Claude Code](https://docs.anthropic.com/en/docs/claude-code)).
|
|
9
|
+
|
|
10
|
+
> Compatibility note: `claude-relay` remains available as a compatibility package/command alias.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
You have tools that speak the OpenAI or Anthropic API. You have Claude Code with its tools, MCP servers, and agentic capabilities. **agent-relay** bridges the two — point any compatible client at it and every request flows through `claude -p` under the hood.
|
|
15
|
+
|
|
16
|
+
- **Use Claude Code from any OpenAI or Anthropic client** — Cursor, Continue, aider, LangChain, custom scripts
|
|
17
|
+
- **Keep Claude Code's superpowers** — tool use, MCP servers, file access, shell execution
|
|
18
|
+
- **Zero config** — if `claude` works on your machine, so does this
|
|
19
|
+
- **Real token usage** — reports actual token counts from Claude (not zeros)
|
|
20
|
+
- **Token-level streaming** — uses `--include-partial-messages` for true real-time deltas
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# With uv (recommended)
|
|
26
|
+
uvx agent-relay serve
|
|
27
|
+
|
|
28
|
+
# Or install globally
|
|
29
|
+
uv tool install agentrelay-cli
|
|
30
|
+
agent-relay serve
|
|
31
|
+
|
|
32
|
+
# Or from source
|
|
33
|
+
git clone https://github.com/npow/claude-relay.git
|
|
34
|
+
cd claude-relay
|
|
35
|
+
uv sync
|
|
36
|
+
uv run agent-relay serve
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick start
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
agent-relay serve
|
|
43
|
+
# Server starts on http://localhost:18082
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Run as background service (macOS)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Install and auto-start on login
|
|
50
|
+
agent-relay service install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The installer will offer to add these to your `~/.zshrc` (or `~/.bashrc`) so every SDK and agent picks up the relay automatically:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
export ANTHROPIC_BASE_URL="http://127.0.0.1:18082"
|
|
57
|
+
export OPENAI_BASE_URL="http://127.0.0.1:18082/v1"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Check status
|
|
62
|
+
agent-relay service status
|
|
63
|
+
|
|
64
|
+
# Update
|
|
65
|
+
uv tool upgrade agentrelay-cli
|
|
66
|
+
agent-relay service restart
|
|
67
|
+
|
|
68
|
+
# Stop and remove
|
|
69
|
+
agent-relay service uninstall
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Point any OpenAI-compatible client at it:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from openai import OpenAI
|
|
76
|
+
|
|
77
|
+
client = OpenAI(base_url="http://localhost:18082/v1", api_key="unused")
|
|
78
|
+
|
|
79
|
+
# Streaming
|
|
80
|
+
for chunk in client.chat.completions.create(
|
|
81
|
+
model="sonnet",
|
|
82
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
83
|
+
stream=True,
|
|
84
|
+
):
|
|
85
|
+
print(chunk.choices[0].delta.content or "", end="")
|
|
86
|
+
|
|
87
|
+
# Non-streaming
|
|
88
|
+
resp = client.chat.completions.create(
|
|
89
|
+
model="sonnet",
|
|
90
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
91
|
+
)
|
|
92
|
+
print(resp.choices[0].message.content)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Anthropic SDK
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import anthropic
|
|
99
|
+
|
|
100
|
+
# Just set the base URL — the SDK reads ANTHROPIC_BASE_URL automatically
|
|
101
|
+
# export ANTHROPIC_BASE_URL=http://localhost:18082
|
|
102
|
+
client = anthropic.Anthropic(base_url="http://localhost:18082")
|
|
103
|
+
|
|
104
|
+
# Streaming
|
|
105
|
+
with client.messages.stream(
|
|
106
|
+
model="sonnet",
|
|
107
|
+
max_tokens=1024,
|
|
108
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
109
|
+
) as stream:
|
|
110
|
+
for text in stream.text_stream:
|
|
111
|
+
print(text, end="")
|
|
112
|
+
|
|
113
|
+
# Non-streaming
|
|
114
|
+
resp = client.messages.create(
|
|
115
|
+
model="sonnet",
|
|
116
|
+
max_tokens=1024,
|
|
117
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
118
|
+
)
|
|
119
|
+
print(resp.content[0].text)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### LangChain
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from langchain_anthropic import ChatAnthropic
|
|
126
|
+
|
|
127
|
+
# export ANTHROPIC_BASE_URL=http://localhost:18082
|
|
128
|
+
llm = ChatAnthropic(model="sonnet")
|
|
129
|
+
print(llm.invoke("Hello!").content)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### curl
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# OpenAI format
|
|
136
|
+
curl http://localhost:18082/v1/chat/completions \
|
|
137
|
+
-H "Content-Type: application/json" \
|
|
138
|
+
-d '{"model":"sonnet","messages":[{"role":"user","content":"Hello"}],"stream":true}'
|
|
139
|
+
|
|
140
|
+
# OpenAI Responses format
|
|
141
|
+
curl http://localhost:18082/v1/responses \
|
|
142
|
+
-H "Content-Type: application/json" \
|
|
143
|
+
-d '{"model":"sonnet","input":"Hello"}'
|
|
144
|
+
|
|
145
|
+
# Anthropic format
|
|
146
|
+
curl http://localhost:18082/v1/messages \
|
|
147
|
+
-H "Content-Type: application/json" \
|
|
148
|
+
-d '{"model":"sonnet","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Configuration
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
agent-relay serve [--host HOST] [--port PORT]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
| Flag | Default | Description |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `--host` | `0.0.0.0` | Bind address |
|
|
160
|
+
| `--port` | `18082` | Bind port |
|
|
161
|
+
|
|
162
|
+
## API
|
|
163
|
+
|
|
164
|
+
| Endpoint | Method | Description |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `/v1/chat/completions` | POST | Chat completions (OpenAI-compatible) |
|
|
167
|
+
| `/v1/responses` | POST | Responses API (OpenAI-compatible) |
|
|
168
|
+
| `/v1/messages` | POST | Messages (Anthropic-compatible) |
|
|
169
|
+
| `/v1/models` | GET | List available models |
|
|
170
|
+
| `/health` | GET | Server and CLI status |
|
|
171
|
+
|
|
172
|
+
All endpoints also work without the `/v1` prefix. CORS is enabled for all origins.
|
|
173
|
+
|
|
174
|
+
### Supported features
|
|
175
|
+
|
|
176
|
+
| Feature | Status |
|
|
177
|
+
|---|---|
|
|
178
|
+
| Streaming (SSE) | Yes |
|
|
179
|
+
| System messages | Yes (via `--system-prompt`) |
|
|
180
|
+
| Multi-turn conversations | Yes |
|
|
181
|
+
| Multimodal (text parts) | Yes |
|
|
182
|
+
| Model selection | Yes |
|
|
183
|
+
| Token usage reporting | Yes |
|
|
184
|
+
| CORS | Yes |
|
|
185
|
+
|
|
186
|
+
### Models
|
|
187
|
+
|
|
188
|
+
Pass any model name — it goes directly to `claude --model`:
|
|
189
|
+
|
|
190
|
+
| Model | Description |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `opus` | Most capable |
|
|
193
|
+
| `sonnet` | Balanced (default) |
|
|
194
|
+
| `haiku` | Fastest |
|
|
195
|
+
|
|
196
|
+
## Limitations
|
|
197
|
+
|
|
198
|
+
- `temperature`, `max_tokens`, `top_p`, and other sampling parameters are ignored (Claude Code CLI does not expose them)
|
|
199
|
+
- No tool/function calling passthrough (Claude Code uses its own tools internally, but they aren't exposed via the OpenAI tool-calling protocol)
|
|
200
|
+
- Each request spawns a new `claude` process (~2-3s overhead on top of API latency)
|
|
201
|
+
- No image/audio content forwarding — only text parts of multimodal messages are extracted
|
|
202
|
+
|
|
203
|
+
## How it works
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
OpenAI client ─┐
|
|
207
|
+
├→ claude-relay → claude -p → Anthropic API
|
|
208
|
+
Anthropic client ─┘ (FastAPI) (stream-json)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Each request spawns a `claude -p` process with `--output-format stream-json --include-partial-messages`. The proxy translates between the OpenAI or Anthropic wire format and Claude Code's streaming JSON protocol. Requests are stateless — no conversation history bleeds between calls.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uv sync
|
|
217
|
+
uv run pytest tests/ -v
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
[MIT](LICENSE)
|