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
This commit is contained in:
Franz Kafka 2026-04-15 06:29:03 +00:00
parent 87a74edbf5
commit 8450d96e2e
10 changed files with 1270 additions and 0 deletions

238
types.go Normal file
View file

@ -0,0 +1,238 @@
package main
import "encoding/json"
// OpenAI Types
type ChatCompletionRequest struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
MaxTokens *int `json:"max_tokens,omitempty"`
Stream *bool `json:"stream,omitempty"`
Stop []string `json:"stop,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoices *ToolChoice `json:"tool_choice,omitempty"`
PresencePenalty *float64 `json:"presence_penalty,omitempty"`
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
User string `json:"user,omitempty"`
}
type Message struct {
Role string `json:"role"`
Content interface{} `json:"content"` // string or []ContentPart
Name string `json:"name,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
type ContentPart struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
Source *ImageSource `json:"source,omitempty"`
}
type ImageSource struct {
Type string `json:"type"`
MediaURL string `json:"media_url,omitempty"`
Detail string `json:"detail,omitempty"`
}
type Tool struct {
Type string `json:"type"`
Function Function `json:"function"`
}
type Function struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters map[string]interface{} `json:"parameters,omitempty"`
}
type ToolChoice struct {
Type string `json:"type,omitempty"` // "auto", "none", "required"
Function *FunctionRef `json:"function,omitempty"`
}
type FunctionRef struct {
Name string `json:"name,omitempty"`
}
type Choice struct {
Index int `json:"index"`
Message Message `json:"message"`
FinishReason string `json:"finish_reason,omitempty"`
}
type ChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []Choice `json:"choices"`
Usage Usage `json:"usage"`
}
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
type ToolCall struct {
Index int `json:"index,omitempty"`
ID string `json:"id,omitempty"`
Type string `json:"type,omitempty"`
Function FunctionCall `json:"function,omitempty"`
}
type FunctionCall struct {
Name string `json:"name,omitempty"`
Arguments string `json:"arguments,omitempty"`
}
// Anthropic Types
type AnthropicRequest struct {
Model string `json:"model"`
Messages []AnthropicMessage `json:"messages"`
System string `json:"system,omitempty"`
MaxTokens int `json:"max_tokens"`
Stream bool `json:"stream,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"`
Tools []AnthropicTool `json:"tools,omitempty"`
ToolChoice *AnthropicToolChoice `json:"tool_choice,omitempty"`
}
type AnthropicMessage struct {
Role string `json:"role"`
Content interface{} `json:"content"` // string or []ContentBlock
}
type ContentBlock struct {
Type string `json:"type,omitempty"` // "text", "tool_use", "tool_result"
Text string `json:"text,omitempty"`
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input map[string]interface{} `json:"input,omitempty"`
ToolUseId string `json:"tool_use_id,omitempty"`
Content string `json:"content,omitempty"`
}
type AnthropicTool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema map[string]interface{} `json:"input_schema,omitempty"`
}
type AnthropicToolChoice struct {
Type string `json:"type,omitempty"` // "auto", "none", "any"
Name string `json:"name,omitempty"`
}
type AnthropicResponse struct {
Type string `json:"type,omitempty"`
Id string `json:"id"`
Role string `json:"role,omitempty"`
Model string `json:"model,omitempty"`
Content []ContentBlock `json:"content,omitempty"`
StopReason string `json:"stop_reason,omitempty"`
StopSequence string `json:"stop_sequence,omitempty"`
Usage AnthropicUsage `json:"usage,omitempty"`
}
type AnthropicUsage struct {
InputTokens int `json:"input_tokens,omitempty"`
OutputTokens int `json:"output_tokens,omitempty"`
}
// Streaming Types
type StreamChunk struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []StreamChoice `json:"choices"`
}
type StreamChoice struct {
Index int `json:"index"`
Delta Delta `json:"delta"`
FinishReason string `json:"finish_reason,omitempty"`
}
type Delta struct {
Role string `json:"role,omitempty"`
Content string `json:"content,omitempty"`
ToolCalls []StreamToolCall `json:"tool_calls,omitempty"`
}
type StreamToolCall struct {
Index int `json:"index,omitempty"`
ID string `json:"id,omitempty"`
Type string `json:"type,omitempty"`
Function StreamFunction `json:"function,omitempty"`
}
type StreamFunction struct {
Name string `json:"name,omitempty"`
Arguments string `json:"arguments,omitempty"`
}
// SSE Event Types
type SSEMessage struct {
Type string `json:"type"`
Data json.RawMessage `json:"data,omitempty"`
}
type AnthropicMessageStart struct {
Type string `json:"type"`
Message struct {
Id string `json:"id"`
Role string `json:"role"`
Type string `json:"type"`
} `json:"message"`
}
type AnthropicBlockStart struct {
Type string `json:"type"`
Index int `json:"index"`
ContentBlock struct {
Type string `json:"type"`
Id string `json:"id"`
Name string `json:"name,omitempty"`
} `json:"content_block"`
}
type AnthropicDelta struct {
Type string `json:"type"`
Index int `json:"index"`
Delta struct {
Text string `json:"text,omitempty"`
InputJSONDelta string `json:"input_json_delta,omitempty"`
} `json:"delta"`
}
type AnthropicBlockStop struct {
Type string `json:"type"`
Index int `json:"index"`
}
type AnthropicMessageDelta struct {
Type string `json:"type"`
Delta struct {
StopSequence string `json:"stop_sequence,omitempty"`
} `json:"delta"`
Usage *AnthropicUsage `json:"usage,omitempty"`
StopReason string `json:"stop_reason,omitempty"`
}
type AnthropicMessageStop struct {
Type string `json:"type"`
}