# OpenAI-to-Anthropic Proxy Design (proxx)

## Purpose

A Go proxy that exposes OpenAI-compatible API endpoints, converts requests to Anthropic format, and forwards them to a configurable upstream Anthropic endpoint. The proxy mimics claude-code (the CLI tool) so upstream sees requests as originating from claude-code.

## Architecture

```
Client (OpenAI format) --> proxx (Go :8080) --> Upstream Anthropic endpoint
                                                     e.g. https://api.z.ai/api/anthropic
```

Single binary using Go standard library `net/http`. Only external dependency: `gopkg.in/yaml.v3` for config parsing.

## Configuration

Config loaded from `config.yaml` in the working directory:

```yaml
port: 8080
upstream_url: "https://api.z.ai/api/anthropic"
```

## Endpoints

### POST /v1/chat/completions

Accepts OpenAI chat completion format. Converts to Anthropic `POST /v1/messages`. Supports both streaming and non-streaming modes.

### GET /v1/models

Returns a static list of Claude models in OpenAI `/v1/models` response format. No upstream call needed.

## Authentication

The API key is extracted from the incoming request's `Authorization: Bearer <key>` header and forwarded to the upstream as the `x-api-key` header.

## Claude-Code Mimicry

The proxy sets these headers on all upstream requests:

| Header | Value |
|---|---|
| `User-Agent` | `claude-cli/1.0.18 (pro, cli)` |
| `x-api-key` | From incoming Bearer token |
| `x-app` | `cli` |
| `anthropic-version` | `2023-06-01` |
| `anthropic-beta` | `interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05,context-management-2025-06-27` |
| `X-Claude-Code-Session-Id` | Random UUID generated at startup |
| `content-type` | `application/json` |

## Request Conversion (OpenAI -> Anthropic)

### Fields mapped directly
- `model` -> `model`
- `temperature` -> `temperature`
- `max_tokens` -> `max_tokens` (default 32000 if omitted)
- `stream` -> `stream`
- `top_p` -> `top_p`
- `stop` -> `stop_sequences`

### Messages
- OpenAI `messages[]` with `role` and `content` mapped to Anthropic `messages[]`
- `system` message (role="system") extracted to top-level `system` field
- String content mapped directly; array content (text+image parts) mapped to Anthropic content block format

### Tools
- OpenAI function tools converted to Anthropic tool format (`name`, `description`, `input_schema`)
- `tool_choice` mapped: `auto`/`none`/`required` + specific tool selection

## Response Conversion (Anthropic -> OpenAI)

### Non-streaming
- `content[0].text` -> `choices[0].message.content`
- `content[].type == "tool_use"` -> `choices[0].message.tool_calls[]`
- `usage.input_tokens` -> `usage.prompt_tokens`
- `usage.output_tokens` -> `usage.completion_tokens`
- `stop_reason` mapped: `end_turn` -> `stop`, `tool_use` -> `tool_calls`, `max_tokens` -> `length`

### Streaming
- Anthropic SSE events (`message_start`, `content_block_start`, `content_block_delta`, `content_block_stop`, `message_delta`, `message_stop`) converted to OpenAI SSE format (`role`, `content` deltas, `tool_calls` deltas, `finish_reason`)
- Final `data: [DONE]` sent after stream ends

### Error Responses
- Upstream errors converted to OpenAI error format: `{"error": {"message": "...", "type": "...", "code": "..."}}`
- Connection failures: HTTP 502
- Malformed requests: HTTP 400

## Project Structure

```
proxx/
├── main.go           # Entry point, config loading, HTTP server setup
├── handler.go        # HTTP handlers for /v1/chat/completions and /v1/models
├── converter.go      # OpenAI <-> Anthropic format conversion logic
├── types.go          # All request/response struct types
├── streaming.go      # SSE streaming conversion (Anthropic -> OpenAI)
├── config.yaml       # Default configuration file
├── go.mod
└── go.sum
```

## Testing

- Unit tests for the converter functions (pure logic, no HTTP)
- Integration test with a mock upstream server to verify end-to-end flow
- Streaming test to verify SSE event conversion
