proxx/docs/superpowers/PLAN.md
Franz Kafka 8450d96e2e Implement OpenAI-to-Anthropic proxy with streaming support
- 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
2026-04-15 06:29:03 +00:00

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.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