# proxx Implementation Plan ## Goal A working Go binary that proxies OpenAI-format requests to an Anthropic endpoint, with full streaming and non-streaming support. ## Approach Straightforward per the spec. Go 1.21+ with only `gopkg.in/yaml.v3` as external dependency. Standard library `net/http` for everything HTTP-related. ## Steps ### Phase 1: Project Scaffolding - [ ] Initialize `go mod init github.com/penal-colony/proxx` - [ ] Create directory structure: `main.go`, `handler.go`, `converter.go`, `types.go`, `streaming.go`, `config.yaml` - [ ] Add `gopkg.in/yaml.v3` dependency ### Phase 2: Types (`types.go`) - [ ] OpenAI request/response structs (`ChatCompletionRequest`, `ChatCompletionResponse`, `Message`, `Choice`, `Usage`) - [ ] Anthropic request/response structs (`AnthropicRequest`, `AnthropicResponse`, `ContentBlock`, `Message`, `Usage`) - [ ] Tool/function conversion types - [ ] Streaming event structs (Anthropic SSE + OpenAI SSE) ### Phase 3: Converter (`converter.go`) - [ ] `convertRequest()` — map OpenAI req to Anthropic req (model, messages, system, tools, temperature, top_p, max_tokens, stop_sequences) - [ ] `extractSystemMessage()` — pull role=system out of messages into top-level system field - [ ] `convertTools()` — map OpenAI function tools to Anthropic tool format - [ ] `convertResponse()` — map Anthropic non-streaming response to OpenAI format - [ ] `mapStopReason()` — end_turn→stop, tool_use→tool_calls, max_tokens→length ### Phase 4: Streaming (`streaming.go`) - [ ] `StreamConverter` struct holding accumulated state - [ ] `HandleAnthropicStream()` — parse SSE events, convert to OpenAI SSE format - [ ] Handle: `message_start`, `content_block_start`, `content_block_delta`, `content_block_stop`, `message_delta`, `message_stop` - [ ] Map: role, content deltas, tool_use deltas, finish_reason - [ ] Send `data: [DONE]` on stream end ### Phase 5: HTTP Handlers (`handler.go`) - [ ] `handleModels()` — static list of Claude models in OpenAI format - [ ] `handleChatCompletions()` — routing: detect streaming vs non-streaming, call upstream, return converted response - [ ] Extract `Authorization: Bearer` → forward as `x-api-key` - [ ] Set Claude-Code mimicry headers on upstream requests - [ ] Error handling: 400 for bad requests, 502 for upstream failures ### Phase 6: Entry Point (`main.go`) - [ ] Load `config.yaml` via `gopkg.in/yaml.v3` - [ ] Register handlers on `/v1/chat/completions` and `/v1/models` - [ ] Start HTTP server on configured port - [ ] Generate random `X-Claude-Code-Session-Id` UUID at startup ### Phase 7: Config - [ ] Create `config.yaml` with defaults (port 8080, upstream_url) ### Phase 8: Testing - [ ] Unit tests for `converter.go` (pure logic, no HTTP) - [ ] Unit tests for `streaming.go` (test SSE event conversion) - [ ] Integration test with mock upstream ## Risks - **SSE parsing**: Anthropic uses SSE format, need to handle `data:` lines correctly. Risk: low, well-documented format. - **Tool calling conversion**: Complex nested structure mapping. Risk: medium — need to verify edge cases. - **Streaming state machine**: Accumulating partial tool_use blocks across multiple events. Risk: medium — test with actual stream. ## Definition of Done - [ ] `go build` produces a binary with no errors - [ ] Unit tests pass for converter and streaming logic - [ ] Binary starts, loads config, listens on port - [ ] `/v1/models` returns Claude model list in OpenAI format - [ ] Non-streaming `/v1/chat/completions` round-trips correctly through Anthropic upstream - [ ] Streaming `/v1/chat/completions` produces valid OpenAI SSE output