Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

209 righe
7.7 KiB

  1. // +build ignore
  2. package main
  3. import (
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "net/http"
  8. "os"
  9. "time"
  10. "github.com/QuantumNous/new-api/common"
  11. "github.com/QuantumNous/new-api/middleware"
  12. "github.com/gin-gonic/gin"
  13. )
  14. func main() {
  15. gin.SetMode(gin.ReleaseMode)
  16. middleware.SetCaptureEnabled(1, true)
  17. router := gin.New()
  18. router.Use(gin.Recovery())
  19. // 模拟 TokenAuth
  20. router.Use(func(c *gin.Context) {
  21. auth := c.GetHeader("Authorization")
  22. if auth == "" || auth == "Bearer disabled" {
  23. c.JSON(401, gin.H{"error": gin.H{"message": "Unauthorized"}})
  24. c.Abort()
  25. return
  26. }
  27. c.Set("id", 1)
  28. c.Next()
  29. })
  30. // 模拟 RequestId
  31. router.Use(func(c *gin.Context) {
  32. reqID := c.GetHeader(common.RequestIdKey)
  33. if reqID == "" {
  34. reqID = fmt.Sprintf("req-%d", time.Now().UnixNano())
  35. }
  36. c.Set(common.RequestIdKey, reqID)
  37. c.Next()
  38. })
  39. router.Use(middleware.RelayCaptureMiddleware())
  40. // ═══════════════════════════════════════════════════════
  41. // OpenAI Chat Completions API
  42. // ═══════════════════════════════════════════════════════
  43. router.POST("/v1/chat/completions", func(c *gin.Context) {
  44. stream := c.Query("stream") == "true"
  45. if stream {
  46. c.Header("Content-Type", "text/event-stream")
  47. f := c.Writer.(http.Flusher)
  48. for _, s := range []string{
  49. "data: {\"id\":\"chatcmpl-001\",\"choices\":[{\"delta\":{\"content\":\"Hello \"}}]}\n\n",
  50. "data: {\"id\":\"chatcmpl-001\",\"choices\":[{\"delta\":{\"content\":\"World!\"}}]}\n\n",
  51. "data: [DONE]\n\n",
  52. } {
  53. c.Writer.Write([]byte(s))
  54. f.Flush()
  55. }
  56. return
  57. }
  58. c.JSON(200, gin.H{
  59. "id": "chatcmpl-001",
  60. "object": "chat.completion",
  61. "model": "gpt-4",
  62. "choices": []gin.H{{"index": 0, "message": gin.H{"role": "assistant", "content": "Hello from Chat Completions!"}}},
  63. "usage": gin.H{"prompt_tokens": 5, "completion_tokens": 5, "total_tokens": 10},
  64. })
  65. })
  66. // ═══════════════════════════════════════════════════════
  67. // OpenAI Responses API (new)
  68. // ═══════════════════════════════════════════════════════
  69. router.POST("/v1/responses", func(c *gin.Context) {
  70. c.JSON(200, gin.H{
  71. "id": "resp_abc123",
  72. "object": "response",
  73. "status": "completed",
  74. "output": []gin.H{{
  75. "type": "message",
  76. "role": "assistant",
  77. "content": []gin.H{{
  78. "type": "output_text",
  79. "text": "Hello from Responses API!",
  80. }},
  81. }},
  82. "usage": gin.H{"input_tokens": 8, "output_tokens": 4, "total_tokens": 12},
  83. })
  84. })
  85. // ═══════════════════════════════════════════════════════
  86. // Claude Messages API (Anthropic)
  87. // ═══════════════════════════════════════════════════════
  88. router.POST("/v1/messages", func(c *gin.Context) {
  89. c.JSON(200, gin.H{
  90. "id": "msg_01AbCdEfGhIjKlMnOpQrStUv",
  91. "type": "message",
  92. "role": "assistant",
  93. "model": "claude-sonnet-4-6",
  94. "content": []gin.H{{"type": "text", "text": "Hello! How can I assist you today?"}},
  95. "stop_reason": "end_turn",
  96. "usage": gin.H{"input_tokens": 8, "output_tokens": 12},
  97. })
  98. })
  99. // ═══════════════════════════════════════════════════════
  100. // Gemini Native API (/v1beta/models/*)
  101. // ═══════════════════════════════════════════════════════
  102. router.POST("/v1beta/models/*path", func(c *gin.Context) {
  103. path := c.Param("path")
  104. c.JSON(200, gin.H{
  105. "candidates": []gin.H{{
  106. "content": gin.H{
  107. "role": "model",
  108. "parts": []gin.H{{"text": fmt.Sprintf("Gemini response for: /%s", path)}},
  109. },
  110. "finishReason": "STOP",
  111. }},
  112. "usageMetadata": gin.H{"promptTokenCount": 5, "candidatesTokenCount": 10, "totalTokenCount": 15},
  113. })
  114. })
  115. // ═══════════════════════════════════════════════════════
  116. // Gemini via /v1/models/* (main route)
  117. // ═══════════════════════════════════════════════════════
  118. router.POST("/v1/models/*path", func(c *gin.Context) {
  119. path := c.Param("path")
  120. c.JSON(200, gin.H{
  121. "candidates": []gin.H{{
  122. "content": gin.H{
  123. "role": "model",
  124. "parts": []gin.H{{"text": fmt.Sprintf("Gemini(v1) response for: /%s", path)}},
  125. },
  126. "finishReason": "STOP",
  127. }},
  128. })
  129. })
  130. // ═══════════════════════════════════════════════════════
  131. // Models list
  132. // ═══════════════════════════════════════════════════════
  133. router.GET("/v1/models", func(c *gin.Context) {
  134. c.JSON(200, gin.H{"data": []gin.H{{"id": "gpt-4"}, {"id": "claude-3"}, {"id": "gemini-2.0-flash"}}})
  135. })
  136. // ═══════════════════════════════════════════════════════
  137. // Error endpoint for testing error status codes
  138. // ═══════════════════════════════════════════════════════
  139. router.POST("/v1/error", func(c *gin.Context) {
  140. code := 500
  141. if c := c.Query("code"); c != "" {
  142. fmt.Sscanf(c, "%d", &code)
  143. }
  144. c.JSON(code, gin.H{"error": gin.H{"message": "test error", "type": "test_error"}})
  145. })
  146. // ═══════════════════════════════════════════════════════
  147. // Capture file management endpoints
  148. // ═══════════════════════════════════════════════════════
  149. router.GET("/health", func(c *gin.Context) {
  150. c.JSON(200, gin.H{"status": "ok"})
  151. })
  152. router.GET("/capture-files", func(c *gin.Context) {
  153. today := time.Now().Format("2006-01-02")
  154. dir := fmt.Sprintf("./data/relay-capture/%s", today)
  155. entries, err := os.ReadDir(dir)
  156. if err != nil {
  157. c.JSON(200, gin.H{"files": []string{}, "error": err.Error()})
  158. return
  159. }
  160. var files []string
  161. for _, e := range entries {
  162. files = append(files, e.Name())
  163. }
  164. c.JSON(200, gin.H{"dir": dir, "files": files})
  165. })
  166. router.GET("/capture-files/:name", func(c *gin.Context) {
  167. today := time.Now().Format("2006-01-02")
  168. path := fmt.Sprintf("./data/relay-capture/%s/%s", today, c.Param("name"))
  169. data, err := os.ReadFile(path)
  170. if err != nil {
  171. c.JSON(404, gin.H{"error": err.Error()})
  172. return
  173. }
  174. var record map[string]interface{}
  175. json.Unmarshal(data, &record)
  176. c.JSON(200, record)
  177. })
  178. router.DELETE("/capture-files", func(c *gin.Context) {
  179. os.RemoveAll("./data/relay-capture")
  180. c.JSON(200, gin.H{"status": "cleaned"})
  181. })
  182. port := "18080"
  183. if p := os.Getenv("PORT"); p != "" {
  184. port = p
  185. }
  186. log.Printf("Mock Relay Server on :%s (chat|responses|messages|gemini)", port)
  187. http.ListenAndServe(":"+port, router)
  188. }