Skip to content

Commit 0fc6a99

Browse files
committed
update
1 parent 853a577 commit 0fc6a99

File tree

16 files changed

+1385
-47
lines changed

16 files changed

+1385
-47
lines changed

backend/README_CN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ backend/
9292
cp ../.env.example ../.env
9393
```
9494

95+
需要配置数据库文件路径 `DATABASE_PATH` ,并保存目录存在,如:`DATABASE_PATH=./data/api_key_rotator.db`
96+
9597
4. **运行服务**
9698
```bash
9799
go run main.go
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package converters
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
// ResponseConverter interface for format conversion
10+
type ResponseConverter interface {
11+
// Convert transforms a complete (non-streaming) response body
12+
Convert(body []byte) ([]byte, error)
13+
// ConvertStreamChunk transforms a single SSE chunk's JSON payload
14+
ConvertStreamChunk(chunk []byte) ([]byte, error)
15+
// GetContentType returns the content type for the converted response
16+
GetContentType() string
17+
}
18+
19+
// PassthroughConverter returns the response as-is without conversion
20+
type PassthroughConverter struct{}
21+
22+
func (c *PassthroughConverter) Convert(body []byte) ([]byte, error) {
23+
return body, nil
24+
}
25+
26+
func (c *PassthroughConverter) ConvertStreamChunk(chunk []byte) ([]byte, error) {
27+
return chunk, nil
28+
}
29+
30+
func (c *PassthroughConverter) GetContentType() string {
31+
return "application/json"
32+
}
33+
34+
// NewResponseConverter creates a converter based on input and output formats
35+
// inputFormat: the format of the upstream API response (openai_compatible, anthropic_native, gemini_native)
36+
// outputFormat: the desired output format (none, openai, anthropic, gemini)
37+
func NewResponseConverter(inputFormat, outputFormat string) ResponseConverter {
38+
// Normalize input format to match output format naming
39+
normalizedInput := NormalizeFormat(inputFormat)
40+
41+
// If no conversion needed or same format, use passthrough
42+
if outputFormat == "none" || outputFormat == "" || normalizedInput == outputFormat {
43+
return &PassthroughConverter{}
44+
}
45+
46+
// Select appropriate converter based on input → output combination
47+
switch {
48+
case normalizedInput == "openai" && outputFormat == "anthropic":
49+
return &OpenAIToAnthropicConverter{}
50+
case normalizedInput == "openai" && outputFormat == "gemini":
51+
return &OpenAIToGeminiConverter{}
52+
case normalizedInput == "anthropic" && outputFormat == "openai":
53+
return &AnthropicToOpenAIConverter{}
54+
case normalizedInput == "gemini" && outputFormat == "openai":
55+
return &GeminiToOpenAIConverter{}
56+
case normalizedInput == "anthropic" && outputFormat == "gemini":
57+
// anthropic → gemini: chain through openai
58+
return &ChainedConverter{
59+
first: &AnthropicToOpenAIConverter{},
60+
second: &OpenAIToGeminiConverter{},
61+
}
62+
case normalizedInput == "gemini" && outputFormat == "anthropic":
63+
// gemini → anthropic: chain through openai
64+
return &ChainedConverter{
65+
first: &GeminiToOpenAIConverter{},
66+
second: &OpenAIToAnthropicConverter{},
67+
}
68+
default:
69+
// Unsupported conversion, use passthrough
70+
return &PassthroughConverter{}
71+
}
72+
}
73+
74+
// NormalizeFormat converts api_format values to client_format naming convention
75+
func NormalizeFormat(format string) string {
76+
switch format {
77+
case "openai_compatible":
78+
return "openai"
79+
case "anthropic_native":
80+
return "anthropic"
81+
case "gemini_native":
82+
return "gemini"
83+
default:
84+
return format
85+
}
86+
}
87+
88+
// ChainedConverter chains two converters together
89+
type ChainedConverter struct {
90+
first ResponseConverter
91+
second ResponseConverter
92+
}
93+
94+
func (c *ChainedConverter) Convert(body []byte) ([]byte, error) {
95+
intermediate, err := c.first.Convert(body)
96+
if err != nil {
97+
return nil, err
98+
}
99+
return c.second.Convert(intermediate)
100+
}
101+
102+
func (c *ChainedConverter) ConvertStreamChunk(chunk []byte) ([]byte, error) {
103+
intermediate, err := c.first.ConvertStreamChunk(chunk)
104+
if err != nil {
105+
return nil, err
106+
}
107+
return c.second.ConvertStreamChunk(intermediate)
108+
}
109+
110+
func (c *ChainedConverter) GetContentType() string {
111+
return c.second.GetContentType()
112+
}
113+
114+
// Helper function to parse SSE data line
115+
func ParseSSEChunk(data []byte) ([]byte, bool) {
116+
str := strings.TrimSpace(string(data))
117+
if strings.HasPrefix(str, "data: ") {
118+
payload := strings.TrimPrefix(str, "data: ")
119+
if payload == "[DONE]" {
120+
return nil, false
121+
}
122+
return []byte(payload), true
123+
}
124+
return nil, false
125+
}
126+
127+
// Helper function to format SSE data line
128+
func FormatSSEChunk(data []byte) []byte {
129+
return []byte(fmt.Sprintf("data: %s\n\n", string(data)))
130+
}
131+
132+
// Common JSON helper
133+
func parseJSON(data []byte, v interface{}) error {
134+
return json.Unmarshal(data, v)
135+
}
136+
137+
func toJSON(v interface{}) ([]byte, error) {
138+
return json.Marshal(v)
139+
}

0 commit comments

Comments
 (0)