- Add request/response converters (OpenAI <-> Anthropic formats) - Implement SSE streaming conversion (Anthropic events -> OpenAI SSE) - Add /v1/models endpoint with Claude model list - Add /v1/chat/completions endpoint with streaming and non-streaming support - Fix context key type matching bug (sessionIDKey) - Configurable upstream URL via config.yaml - Mimic claude-code CLI headers for upstream requests
3.6 KiB
3.6 KiB
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.v3dependency
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 fieldconvertTools()— map OpenAI function tools to Anthropic tool formatconvertResponse()— map Anthropic non-streaming response to OpenAI formatmapStopReason()— end_turn→stop, tool_use→tool_calls, max_tokens→length
Phase 4: Streaming (streaming.go)
StreamConverterstruct holding accumulated stateHandleAnthropicStream()— 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 formathandleChatCompletions()— routing: detect streaming vs non-streaming, call upstream, return converted response- Extract
Authorization: Bearer→ forward asx-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.yamlviagopkg.in/yaml.v3 - Register handlers on
/v1/chat/completionsand/v1/models - Start HTTP server on configured port
- Generate random
X-Claude-Code-Session-IdUUID at startup
Phase 7: Config
- Create
config.yamlwith 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 buildproduces a binary with no errors- Unit tests pass for converter and streaming logic
- Binary starts, loads config, listens on port
/v1/modelsreturns Claude model list in OpenAI format- Non-streaming
/v1/chat/completionsround-trips correctly through Anthropic upstream - Streaming
/v1/chat/completionsproduces valid OpenAI SSE output