feat: 更新端口配置,将所有相关文件中的端口从 8080 修改为 7860
Browse files- .env.example +1 -1
- CLAUDE.md +2 -2
- Dockerfile +1 -1
- README.md +9 -9
- claude_proxy.sh +1 -1
- main.go +34 -34
- test.sh +3 -3
.env.example
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
# Copy this file to .env and set your own values
|
| 3 |
|
| 4 |
# Server configuration
|
| 5 |
-
PORT=
|
| 6 |
|
| 7 |
|
| 8 |
# Debug mode (set to "true" to enable debug logging)
|
|
|
|
| 2 |
# Copy this file to .env and set your own values
|
| 3 |
|
| 4 |
# Server configuration
|
| 5 |
+
PORT=7860
|
| 6 |
|
| 7 |
|
| 8 |
# Debug mode (set to "true" to enable debug logging)
|
CLAUDE.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
| 21 |
|
| 22 |
```bash
|
| 23 |
# 服务器配置
|
| 24 |
-
PORT=
|
| 25 |
DEBUG=true # 开启调试模式
|
| 26 |
|
| 27 |
# CORS 配置
|
|
@@ -86,7 +86,7 @@ go run main.go
|
|
| 86 |
|
| 87 |
### 健康检查
|
| 88 |
```bash
|
| 89 |
-
curl http://localhost:
|
| 90 |
|
| 91 |
# 预期响应
|
| 92 |
{
|
|
|
|
| 21 |
|
| 22 |
```bash
|
| 23 |
# 服务器配置
|
| 24 |
+
PORT=7860 # 服务器端口
|
| 25 |
DEBUG=true # 开启调试模式
|
| 26 |
|
| 27 |
# CORS 配置
|
|
|
|
| 86 |
|
| 87 |
### 健康检查
|
| 88 |
```bash
|
| 89 |
+
curl http://localhost:7860/
|
| 90 |
|
| 91 |
# 预期响应
|
| 92 |
{
|
Dockerfile
CHANGED
|
@@ -17,6 +17,6 @@ WORKDIR /root/
|
|
| 17 |
|
| 18 |
COPY --from=builder /app/main .
|
| 19 |
|
| 20 |
-
EXPOSE
|
| 21 |
|
| 22 |
CMD ["./main"]
|
|
|
|
| 17 |
|
| 18 |
COPY --from=builder /app/main .
|
| 19 |
|
| 20 |
+
EXPOSE 7860
|
| 21 |
|
| 22 |
CMD ["./main"]
|
README.md
CHANGED
|
@@ -64,7 +64,7 @@ go mod tidy
|
|
| 64 |
go run main.go
|
| 65 |
```
|
| 66 |
|
| 67 |
-
服务器将在`http://localhost:
|
| 68 |
|
| 69 |
### 使用配置脚本
|
| 70 |
|
|
@@ -89,7 +89,7 @@ docker build -t claude-proxy .
|
|
| 89 |
|
| 90 |
2. 运行容器:
|
| 91 |
```bash
|
| 92 |
-
docker run -p
|
| 93 |
-e DEBUG=false \
|
| 94 |
claude-proxy
|
| 95 |
```
|
|
@@ -98,7 +98,7 @@ docker run -p 8080:8080 \
|
|
| 98 |
|
| 99 |
### Google Gemini
|
| 100 |
```bash
|
| 101 |
-
curl -X POST http://localhost:
|
| 102 |
-H 'Content-Type: application/json' \
|
| 103 |
-H 'x-api-key: YOUR_GEMINI_API_KEY' \
|
| 104 |
-d '{
|
|
@@ -110,7 +110,7 @@ curl -X POST http://localhost:8080/https/generativelanguage.googleapis.com/v1bet
|
|
| 110 |
|
| 111 |
### Groq
|
| 112 |
```bash
|
| 113 |
-
curl -X POST http://localhost:
|
| 114 |
-H 'Content-Type: application/json' \
|
| 115 |
-H 'x-api-key: YOUR_GROQ_API_KEY' \
|
| 116 |
-d '{
|
|
@@ -122,7 +122,7 @@ curl -X POST http://localhost:8080/https/api.groq.com/openai/v1/llama3-70b-8192/
|
|
| 122 |
|
| 123 |
### OpenAI
|
| 124 |
```bash
|
| 125 |
-
curl -X POST http://localhost:
|
| 126 |
-H 'Content-Type: application/json' \
|
| 127 |
-H 'x-api-key: YOUR_OPENAI_API_KEY' \
|
| 128 |
-d '{
|
|
@@ -173,7 +173,7 @@ curl -X POST http://localhost:8080/https/api.openai.com/v1/gpt-4/v1/messages \
|
|
| 173 |
|
| 174 |
```bash
|
| 175 |
# 服务器配置
|
| 176 |
-
PORT=
|
| 177 |
DEBUG=true
|
| 178 |
|
| 179 |
# CORS配置(可选)
|
|
@@ -187,7 +187,7 @@ CORS_ALLOW_HEADERS=Accept,Content-Type,Content-Length,Accept-Encoding,X-CSRF-Tok
|
|
| 187 |
设置环境变量后,可以使用简化的URL:
|
| 188 |
```bash
|
| 189 |
# 然后可以使用包含"haiku"的任何路径
|
| 190 |
-
curl -X POST http://localhost:
|
| 191 |
-H 'Content-Type: application/json' \
|
| 192 |
-H 'x-api-key: YOUR_ANTHROPIC_API_KEY' \
|
| 193 |
-d '{...}'
|
|
@@ -213,7 +213,7 @@ curl -X POST http://localhost:8080/haiku/v1/messages \
|
|
| 213 |
|
| 214 |
启动服务器后,访问根路径检查状态:
|
| 215 |
```bash
|
| 216 |
-
curl http://localhost:
|
| 217 |
```
|
| 218 |
|
| 219 |
应该返回:
|
|
@@ -244,7 +244,7 @@ curl http://localhost:8080/
|
|
| 244 |
|
| 245 |
在Hugging Face Space设置中配置以下环境变量:
|
| 246 |
|
| 247 |
-
- `PORT`: 服务器端口(默认
|
| 248 |
- `DEBUG`: 调试模式(true/false)
|
| 249 |
|
| 250 |
## 🤝 贡献
|
|
|
|
| 64 |
go run main.go
|
| 65 |
```
|
| 66 |
|
| 67 |
+
服务器将在`http://localhost:7860`启动。
|
| 68 |
|
| 69 |
### 使用配置脚本
|
| 70 |
|
|
|
|
| 89 |
|
| 90 |
2. 运行容器:
|
| 91 |
```bash
|
| 92 |
+
docker run -p 7860:7860 \
|
| 93 |
-e DEBUG=false \
|
| 94 |
claude-proxy
|
| 95 |
```
|
|
|
|
| 98 |
|
| 99 |
### Google Gemini
|
| 100 |
```bash
|
| 101 |
+
curl -X POST http://localhost:7860/https/generativelanguage.googleapis.com/v1beta/gemini-1.5-pro/v1/messages \
|
| 102 |
-H 'Content-Type: application/json' \
|
| 103 |
-H 'x-api-key: YOUR_GEMINI_API_KEY' \
|
| 104 |
-d '{
|
|
|
|
| 110 |
|
| 111 |
### Groq
|
| 112 |
```bash
|
| 113 |
+
curl -X POST http://localhost:7860/https/api.groq.com/openai/v1/llama3-70b-8192/v1/messages \
|
| 114 |
-H 'Content-Type: application/json' \
|
| 115 |
-H 'x-api-key: YOUR_GROQ_API_KEY' \
|
| 116 |
-d '{
|
|
|
|
| 122 |
|
| 123 |
### OpenAI
|
| 124 |
```bash
|
| 125 |
+
curl -X POST http://localhost:7860/https/api.openai.com/v1/gpt-4/v1/messages \
|
| 126 |
-H 'Content-Type: application/json' \
|
| 127 |
-H 'x-api-key: YOUR_OPENAI_API_KEY' \
|
| 128 |
-d '{
|
|
|
|
| 173 |
|
| 174 |
```bash
|
| 175 |
# 服务器配置
|
| 176 |
+
PORT=7860
|
| 177 |
DEBUG=true
|
| 178 |
|
| 179 |
# CORS配置(可选)
|
|
|
|
| 187 |
设置环境变量后,可以使用简化的URL:
|
| 188 |
```bash
|
| 189 |
# 然后可以使用包含"haiku"的任何路径
|
| 190 |
+
curl -X POST http://localhost:7860/haiku/v1/messages \
|
| 191 |
-H 'Content-Type: application/json' \
|
| 192 |
-H 'x-api-key: YOUR_ANTHROPIC_API_KEY' \
|
| 193 |
-d '{...}'
|
|
|
|
| 213 |
|
| 214 |
启动服务器后,访问根路径检查状态:
|
| 215 |
```bash
|
| 216 |
+
curl http://localhost:7860/
|
| 217 |
```
|
| 218 |
|
| 219 |
应该返回:
|
|
|
|
| 244 |
|
| 245 |
在Hugging Face Space设置中配置以下环境变量:
|
| 246 |
|
| 247 |
+
- `PORT`: 服务器端口(默认7860)
|
| 248 |
- `DEBUG`: 调试模式(true/false)
|
| 249 |
|
| 250 |
## 🤝 贡献
|
claude_proxy.sh
CHANGED
|
@@ -144,7 +144,7 @@ get_api_key() {
|
|
| 144 |
# Function to get proxy URL
|
| 145 |
get_proxy_url() {
|
| 146 |
echo ""
|
| 147 |
-
get_input "Enter your proxy server URL" "http://localhost:
|
| 148 |
}
|
| 149 |
|
| 150 |
# Function to get model name
|
|
|
|
| 144 |
# Function to get proxy URL
|
| 145 |
get_proxy_url() {
|
| 146 |
echo ""
|
| 147 |
+
get_input "Enter your proxy server URL" "http://localhost:7860" "PROXY_URL"
|
| 148 |
}
|
| 149 |
|
| 150 |
# Function to get model name
|
main.go
CHANGED
|
@@ -58,8 +58,8 @@ type OpenAIRequest struct {
|
|
| 58 |
}
|
| 59 |
|
| 60 |
type OpenAITool struct {
|
| 61 |
-
Type string
|
| 62 |
-
Function OpenAIToolFunction
|
| 63 |
}
|
| 64 |
|
| 65 |
type OpenAIToolFunction struct {
|
|
@@ -118,7 +118,7 @@ func main() {
|
|
| 118 |
|
| 119 |
port := os.Getenv("PORT")
|
| 120 |
if port == "" {
|
| 121 |
-
port = "
|
| 122 |
}
|
| 123 |
|
| 124 |
// Set debug mode based on environment variable
|
|
@@ -129,41 +129,41 @@ func main() {
|
|
| 129 |
}
|
| 130 |
|
| 131 |
router := gin.Default()
|
| 132 |
-
|
| 133 |
// CORS configuration from environment or defaults
|
| 134 |
corsOrigin := os.Getenv("CORS_ALLOW_ORIGIN")
|
| 135 |
if corsOrigin == "" {
|
| 136 |
corsOrigin = "*"
|
| 137 |
}
|
| 138 |
-
|
| 139 |
corsMethods := os.Getenv("CORS_ALLOW_METHODS")
|
| 140 |
if corsMethods == "" {
|
| 141 |
corsMethods = "POST, GET, OPTIONS, PUT, DELETE"
|
| 142 |
}
|
| 143 |
-
|
| 144 |
corsHeaders := os.Getenv("CORS_ALLOW_HEADERS")
|
| 145 |
if corsHeaders == "" {
|
| 146 |
corsHeaders = "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, x-api-key, anthropic-version"
|
| 147 |
}
|
| 148 |
-
|
| 149 |
router.Use(func(c *gin.Context) {
|
| 150 |
c.Header("Access-Control-Allow-Origin", corsOrigin)
|
| 151 |
c.Header("Access-Control-Allow-Methods", corsMethods)
|
| 152 |
c.Header("Access-Control-Allow-Headers", corsHeaders)
|
| 153 |
-
|
| 154 |
if c.Request.Method == "OPTIONS" {
|
| 155 |
c.AbortWithStatus(204)
|
| 156 |
return
|
| 157 |
}
|
| 158 |
-
|
| 159 |
c.Next()
|
| 160 |
})
|
| 161 |
|
| 162 |
router.POST("/*path", handleProxy)
|
| 163 |
router.GET("/", func(c *gin.Context) {
|
| 164 |
c.JSON(200, gin.H{
|
| 165 |
-
"message":
|
| 166 |
-
"version":
|
| 167 |
"environment": gin.Mode(),
|
| 168 |
})
|
| 169 |
})
|
|
@@ -174,7 +174,7 @@ func main() {
|
|
| 174 |
|
| 175 |
func handleProxy(c *gin.Context) {
|
| 176 |
path := c.Param("path")
|
| 177 |
-
|
| 178 |
if !strings.HasSuffix(path, "/v1/messages") {
|
| 179 |
c.JSON(404, gin.H{"error": "Not found"})
|
| 180 |
return
|
|
@@ -203,7 +203,7 @@ func handleProxy(c *gin.Context) {
|
|
| 203 |
log.Printf("=== End Claude Request Debug ===")
|
| 204 |
|
| 205 |
openaiReq := convertClaudeToOpenAI(claudeReq, config.Model)
|
| 206 |
-
|
| 207 |
// Debug: Print all incoming headers
|
| 208 |
log.Printf("=== Incoming Headers Debug ===")
|
| 209 |
for key, values := range c.Request.Header {
|
|
@@ -212,7 +212,7 @@ func handleProxy(c *gin.Context) {
|
|
| 212 |
}
|
| 213 |
}
|
| 214 |
log.Printf("=== End Headers Debug ===")
|
| 215 |
-
|
| 216 |
apiKey := c.GetHeader("x-api-key")
|
| 217 |
if apiKey == "" {
|
| 218 |
// Try alternative header names that Claude CLI might use
|
|
@@ -227,11 +227,11 @@ func handleProxy(c *gin.Context) {
|
|
| 227 |
if apiKey == "" {
|
| 228 |
apiKey = c.GetHeader("anthropic-version")
|
| 229 |
}
|
| 230 |
-
|
| 231 |
log.Printf("=== API Key Debug ===")
|
| 232 |
log.Printf("Final API Key: %s", apiKey)
|
| 233 |
log.Printf("=== End API Key Debug ===")
|
| 234 |
-
|
| 235 |
if apiKey == "" {
|
| 236 |
c.JSON(401, gin.H{"error": "API key required"})
|
| 237 |
return
|
|
@@ -246,7 +246,7 @@ func handleProxy(c *gin.Context) {
|
|
| 246 |
|
| 247 |
func parseProxyConfig(path string) (*ProxyConfig, error) {
|
| 248 |
log.Printf("Debug: Parsing path: %s", path)
|
| 249 |
-
|
| 250 |
// Remove query parameters from path before matching
|
| 251 |
pathWithoutQuery := strings.Split(path, "?")[0]
|
| 252 |
log.Printf("Debug: Path without query: %s", pathWithoutQuery)
|
|
@@ -254,51 +254,51 @@ func parseProxyConfig(path string) (*ProxyConfig, error) {
|
|
| 254 |
// Support both formats:
|
| 255 |
// 1. /protocol/domain/path/haiku_model/model_name/v1/messages (without colon)
|
| 256 |
// 2. /protocol://domain/path/haiku_model/model_name/v1/messages (with colon)
|
| 257 |
-
|
| 258 |
// First try format with colon: /https://domain/path/haiku_model/model_name/v1/messages
|
| 259 |
reWithColon := regexp.MustCompile(`^/([^:]+)://([^/]+)(/.*?)/([^/]+)/([^/]+)/v1/messages$`)
|
| 260 |
matches := reWithColon.FindStringSubmatch(pathWithoutQuery)
|
| 261 |
-
|
| 262 |
if len(matches) == 6 {
|
| 263 |
protocol := matches[1]
|
| 264 |
domain := matches[2]
|
| 265 |
urlPath := matches[3]
|
| 266 |
haikuModel := matches[4]
|
| 267 |
modelName := matches[5]
|
| 268 |
-
|
| 269 |
baseURL := fmt.Sprintf("%s://%s%s", protocol, domain, urlPath)
|
| 270 |
-
|
| 271 |
log.Printf("Debug: Parsed (with colon) - protocol: %s, domain: %s, urlPath: %s, haikuModel: %s, modelName: %s", protocol, domain, urlPath, haikuModel, modelName)
|
| 272 |
log.Printf("Debug: Final baseURL: %s", baseURL)
|
| 273 |
-
|
| 274 |
return &ProxyConfig{
|
| 275 |
BaseURL: baseURL,
|
| 276 |
Model: modelName,
|
| 277 |
}, nil
|
| 278 |
}
|
| 279 |
-
|
| 280 |
// Then try format without colon: /https/domain/path/haiku_model/model_name/v1/messages
|
| 281 |
reWithoutColon := regexp.MustCompile(`^/([^/]+)/([^/]+)(/.*?)/([^/]+)/([^/]+)/v1/messages$`)
|
| 282 |
matches = reWithoutColon.FindStringSubmatch(pathWithoutQuery)
|
| 283 |
-
|
| 284 |
if len(matches) == 6 {
|
| 285 |
protocol := matches[1]
|
| 286 |
domain := matches[2]
|
| 287 |
urlPath := matches[3]
|
| 288 |
haikuModel := matches[4]
|
| 289 |
modelName := matches[5]
|
| 290 |
-
|
| 291 |
baseURL := fmt.Sprintf("%s://%s%s", protocol, domain, urlPath)
|
| 292 |
-
|
| 293 |
log.Printf("Debug: Parsed (without colon) - protocol: %s, domain: %s, urlPath: %s, haikuModel: %s, modelName: %s", protocol, domain, urlPath, haikuModel, modelName)
|
| 294 |
log.Printf("Debug: Final baseURL: %s", baseURL)
|
| 295 |
-
|
| 296 |
return &ProxyConfig{
|
| 297 |
BaseURL: baseURL,
|
| 298 |
Model: modelName,
|
| 299 |
}, nil
|
| 300 |
}
|
| 301 |
-
|
| 302 |
return nil, fmt.Errorf("invalid path format. Expected: /protocol/domain/path/haiku_model/model_name/v1/messages or /protocol://domain/path/haiku_model/model_name/v1/messages, got: %s", pathWithoutQuery)
|
| 303 |
}
|
| 304 |
|
|
@@ -523,7 +523,7 @@ func handleStreamingRequest(c *gin.Context, openaiReq OpenAIRequest, config *Pro
|
|
| 523 |
|
| 524 |
reader := resp.Body
|
| 525 |
buffer := make([]byte, 1024)
|
| 526 |
-
|
| 527 |
for {
|
| 528 |
n, err := reader.Read(buffer)
|
| 529 |
if err != nil {
|
|
@@ -536,7 +536,7 @@ func handleStreamingRequest(c *gin.Context, openaiReq OpenAIRequest, config *Pro
|
|
| 536 |
|
| 537 |
data := string(buffer[:n])
|
| 538 |
lines := strings.Split(data, "\n")
|
| 539 |
-
|
| 540 |
for _, line := range lines {
|
| 541 |
if strings.HasPrefix(line, "data: ") {
|
| 542 |
eventData := strings.TrimPrefix(line, "data: ")
|
|
@@ -545,7 +545,7 @@ func handleStreamingRequest(c *gin.Context, openaiReq OpenAIRequest, config *Pro
|
|
| 545 |
flusher.Flush()
|
| 546 |
return
|
| 547 |
}
|
| 548 |
-
|
| 549 |
convertedData := convertStreamingData(eventData)
|
| 550 |
if convertedData != "" {
|
| 551 |
c.Writer.WriteString("data: " + convertedData + "\n\n")
|
|
@@ -567,7 +567,7 @@ func convertStreamingData(data string) string {
|
|
| 567 |
}
|
| 568 |
|
| 569 |
claudeChunk := map[string]interface{}{
|
| 570 |
-
"type":
|
| 571 |
"index": 0,
|
| 572 |
"delta": map[string]interface{}{
|
| 573 |
"type": "text_delta",
|
|
@@ -607,7 +607,7 @@ func convertOpenAIToClaude(openaiResp OpenAIResponse) ClaudeResponse {
|
|
| 607 |
Text: choice.Message.Content,
|
| 608 |
},
|
| 609 |
}
|
| 610 |
-
|
| 611 |
switch choice.FinishReason {
|
| 612 |
case "stop":
|
| 613 |
claudeResp.StopReason = "end_turn"
|
|
@@ -622,4 +622,4 @@ func convertOpenAIToClaude(openaiResp OpenAIResponse) ClaudeResponse {
|
|
| 622 |
claudeResp.Usage.OutputTokens = openaiResp.Usage.CompletionTokens
|
| 623 |
|
| 624 |
return claudeResp
|
| 625 |
-
}
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
type OpenAITool struct {
|
| 61 |
+
Type string `json:"type"`
|
| 62 |
+
Function OpenAIToolFunction `json:"function"`
|
| 63 |
}
|
| 64 |
|
| 65 |
type OpenAIToolFunction struct {
|
|
|
|
| 118 |
|
| 119 |
port := os.Getenv("PORT")
|
| 120 |
if port == "" {
|
| 121 |
+
port = "7860"
|
| 122 |
}
|
| 123 |
|
| 124 |
// Set debug mode based on environment variable
|
|
|
|
| 129 |
}
|
| 130 |
|
| 131 |
router := gin.Default()
|
| 132 |
+
|
| 133 |
// CORS configuration from environment or defaults
|
| 134 |
corsOrigin := os.Getenv("CORS_ALLOW_ORIGIN")
|
| 135 |
if corsOrigin == "" {
|
| 136 |
corsOrigin = "*"
|
| 137 |
}
|
| 138 |
+
|
| 139 |
corsMethods := os.Getenv("CORS_ALLOW_METHODS")
|
| 140 |
if corsMethods == "" {
|
| 141 |
corsMethods = "POST, GET, OPTIONS, PUT, DELETE"
|
| 142 |
}
|
| 143 |
+
|
| 144 |
corsHeaders := os.Getenv("CORS_ALLOW_HEADERS")
|
| 145 |
if corsHeaders == "" {
|
| 146 |
corsHeaders = "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, x-api-key, anthropic-version"
|
| 147 |
}
|
| 148 |
+
|
| 149 |
router.Use(func(c *gin.Context) {
|
| 150 |
c.Header("Access-Control-Allow-Origin", corsOrigin)
|
| 151 |
c.Header("Access-Control-Allow-Methods", corsMethods)
|
| 152 |
c.Header("Access-Control-Allow-Headers", corsHeaders)
|
| 153 |
+
|
| 154 |
if c.Request.Method == "OPTIONS" {
|
| 155 |
c.AbortWithStatus(204)
|
| 156 |
return
|
| 157 |
}
|
| 158 |
+
|
| 159 |
c.Next()
|
| 160 |
})
|
| 161 |
|
| 162 |
router.POST("/*path", handleProxy)
|
| 163 |
router.GET("/", func(c *gin.Context) {
|
| 164 |
c.JSON(200, gin.H{
|
| 165 |
+
"message": "Claude to OpenAI API Proxy",
|
| 166 |
+
"version": "1.0.0",
|
| 167 |
"environment": gin.Mode(),
|
| 168 |
})
|
| 169 |
})
|
|
|
|
| 174 |
|
| 175 |
func handleProxy(c *gin.Context) {
|
| 176 |
path := c.Param("path")
|
| 177 |
+
|
| 178 |
if !strings.HasSuffix(path, "/v1/messages") {
|
| 179 |
c.JSON(404, gin.H{"error": "Not found"})
|
| 180 |
return
|
|
|
|
| 203 |
log.Printf("=== End Claude Request Debug ===")
|
| 204 |
|
| 205 |
openaiReq := convertClaudeToOpenAI(claudeReq, config.Model)
|
| 206 |
+
|
| 207 |
// Debug: Print all incoming headers
|
| 208 |
log.Printf("=== Incoming Headers Debug ===")
|
| 209 |
for key, values := range c.Request.Header {
|
|
|
|
| 212 |
}
|
| 213 |
}
|
| 214 |
log.Printf("=== End Headers Debug ===")
|
| 215 |
+
|
| 216 |
apiKey := c.GetHeader("x-api-key")
|
| 217 |
if apiKey == "" {
|
| 218 |
// Try alternative header names that Claude CLI might use
|
|
|
|
| 227 |
if apiKey == "" {
|
| 228 |
apiKey = c.GetHeader("anthropic-version")
|
| 229 |
}
|
| 230 |
+
|
| 231 |
log.Printf("=== API Key Debug ===")
|
| 232 |
log.Printf("Final API Key: %s", apiKey)
|
| 233 |
log.Printf("=== End API Key Debug ===")
|
| 234 |
+
|
| 235 |
if apiKey == "" {
|
| 236 |
c.JSON(401, gin.H{"error": "API key required"})
|
| 237 |
return
|
|
|
|
| 246 |
|
| 247 |
func parseProxyConfig(path string) (*ProxyConfig, error) {
|
| 248 |
log.Printf("Debug: Parsing path: %s", path)
|
| 249 |
+
|
| 250 |
// Remove query parameters from path before matching
|
| 251 |
pathWithoutQuery := strings.Split(path, "?")[0]
|
| 252 |
log.Printf("Debug: Path without query: %s", pathWithoutQuery)
|
|
|
|
| 254 |
// Support both formats:
|
| 255 |
// 1. /protocol/domain/path/haiku_model/model_name/v1/messages (without colon)
|
| 256 |
// 2. /protocol://domain/path/haiku_model/model_name/v1/messages (with colon)
|
| 257 |
+
|
| 258 |
// First try format with colon: /https://domain/path/haiku_model/model_name/v1/messages
|
| 259 |
reWithColon := regexp.MustCompile(`^/([^:]+)://([^/]+)(/.*?)/([^/]+)/([^/]+)/v1/messages$`)
|
| 260 |
matches := reWithColon.FindStringSubmatch(pathWithoutQuery)
|
| 261 |
+
|
| 262 |
if len(matches) == 6 {
|
| 263 |
protocol := matches[1]
|
| 264 |
domain := matches[2]
|
| 265 |
urlPath := matches[3]
|
| 266 |
haikuModel := matches[4]
|
| 267 |
modelName := matches[5]
|
| 268 |
+
|
| 269 |
baseURL := fmt.Sprintf("%s://%s%s", protocol, domain, urlPath)
|
| 270 |
+
|
| 271 |
log.Printf("Debug: Parsed (with colon) - protocol: %s, domain: %s, urlPath: %s, haikuModel: %s, modelName: %s", protocol, domain, urlPath, haikuModel, modelName)
|
| 272 |
log.Printf("Debug: Final baseURL: %s", baseURL)
|
| 273 |
+
|
| 274 |
return &ProxyConfig{
|
| 275 |
BaseURL: baseURL,
|
| 276 |
Model: modelName,
|
| 277 |
}, nil
|
| 278 |
}
|
| 279 |
+
|
| 280 |
// Then try format without colon: /https/domain/path/haiku_model/model_name/v1/messages
|
| 281 |
reWithoutColon := regexp.MustCompile(`^/([^/]+)/([^/]+)(/.*?)/([^/]+)/([^/]+)/v1/messages$`)
|
| 282 |
matches = reWithoutColon.FindStringSubmatch(pathWithoutQuery)
|
| 283 |
+
|
| 284 |
if len(matches) == 6 {
|
| 285 |
protocol := matches[1]
|
| 286 |
domain := matches[2]
|
| 287 |
urlPath := matches[3]
|
| 288 |
haikuModel := matches[4]
|
| 289 |
modelName := matches[5]
|
| 290 |
+
|
| 291 |
baseURL := fmt.Sprintf("%s://%s%s", protocol, domain, urlPath)
|
| 292 |
+
|
| 293 |
log.Printf("Debug: Parsed (without colon) - protocol: %s, domain: %s, urlPath: %s, haikuModel: %s, modelName: %s", protocol, domain, urlPath, haikuModel, modelName)
|
| 294 |
log.Printf("Debug: Final baseURL: %s", baseURL)
|
| 295 |
+
|
| 296 |
return &ProxyConfig{
|
| 297 |
BaseURL: baseURL,
|
| 298 |
Model: modelName,
|
| 299 |
}, nil
|
| 300 |
}
|
| 301 |
+
|
| 302 |
return nil, fmt.Errorf("invalid path format. Expected: /protocol/domain/path/haiku_model/model_name/v1/messages or /protocol://domain/path/haiku_model/model_name/v1/messages, got: %s", pathWithoutQuery)
|
| 303 |
}
|
| 304 |
|
|
|
|
| 523 |
|
| 524 |
reader := resp.Body
|
| 525 |
buffer := make([]byte, 1024)
|
| 526 |
+
|
| 527 |
for {
|
| 528 |
n, err := reader.Read(buffer)
|
| 529 |
if err != nil {
|
|
|
|
| 536 |
|
| 537 |
data := string(buffer[:n])
|
| 538 |
lines := strings.Split(data, "\n")
|
| 539 |
+
|
| 540 |
for _, line := range lines {
|
| 541 |
if strings.HasPrefix(line, "data: ") {
|
| 542 |
eventData := strings.TrimPrefix(line, "data: ")
|
|
|
|
| 545 |
flusher.Flush()
|
| 546 |
return
|
| 547 |
}
|
| 548 |
+
|
| 549 |
convertedData := convertStreamingData(eventData)
|
| 550 |
if convertedData != "" {
|
| 551 |
c.Writer.WriteString("data: " + convertedData + "\n\n")
|
|
|
|
| 567 |
}
|
| 568 |
|
| 569 |
claudeChunk := map[string]interface{}{
|
| 570 |
+
"type": "content_block_delta",
|
| 571 |
"index": 0,
|
| 572 |
"delta": map[string]interface{}{
|
| 573 |
"type": "text_delta",
|
|
|
|
| 607 |
Text: choice.Message.Content,
|
| 608 |
},
|
| 609 |
}
|
| 610 |
+
|
| 611 |
switch choice.FinishReason {
|
| 612 |
case "stop":
|
| 613 |
claudeResp.StopReason = "end_turn"
|
|
|
|
| 622 |
claudeResp.Usage.OutputTokens = openaiResp.Usage.CompletionTokens
|
| 623 |
|
| 624 |
return claudeResp
|
| 625 |
+
}
|
test.sh
CHANGED
|
@@ -8,7 +8,7 @@ echo "====================================="
|
|
| 8 |
# Test 1: Health check
|
| 9 |
echo ""
|
| 10 |
echo "📊 Test 1: Health check"
|
| 11 |
-
response=$(curl -s http://localhost:
|
| 12 |
echo "Response: $response"
|
| 13 |
|
| 14 |
if echo "$response" | grep -q "Claude to OpenAI API Proxy"; then
|
|
@@ -21,7 +21,7 @@ fi
|
|
| 21 |
# Test 2: Invalid path
|
| 22 |
echo ""
|
| 23 |
echo "📊 Test 2: Invalid path"
|
| 24 |
-
response=$(curl -s -w "%{http_code}" -o /dev/null http://localhost:
|
| 25 |
echo "HTTP Status: $response"
|
| 26 |
|
| 27 |
if [ "$response" = "404" ]; then
|
|
@@ -33,7 +33,7 @@ fi
|
|
| 33 |
# Test 3: Valid path format validation
|
| 34 |
echo ""
|
| 35 |
echo "📊 Test 3: Valid path format validation"
|
| 36 |
-
response=$(curl -s -w "%{http_code}" -o /dev/null -X POST http://localhost:
|
| 37 |
-H 'Content-Type: application/json' \
|
| 38 |
-H 'x-api-key: test-key' \
|
| 39 |
-d '{"model": "gpt-4", "max_tokens": 10, "messages": [{"role": "user", "content": "test"}]}')
|
|
|
|
| 8 |
# Test 1: Health check
|
| 9 |
echo ""
|
| 10 |
echo "📊 Test 1: Health check"
|
| 11 |
+
response=$(curl -s http://localhost:7860/)
|
| 12 |
echo "Response: $response"
|
| 13 |
|
| 14 |
if echo "$response" | grep -q "Claude to OpenAI API Proxy"; then
|
|
|
|
| 21 |
# Test 2: Invalid path
|
| 22 |
echo ""
|
| 23 |
echo "📊 Test 2: Invalid path"
|
| 24 |
+
response=$(curl -s -w "%{http_code}" -o /dev/null http://localhost:7860/invalid/path)
|
| 25 |
echo "HTTP Status: $response"
|
| 26 |
|
| 27 |
if [ "$response" = "404" ]; then
|
|
|
|
| 33 |
# Test 3: Valid path format validation
|
| 34 |
echo ""
|
| 35 |
echo "📊 Test 3: Valid path format validation"
|
| 36 |
+
response=$(curl -s -w "%{http_code}" -o /dev/null -X POST http://localhost:7860/https/api.openai.com/v1/gpt-4/v1/messages \
|
| 37 |
-H 'Content-Type: application/json' \
|
| 38 |
-H 'x-api-key: test-key' \
|
| 39 |
-d '{"model": "gpt-4", "max_tokens": 10, "messages": [{"role": "user", "content": "test"}]}')
|