Skip to content

Commit 5700e50

Browse files
authored
feat: Anthropic Messages API endpoint (/v1/messages) (#21)
* feat: add Anthropic Messages API endpoint (/v1/messages) * fix: review fixes for /v1/messages edge handling - model_not_found catch referenced out-of-scope 'model' (ReferenceError when resolveRequestedModel threw without a message); read req.body.model - streaming idleTimeout fallback duplicated already-streamed text: reuse the chat-completions startsWith prefix-trim, and only full-poll on __error/noData or a fully empty stream - terminal upstream failure during streaming returned an empty 200 message; emit an Anthropic error event + message_stop instead * fix(anthropic): normalize 5xx error types, preserve tool_result metadata, and flush filter buffers * fix(anthropic): emit streaming deltas incrementally, force required tool calls, and pass registry to markup stripper
1 parent 15404a4 commit 5700e50

5 files changed

Lines changed: 1577 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- **Anthropic Messages API**: `POST /v1/messages` implements the Anthropic Messages protocol (Claude SDK compatible) on top of the same OpenCode backend — `x-api-key` or Bearer auth, `system` (string or blocks), text/image/tool_use/tool_result content blocks, `tools` with `input_schema`, `tool_choice`, `stop_sequences`, and Anthropic-shaped errors (`{type:'error',error:{type,message}}`). Tool calls surface as `tool_use` content blocks with the original client tool names (no `external__` namespace). Streaming (`stream: true`) emits the Anthropic SSE sequence — `message_start`, `content_block_start/delta/stop` (`thinking_delta`, `text_delta`, `input_json_delta`), `message_delta`, `message_stop` — with keepalives and polling fallback, mirroring the chat-completions pipeline including transient-error retry. Documented in both READMEs; covered by `tests/unit/anthropic.test.js`.
1213
- **Built-in Web Panel (`WEB_PANEL_ENABLED` / `OPENCODE_WEB_PANEL_ENABLED`, default off)**: An optional read-only dashboard served by the proxy itself. `GET /panel` returns a self-contained dark-themed HTML page (zero external assets, CSP locked to same-origin, inline style/script only) showing runtime status (status, uptime, backend URL), the model list, and backend diagnostics from `/health/details` (tool-mode counters, tool ID cache). The page fetches `GET /panel/data.json` — a JSON feed assembled from the models list, the `/health/details` payload and `process.uptime()` — with a Bearer key kept in the browser's localStorage. Auth matches the diagnostics endpoints: with `API_KEY` set, `data.json` requires the Bearer key (the HTML shell is public but carries no data), and `PUBLIC_DIAGNOSTICS=true` or an empty `API_KEY` lifts auth. When disabled, both routes 404 and cost nothing. The feed never returns `OPENCODE_SERVER_PASSWORD`, `ZEN_API_KEY`, `API_KEY` or `API_KEYS` values, and responses carry `X-Content-Type-Options: nosniff` plus `Cache-Control: no-store`. Added `tests/unit/panel.test.js` (10 tests: disabled 404s, headers, 401 gating, secret-leak assertions, public/no-key modes) and full configuration docs.
1314

1415
## [2.5.0] - 2026-10-15

‎README.en.md‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,49 @@ curl -X POST http://127.0.0.1:10000/v1/chat/completions \
104104

105105
When the model calls tools, non-streaming responses return `message.tool_calls` and streaming responses return `delta.tool_calls`.
106106

107+
### Anthropic Messages API (/v1/messages)
108+
109+
An endpoint for Claude SDK / Anthropic clients. Authentication uses the `x-api-key` header (`Authorization: Bearer` also works), and tool calls come back as `tool_use` content blocks.
110+
111+
```bash
112+
curl -X POST http://127.0.0.1:10000/v1/messages \
113+
-H "x-api-key: ***" \
114+
-H "anthropic-version: 2023-06-01" \
115+
-H "Content-Type: application/json" \
116+
-d '{
117+
"model": "opencode/big-pickle",
118+
"max_tokens": 1024,
119+
"system": "You are concise.",
120+
"messages": [{"role": "user", "content": "Hello!"}],
121+
"stream": true
122+
}'
123+
```
124+
125+
Tool round-trip (send results back as `tool_result` blocks):
126+
127+
```bash
128+
curl -X POST http://127.0.0.1:10000/v1/messages \
129+
-H "x-api-key: ***" \
130+
-H "anthropic-version: 2023-06-01" \
131+
-H "Content-Type: application/json" \
132+
-d '{
133+
"model": "opencode/big-pickle",
134+
"max_tokens": 1024,
135+
"messages": [{"role": "user", "content": "Fetch the title of https://example.com"}],
136+
"tools": [{
137+
"name": "web_fetch",
138+
"description": "Fetch a URL and return its content summary",
139+
"input_schema": {
140+
"type": "object",
141+
"properties": {"url": {"type": "string"}},
142+
"required": ["url"]
143+
}
144+
}]
145+
}'
146+
```
147+
148+
The response contains a `{"type":"tool_use","id":"…","name":"web_fetch","input":{…}}` content block (`stop_reason: "tool_use"`); send the result back as a `{"type":"tool_result","tool_use_id":"…","content":"…"}` user block to continue the conversation. With `stream: true` the proxy emits the Anthropic SSE sequence `message_start` → `content_block_*` → `message_delta` → `message_stop`.
149+
107150
## ⚙️ Configuration
108151

109152
| Environment variable | Default | Description |
@@ -146,6 +189,7 @@ OPENCODE_PROXY_AUTO_CLEANUP_CONVERSATIONS=true
146189
| `GET` | `/v1/models` | Model list |
147190
| `POST` | `/v1/chat/completions` | Chat Completions |
148191
| `POST` | `/v1/responses` | Responses API |
192+
| `POST` | `/v1/messages` | Anthropic Messages API (Claude SDK compatible) |
149193

150194
Model names: `opencode/big-pickle`, `gpt5-nano` (auto-resolved to `gpt-5-nano`), `opencode/gpt5-nano`.
151195

‎README.md‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,49 @@ curl -X POST http://127.0.0.1:10000/v1/chat/completions \
104104

105105
模型决定调用工具时,非流式响应返回 `message.tool_calls`,流式响应返回 `delta.tool_calls`。
106106

107+
### Anthropic Messages API(/v1/messages)
108+
109+
面向 Claude SDK / Anthropic 客户端的端点。认证使用 `x-api-key` header(`Authorization: Bearer` 亦可用),模型决定调用工具时返回 `tool_use` 内容块。
110+
111+
```bash
112+
curl -X POST http://127.0.0.1:10000/v1/messages \
113+
-H "x-api-key: ***" \
114+
-H "anthropic-version: 2023-06-01" \
115+
-H "Content-Type: application/json" \
116+
-d '{
117+
"model": "opencode/big-pickle",
118+
"max_tokens": 1024,
119+
"system": "You are concise.",
120+
"messages": [{"role": "user", "content": "你好!"}],
121+
"stream": true
122+
}'
123+
```
124+
125+
工具往返(工具结果以 `tool_result` 块回传):
126+
127+
```bash
128+
curl -X POST http://127.0.0.1:10000/v1/messages \
129+
-H "x-api-key: ***" \
130+
-H "anthropic-version: 2023-06-01" \
131+
-H "Content-Type: application/json" \
132+
-d '{
133+
"model": "opencode/big-pickle",
134+
"max_tokens": 1024,
135+
"messages": [{"role": "user", "content": "帮我获取 https://example.com 的标题"}],
136+
"tools": [{
137+
"name": "web_fetch",
138+
"description": "Fetch a URL and return its content summary",
139+
"input_schema": {
140+
"type": "object",
141+
"properties": {"url": {"type": "string"}},
142+
"required": ["url"]
143+
}
144+
}]
145+
}'
146+
```
147+
148+
响应包含 `{"type":"tool_use","id":"…","name":"web_fetch","input":{…}}` 内容块(`stop_reason: "tool_use"`);把工具结果作为 `{"type":"tool_result","tool_use_id":"…","content":"…"}` 用户块发回即可继续对话。`stream: true` 时按 Anthropic SSE 事件序输出 `message_start` → `content_block_*` → `message_delta` → `message_stop`。
149+
107150
## ⚙️ 配置
108151

109152
| 环境变量 | 默认值 | 说明 |
@@ -146,6 +189,7 @@ OPENCODE_PROXY_AUTO_CLEANUP_CONVERSATIONS=true
146189
| `GET` | `/v1/models` | 模型列表 |
147190
| `POST` | `/v1/chat/completions` | Chat Completions |
148191
| `POST` | `/v1/responses` | Responses API |
192+
| `POST` | `/v1/messages` | Anthropic Messages API(兼容 Claude SDK) |
149193

150194
模型名称写法:`opencode/big-pickle`、`gpt5-nano`(自动解析为 `gpt-5-nano`)、`opencode/gpt5-nano`。
151195

0 commit comments

Comments
 (0)