mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
refactor: standardize constant naming and improve file-based auth handling
- Renamed constants from uppercase to CamelCase for consistency. - Replaced redundant file-based auth handling logic with the new `util.CountAuthFiles` helper. - Fixed various error-handling inconsistencies and enhanced robustness in file operations. - Streamlined auth client reload logic in server and watcher components. - Applied minor code readability improvements across multiple packages.
This commit is contained in:
@@ -43,7 +43,7 @@ func NewClaudeCodeAPIHandler(apiHandlers *handlers.BaseAPIHandler) *ClaudeCodeAP
|
|||||||
|
|
||||||
// HandlerType returns the identifier for this handler implementation.
|
// HandlerType returns the identifier for this handler implementation.
|
||||||
func (h *ClaudeCodeAPIHandler) HandlerType() string {
|
func (h *ClaudeCodeAPIHandler) HandlerType() string {
|
||||||
return CLAUDE
|
return Claude
|
||||||
}
|
}
|
||||||
|
|
||||||
// Models returns a list of models supported by this handler.
|
// Models returns a list of models supported by this handler.
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func NewGeminiCLIAPIHandler(apiHandlers *handlers.BaseAPIHandler) *GeminiCLIAPIH
|
|||||||
|
|
||||||
// HandlerType returns the type of this handler.
|
// HandlerType returns the type of this handler.
|
||||||
func (h *GeminiCLIAPIHandler) HandlerType() string {
|
func (h *GeminiCLIAPIHandler) HandlerType() string {
|
||||||
return GEMINICLI
|
return GeminiCLI
|
||||||
}
|
}
|
||||||
|
|
||||||
// Models returns a list of models supported by this handler.
|
// Models returns a list of models supported by this handler.
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func NewGeminiAPIHandler(apiHandlers *handlers.BaseAPIHandler) *GeminiAPIHandler
|
|||||||
|
|
||||||
// HandlerType returns the identifier for this handler implementation.
|
// HandlerType returns the identifier for this handler implementation.
|
||||||
func (h *GeminiAPIHandler) HandlerType() string {
|
func (h *GeminiAPIHandler) HandlerType() string {
|
||||||
return GEMINI
|
return Gemini
|
||||||
}
|
}
|
||||||
|
|
||||||
// Models returns the Gemini-compatible model metadata supported by this handler.
|
// Models returns the Gemini-compatible model metadata supported by this handler.
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ func (h *Handler) UploadAuthFile(c *gin.Context) {
|
|||||||
c.JSON(500, gin.H{"error": fmt.Sprintf("failed to write file: %v", errWrite)})
|
c.JSON(500, gin.H{"error": fmt.Sprintf("failed to write file: %v", errWrite)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.registerAuthFromFile(ctx, dst, data); err != nil {
|
if err = h.registerAuthFromFile(ctx, dst, data); err != nil {
|
||||||
c.JSON(500, gin.H{"error": err.Error()})
|
c.JSON(500, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func NewOpenAIAPIHandler(apiHandlers *handlers.BaseAPIHandler) *OpenAIAPIHandler
|
|||||||
|
|
||||||
// HandlerType returns the identifier for this handler implementation.
|
// HandlerType returns the identifier for this handler implementation.
|
||||||
func (h *OpenAIAPIHandler) HandlerType() string {
|
func (h *OpenAIAPIHandler) HandlerType() string {
|
||||||
return OPENAI
|
return OpenAI
|
||||||
}
|
}
|
||||||
|
|
||||||
// Models returns the OpenAI-compatible model metadata supported by this handler.
|
// Models returns the OpenAI-compatible model metadata supported by this handler.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func NewOpenAIResponsesAPIHandler(apiHandlers *handlers.BaseAPIHandler) *OpenAIR
|
|||||||
|
|
||||||
// HandlerType returns the identifier for this handler implementation.
|
// HandlerType returns the identifier for this handler implementation.
|
||||||
func (h *OpenAIResponsesAPIHandler) HandlerType() string {
|
func (h *OpenAIResponsesAPIHandler) HandlerType() string {
|
||||||
return OPENAI_RESPONSE
|
return OpenaiResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// Models returns the OpenAIResponses-compatible model metadata supported by this handler.
|
// Models returns the OpenAIResponses-compatible model metadata supported by this handler.
|
||||||
@@ -161,6 +161,7 @@ func (h *OpenAIResponsesAPIHandler) forwardResponsesStream(c *gin.Context, flush
|
|||||||
return
|
return
|
||||||
case chunk, ok := <-data:
|
case chunk, ok := <-data:
|
||||||
if !ok {
|
if !ok {
|
||||||
|
_, _ = c.Writer.Write([]byte("\n"))
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
cancel(nil)
|
cancel(nil)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -439,38 +439,14 @@ func (s *Server) UpdateClients(cfg *config.Config) {
|
|||||||
s.mgmt.SetAuthManager(s.handlers.AuthManager)
|
s.mgmt.SetAuthManager(s.handlers.AuthManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count types from AuthManager state + config
|
// Count client sources from configuration and auth directory
|
||||||
authFiles := 0
|
authFiles := util.CountAuthFiles(cfg.AuthDir)
|
||||||
glAPIKeyCount := 0
|
glAPIKeyCount := len(cfg.GlAPIKey)
|
||||||
claudeAPIKeyCount := 0
|
claudeAPIKeyCount := len(cfg.ClaudeKey)
|
||||||
codexAPIKeyCount := 0
|
codexAPIKeyCount := len(cfg.CodexKey)
|
||||||
openAICompatCount := 0
|
openAICompatCount := 0
|
||||||
|
for i := range cfg.OpenAICompatibility {
|
||||||
if s.handlers != nil && s.handlers.AuthManager != nil {
|
openAICompatCount += len(cfg.OpenAICompatibility[i].APIKeys)
|
||||||
for _, a := range s.handlers.AuthManager.List() {
|
|
||||||
if a == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if a.Attributes != nil {
|
|
||||||
if p := a.Attributes["path"]; p != "" {
|
|
||||||
authFiles++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch strings.ToLower(a.Provider) {
|
|
||||||
case "gemini":
|
|
||||||
glAPIKeyCount++
|
|
||||||
case "claude":
|
|
||||||
claudeAPIKeyCount++
|
|
||||||
case "codex":
|
|
||||||
codexAPIKeyCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if cfg != nil {
|
|
||||||
for i := range cfg.OpenAICompatibility {
|
|
||||||
openAICompatCount += len(cfg.OpenAICompatibility[i].APIKeys)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
total := authFiles + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
total := authFiles + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func (c *GeminiClient) Init(timeoutSec float64, autoClose bool, closeDelaySec fl
|
|||||||
|
|
||||||
tr := &http.Transport{}
|
tr := &http.Transport{}
|
||||||
if c.Proxy != "" {
|
if c.Proxy != "" {
|
||||||
if pu, err := url.Parse(c.Proxy); err == nil {
|
if pu, errParse := url.Parse(c.Proxy); errParse == nil {
|
||||||
tr.Proxy = http.ProxyURL(pu)
|
tr.Proxy = http.ProxyURL(pu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -348,7 +348,9 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return empty, &TimeoutError{GeminiError{Msg: "Generate content request timed out."}}
|
return empty, &TimeoutError{GeminiError{Msg: "Generate content request timed out."}}
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer func() {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
if resp.StatusCode == 429 {
|
if resp.StatusCode == 429 {
|
||||||
// Surface 429 as TemporarilyBlocked to match Python behavior
|
// Surface 429 as TemporarilyBlocked to match Python behavior
|
||||||
@@ -368,7 +370,7 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
return empty, &APIError{Msg: "Invalid response data received."}
|
return empty, &APIError{Msg: "Invalid response data received."}
|
||||||
}
|
}
|
||||||
var responseJSON []any
|
var responseJSON []any
|
||||||
if err := json.Unmarshal([]byte(parts[2]), &responseJSON); err != nil {
|
if err = json.Unmarshal([]byte(parts[2]), &responseJSON); err != nil {
|
||||||
c.Close(0)
|
c.Close(0)
|
||||||
return empty, &APIError{Msg: "Invalid response data received."}
|
return empty, &APIError{Msg: "Invalid response data received."}
|
||||||
}
|
}
|
||||||
@@ -388,7 +390,7 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var mainPart []any
|
var mainPart []any
|
||||||
if err := json.Unmarshal([]byte(s), &mainPart); err != nil {
|
if err = json.Unmarshal([]byte(s), &mainPart); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(mainPart) > 4 && mainPart[4] != nil {
|
if len(mainPart) > 4 && mainPart[4] != nil {
|
||||||
@@ -406,7 +408,7 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var top []any
|
var top []any
|
||||||
if err := json.Unmarshal([]byte(line), &top); err != nil {
|
if err = json.Unmarshal([]byte(line), &top); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lastTop = top
|
lastTop = top
|
||||||
@@ -420,7 +422,7 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var mainPart []any
|
var mainPart []any
|
||||||
if err := json.Unmarshal([]byte(s), &mainPart); err != nil {
|
if err = json.Unmarshal([]byte(s), &mainPart); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(mainPart) > 4 && mainPart[4] != nil {
|
if len(mainPart) > 4 && mainPart[4] != nil {
|
||||||
@@ -465,7 +467,7 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
if len(bodyArr) > 1 {
|
if len(bodyArr) > 1 {
|
||||||
if metaArr, ok := bodyArr[1].([]any); ok {
|
if metaArr, ok := bodyArr[1].([]any); ok {
|
||||||
for _, v := range metaArr {
|
for _, v := range metaArr {
|
||||||
if s, ok := v.(string); ok {
|
if s, isOk := v.(string); isOk {
|
||||||
metadata = append(metadata, s)
|
metadata = append(metadata, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,22 +484,22 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
reGen := regexp.MustCompile(`http://googleusercontent\.com/image_generation_content/\d+`)
|
reGen := regexp.MustCompile(`http://googleusercontent\.com/image_generation_content/\d+`)
|
||||||
|
|
||||||
for ci, candAny := range candContainer {
|
for ci, candAny := range candContainer {
|
||||||
cArr, ok := candAny.([]any)
|
cArr, isOk := candAny.([]any)
|
||||||
if !ok {
|
if !isOk {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// text: cArr[1][0]
|
// text: cArr[1][0]
|
||||||
var text string
|
var text string
|
||||||
if len(cArr) > 1 {
|
if len(cArr) > 1 {
|
||||||
if sArr, ok := cArr[1].([]any); ok && len(sArr) > 0 {
|
if sArr, isOk1 := cArr[1].([]any); isOk1 && len(sArr) > 0 {
|
||||||
text, _ = sArr[0].(string)
|
text, _ = sArr[0].(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if reCard.MatchString(text) {
|
if reCard.MatchString(text) {
|
||||||
// candidate[22] and candidate[22][0] or text
|
// candidate[22] and candidate[22][0] or text
|
||||||
if len(cArr) > 22 {
|
if len(cArr) > 22 {
|
||||||
if arr, ok := cArr[22].([]any); ok && len(arr) > 0 {
|
if arr, isOk1 := cArr[22].([]any); isOk1 && len(arr) > 0 {
|
||||||
if s, ok := arr[0].(string); ok {
|
if s, isOk2 := arr[0].(string); isOk2 {
|
||||||
text = s
|
text = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,9 +509,9 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
// thoughts: candidate[37][0][0]
|
// thoughts: candidate[37][0][0]
|
||||||
var thoughts *string
|
var thoughts *string
|
||||||
if len(cArr) > 37 {
|
if len(cArr) > 37 {
|
||||||
if a, ok := cArr[37].([]any); ok && len(a) > 0 {
|
if a, ok1 := cArr[37].([]any); ok1 && len(a) > 0 {
|
||||||
if b, ok := a[0].([]any); ok && len(b) > 0 {
|
if b1, ok2 := a[0].([]any); ok2 && len(b1) > 0 {
|
||||||
if s, ok := b[0].(string); ok {
|
if s, ok3 := b1[0].(string); ok3 {
|
||||||
ss := decodeHTML(s)
|
ss := decodeHTML(s)
|
||||||
thoughts = &ss
|
thoughts = &ss
|
||||||
}
|
}
|
||||||
@@ -518,34 +520,34 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// web images: candidate[12][1]
|
// web images: candidate[12][1]
|
||||||
webImages := []WebImage{}
|
var webImages []WebImage
|
||||||
var imgSection any
|
var imgSection any
|
||||||
if len(cArr) > 12 {
|
if len(cArr) > 12 {
|
||||||
imgSection = cArr[12]
|
imgSection = cArr[12]
|
||||||
}
|
}
|
||||||
if arr, ok := imgSection.([]any); ok && len(arr) > 1 {
|
if arr, ok1 := imgSection.([]any); ok1 && len(arr) > 1 {
|
||||||
if imagesArr, ok := arr[1].([]any); ok {
|
if imagesArr, ok2 := arr[1].([]any); ok2 {
|
||||||
for _, wiAny := range imagesArr {
|
for _, wiAny := range imagesArr {
|
||||||
wiArr, ok := wiAny.([]any)
|
wiArr, ok3 := wiAny.([]any)
|
||||||
if !ok {
|
if !ok3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// url: wiArr[0][0][0], title: wiArr[7][0], alt: wiArr[0][4]
|
// url: wiArr[0][0][0], title: wiArr[7][0], alt: wiArr[0][4]
|
||||||
var urlStr, title, alt string
|
var urlStr, title, alt string
|
||||||
if len(wiArr) > 0 {
|
if len(wiArr) > 0 {
|
||||||
if a, ok := wiArr[0].([]any); ok && len(a) > 0 {
|
if a, ok5 := wiArr[0].([]any); ok5 && len(a) > 0 {
|
||||||
if b, ok := a[0].([]any); ok && len(b) > 0 {
|
if b1, ok6 := a[0].([]any); ok6 && len(b1) > 0 {
|
||||||
urlStr, _ = b[0].(string)
|
urlStr, _ = b1[0].(string)
|
||||||
}
|
}
|
||||||
if len(a) > 4 {
|
if len(a) > 4 {
|
||||||
if s, ok := a[4].(string); ok {
|
if s, ok6 := a[4].(string); ok6 {
|
||||||
alt = s
|
alt = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(wiArr) > 7 {
|
if len(wiArr) > 7 {
|
||||||
if a, ok := wiArr[7].([]any); ok && len(a) > 0 {
|
if a, ok4 := wiArr[7].([]any); ok4 && len(a) > 0 {
|
||||||
title, _ = a[0].(string)
|
title, _ = a[0].(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,10 +557,10 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generated images
|
// generated images
|
||||||
genImages := []GeneratedImage{}
|
var genImages []GeneratedImage
|
||||||
hasGen := false
|
hasGen := false
|
||||||
if arr, ok := imgSection.([]any); ok && len(arr) > 7 {
|
if arr, ok1 := imgSection.([]any); ok1 && len(arr) > 7 {
|
||||||
if a, ok := arr[7].([]any); ok && len(a) > 0 && a[0] != nil {
|
if a, ok2 := arr[7].([]any); ok2 && len(a) > 0 && a[0] != nil {
|
||||||
hasGen = true
|
hasGen = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,23 +569,23 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
var imgBody []any
|
var imgBody []any
|
||||||
for pi := bodyIndex; pi < len(responseJSON); pi++ {
|
for pi := bodyIndex; pi < len(responseJSON); pi++ {
|
||||||
part := responseJSON[pi]
|
part := responseJSON[pi]
|
||||||
arr, ok := part.([]any)
|
arr, ok1 := part.([]any)
|
||||||
if !ok || len(arr) < 3 {
|
if !ok1 || len(arr) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s, ok := arr[2].(string)
|
s, ok1 := arr[2].(string)
|
||||||
if !ok {
|
if !ok1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var mp []any
|
var mp []any
|
||||||
if err := json.Unmarshal([]byte(s), &mp); err != nil {
|
if err = json.Unmarshal([]byte(s), &mp); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(mp) > 4 {
|
if len(mp) > 4 {
|
||||||
if tt, ok := mp[4].([]any); ok && len(tt) > ci {
|
if tt, ok2 := mp[4].([]any); ok2 && len(tt) > ci {
|
||||||
if sec, ok := tt[ci].([]any); ok && len(sec) > 12 {
|
if sec, ok3 := tt[ci].([]any); ok3 && len(sec) > 12 {
|
||||||
if ss, ok := sec[12].([]any); ok && len(ss) > 7 {
|
if ss, ok4 := sec[12].([]any); ok4 && len(ss) > 7 {
|
||||||
if first, ok := ss[7].([]any); ok && len(first) > 0 && first[0] != nil {
|
if first, ok5 := ss[7].([]any); ok5 && len(first) > 0 && first[0] != nil {
|
||||||
imgBody = mp
|
imgBody = mp
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -597,34 +599,34 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
}
|
}
|
||||||
imgCand := imgBody[4].([]any)[ci].([]any)
|
imgCand := imgBody[4].([]any)[ci].([]any)
|
||||||
if len(imgCand) > 1 {
|
if len(imgCand) > 1 {
|
||||||
if a, ok := imgCand[1].([]any); ok && len(a) > 0 {
|
if a, ok1 := imgCand[1].([]any); ok1 && len(a) > 0 {
|
||||||
if s, ok := a[0].(string); ok {
|
if s, ok2 := a[0].(string); ok2 {
|
||||||
text = strings.TrimSpace(reGen.ReplaceAllString(s, ""))
|
text = strings.TrimSpace(reGen.ReplaceAllString(s, ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// images list at imgCand[12][7][0]
|
// images list at imgCand[12][7][0]
|
||||||
if len(imgCand) > 12 {
|
if len(imgCand) > 12 {
|
||||||
if s1, ok := imgCand[12].([]any); ok && len(s1) > 7 {
|
if s1, ok1 := imgCand[12].([]any); ok1 && len(s1) > 7 {
|
||||||
if s2, ok := s1[7].([]any); ok && len(s2) > 0 {
|
if s2, ok2 := s1[7].([]any); ok2 && len(s2) > 0 {
|
||||||
if s3, ok := s2[0].([]any); ok {
|
if s3, ok3 := s2[0].([]any); ok3 {
|
||||||
for ii, giAny := range s3 {
|
for ii, giAny := range s3 {
|
||||||
ga, ok := giAny.([]any)
|
ga, ok4 := giAny.([]any)
|
||||||
if !ok || len(ga) < 4 {
|
if !ok4 || len(ga) < 4 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// url: ga[0][3][3]
|
// url: ga[0][3][3]
|
||||||
var urlStr, title, alt string
|
var urlStr, title, alt string
|
||||||
if a, ok := ga[0].([]any); ok && len(a) > 3 {
|
if a, ok5 := ga[0].([]any); ok5 && len(a) > 3 {
|
||||||
if b, ok := a[3].([]any); ok && len(b) > 3 {
|
if b1, ok6 := a[3].([]any); ok6 && len(b1) > 3 {
|
||||||
urlStr, _ = b[3].(string)
|
urlStr, _ = b1[3].(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// title from ga[3][6]
|
// title from ga[3][6]
|
||||||
if len(ga) > 3 {
|
if len(ga) > 3 {
|
||||||
if a, ok := ga[3].([]any); ok {
|
if a, ok5 := ga[3].([]any); ok5 {
|
||||||
if len(a) > 6 {
|
if len(a) > 6 {
|
||||||
if v, ok := a[6].(float64); ok && v != 0 {
|
if v, ok6 := a[6].(float64); ok6 && v != 0 {
|
||||||
title = fmt.Sprintf("[Generated Image %.0f]", v)
|
title = fmt.Sprintf("[Generated Image %.0f]", v)
|
||||||
} else {
|
} else {
|
||||||
title = "[Generated Image]"
|
title = "[Generated Image]"
|
||||||
@@ -634,13 +636,13 @@ func (c *GeminiClient) generateOnce(prompt string, files []string, model Model,
|
|||||||
}
|
}
|
||||||
// alt from ga[3][5][ii] fallback
|
// alt from ga[3][5][ii] fallback
|
||||||
if len(a) > 5 {
|
if len(a) > 5 {
|
||||||
if tt, ok := a[5].([]any); ok {
|
if tt, ok6 := a[5].([]any); ok6 {
|
||||||
if ii < len(tt) {
|
if ii < len(tt) {
|
||||||
if s, ok := tt[ii].(string); ok {
|
if s, ok7 := tt[ii].(string); ok7 {
|
||||||
alt = s
|
alt = s
|
||||||
}
|
}
|
||||||
} else if len(tt) > 0 {
|
} else if len(tt) > 0 {
|
||||||
if s, ok := tt[0].(string); ok {
|
if s, ok7 := tt[0].(string); ok7 {
|
||||||
alt = s
|
alt = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -709,14 +711,6 @@ func extractErrorCode(top []any) (int, bool) {
|
|||||||
return int(f), true
|
return int(f), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// truncateForLog returns a shortened string for logging
|
|
||||||
func truncateForLog(s string, n int) string {
|
|
||||||
if n <= 0 || len(s) <= n {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartChat returns a ChatSession attached to the client
|
// StartChat returns a ChatSession attached to the client
|
||||||
func (c *GeminiClient) StartChat(model Model, gem *Gem, metadata []string) *ChatSession {
|
func (c *GeminiClient) StartChat(model Model, gem *Gem, metadata []string) *ChatSession {
|
||||||
return &ChatSession{client: c, metadata: normalizeMeta(metadata), model: model, gem: gem, requestedModel: strings.ToLower(model.Name)}
|
return &ChatSession{client: c, metadata: normalizeMeta(metadata), model: model, gem: gem, requestedModel: strings.ToLower(model.Name)}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ func (i Image) Save(path string, filename string, cookies map[string]string, ver
|
|||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
}()
|
}()
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return "", fmt.Errorf("Error downloading image: %d %s", resp.StatusCode, resp.Status)
|
return "", fmt.Errorf("error downloading image: %d %s", resp.StatusCode, resp.Status)
|
||||||
}
|
}
|
||||||
if ct := resp.Header.Get("Content-Type"); ct != "" && !strings.Contains(strings.ToLower(ct), "image") {
|
if ct := resp.Header.Get("Content-Type"); ct != "" && !strings.Contains(strings.ToLower(ct), "image") {
|
||||||
Warning("Content type of %s is not image, but %s.", filename, ct)
|
Warning("Content type of %s is not image, but %s.", filename, ct)
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ func LoadConvStore(path string) (map[string][]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
out := map[string][]string{}
|
out := map[string][]string{}
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
err = db.View(func(tx *bolt.Tx) error {
|
||||||
b := tx.Bucket([]byte("account_meta"))
|
b := tx.Bucket([]byte("account_meta"))
|
||||||
@@ -138,24 +140,26 @@ func SaveConvStore(path string, data map[string][]string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
return db.Update(func(tx *bolt.Tx) error {
|
||||||
// Recreate bucket to reflect the given snapshot exactly.
|
// Recreate bucket to reflect the given snapshot exactly.
|
||||||
if b := tx.Bucket([]byte("account_meta")); b != nil {
|
if b := tx.Bucket([]byte("account_meta")); b != nil {
|
||||||
if err := tx.DeleteBucket([]byte("account_meta")); err != nil {
|
if err = tx.DeleteBucket([]byte("account_meta")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b, err := tx.CreateBucket([]byte("account_meta"))
|
b, errCreateBucket := tx.CreateBucket([]byte("account_meta"))
|
||||||
if err != nil {
|
if errCreateBucket != nil {
|
||||||
return err
|
return errCreateBucket
|
||||||
}
|
}
|
||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
enc, e := json.Marshal(v)
|
enc, e := json.Marshal(v)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
if e := b.Put([]byte(k), enc); e != nil {
|
if e = b.Put([]byte(k), enc); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,7 +181,9 @@ func LoadConvData(path string) (map[string]ConversationRecord, map[string]string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
items := map[string]ConversationRecord{}
|
items := map[string]ConversationRecord{}
|
||||||
index := map[string]string{}
|
index := map[string]string{}
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
err = db.View(func(tx *bolt.Tx) error {
|
||||||
@@ -229,37 +235,39 @@ func SaveConvData(path string, items map[string]ConversationRecord, index map[st
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
return db.Update(func(tx *bolt.Tx) error {
|
return db.Update(func(tx *bolt.Tx) error {
|
||||||
// Recreate items bucket
|
// Recreate items bucket
|
||||||
if b := tx.Bucket([]byte("conv_items")); b != nil {
|
if b := tx.Bucket([]byte("conv_items")); b != nil {
|
||||||
if err := tx.DeleteBucket([]byte("conv_items")); err != nil {
|
if err = tx.DeleteBucket([]byte("conv_items")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bi, err := tx.CreateBucket([]byte("conv_items"))
|
bi, errCreateBucket := tx.CreateBucket([]byte("conv_items"))
|
||||||
if err != nil {
|
if errCreateBucket != nil {
|
||||||
return err
|
return errCreateBucket
|
||||||
}
|
}
|
||||||
for k, rec := range items {
|
for k, rec := range items {
|
||||||
enc, e := json.Marshal(rec)
|
enc, e := json.Marshal(rec)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
if e := bi.Put([]byte(k), enc); e != nil {
|
if e = bi.Put([]byte(k), enc); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recreate index bucket
|
// Recreate index bucket
|
||||||
if b := tx.Bucket([]byte("conv_index")); b != nil {
|
if b := tx.Bucket([]byte("conv_index")); b != nil {
|
||||||
if err := tx.DeleteBucket([]byte("conv_index")); err != nil {
|
if err = tx.DeleteBucket([]byte("conv_index")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bx, err := tx.CreateBucket([]byte("conv_index"))
|
bx, errCreateBucket := tx.CreateBucket([]byte("conv_index"))
|
||||||
if err != nil {
|
if errCreateBucket != nil {
|
||||||
return err
|
return errCreateBucket
|
||||||
}
|
}
|
||||||
for k, v := range index {
|
for k, v := range index {
|
||||||
if e := bx.Put([]byte(k), []byte(v)); e != nil {
|
if e := bx.Put([]byte(k), []byte(v)); e != nil {
|
||||||
|
|||||||
@@ -79,10 +79,6 @@ func SendWithSplit(chat *ChatSession, text string, files []string, cfg *config.C
|
|||||||
useHint = false
|
useHint = false
|
||||||
chunkSize = maxChars
|
chunkSize = maxChars
|
||||||
}
|
}
|
||||||
if chunkSize <= 0 {
|
|
||||||
// As a last resort, split by single rune to avoid exceeding the limit
|
|
||||||
chunkSize = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split into rune-safe chunks
|
// Split into rune-safe chunks
|
||||||
chunks := ChunkByRunes(text, chunkSize)
|
chunks := ChunkByRunes(text, chunkSize)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GEMINI = "gemini"
|
Gemini = "gemini"
|
||||||
GEMINICLI = "gemini-cli"
|
GeminiCLI = "gemini-cli"
|
||||||
GEMINIWEB = "gemini-web"
|
GeminiWeb = "gemini-web"
|
||||||
CODEX = "codex"
|
Codex = "codex"
|
||||||
CLAUDE = "claude"
|
Claude = "claude"
|
||||||
OPENAI = "openai"
|
OpenAI = "openai"
|
||||||
OPENAI_RESPONSE = "openai-response"
|
OpenaiResponse = "openai-response"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
@@ -18,9 +19,11 @@ import (
|
|||||||
|
|
||||||
// ClaudeExecutor is a stateless executor for Anthropic Claude over the messages API.
|
// ClaudeExecutor is a stateless executor for Anthropic Claude over the messages API.
|
||||||
// If api_key is unavailable on auth, it falls back to legacy via ClientAdapter.
|
// If api_key is unavailable on auth, it falls back to legacy via ClientAdapter.
|
||||||
type ClaudeExecutor struct{}
|
type ClaudeExecutor struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
func NewClaudeExecutor() *ClaudeExecutor { return &ClaudeExecutor{} }
|
func NewClaudeExecutor(cfg *config.Config) *ClaudeExecutor { return &ClaudeExecutor{cfg: cfg} }
|
||||||
|
|
||||||
func (e *ClaudeExecutor) Identifier() string { return "claude" }
|
func (e *ClaudeExecutor) Identifier() string { return "claude" }
|
||||||
|
|
||||||
@@ -43,6 +46,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/v1/messages?beta=true", baseURL)
|
url := fmt.Sprintf("%s/v1/messages?beta=true", baseURL)
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -62,12 +66,14 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
}
|
}
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, data)
|
||||||
var param any
|
var param any
|
||||||
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
||||||
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
||||||
@@ -87,6 +93,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
body, _ = sjson.SetRawBytes(body, "system", []byte(misc.ClaudeCodeInstructions))
|
body, _ = sjson.SetRawBytes(body, "system", []byte(misc.ClaudeCodeInstructions))
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/v1/messages?beta=true", baseURL)
|
url := fmt.Sprintf("%s/v1/messages?beta=true", baseURL)
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -107,6 +114,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
out := make(chan cliproxyexecutor.StreamChunk)
|
out := make(chan cliproxyexecutor.StreamChunk)
|
||||||
@@ -119,6 +127,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
var param any
|
var param any
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Bytes()
|
line := scanner.Bytes()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, line)
|
||||||
chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
||||||
for i := range chunks {
|
for i := range chunks {
|
||||||
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
@@ -17,9 +18,11 @@ import (
|
|||||||
|
|
||||||
// CodexExecutor is a stateless executor for Codex (OpenAI Responses API entrypoint).
|
// CodexExecutor is a stateless executor for Codex (OpenAI Responses API entrypoint).
|
||||||
// If api_key is unavailable on auth, it falls back to legacy via ClientAdapter.
|
// If api_key is unavailable on auth, it falls back to legacy via ClientAdapter.
|
||||||
type CodexExecutor struct{}
|
type CodexExecutor struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
func NewCodexExecutor() *CodexExecutor { return &CodexExecutor{} }
|
func NewCodexExecutor(cfg *config.Config) *CodexExecutor { return &CodexExecutor{cfg: cfg} }
|
||||||
|
|
||||||
func (e *CodexExecutor) Identifier() string { return "codex" }
|
func (e *CodexExecutor) Identifier() string { return "codex" }
|
||||||
|
|
||||||
@@ -65,6 +68,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/responses"
|
url := strings.TrimSuffix(baseURL, "/") + "/responses"
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -83,12 +87,14 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
|
|||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
}
|
}
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, data)
|
||||||
var param any
|
var param any
|
||||||
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
||||||
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
||||||
@@ -134,6 +140,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
}
|
}
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/responses"
|
url := strings.TrimSuffix(baseURL, "/") + "/responses"
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -153,6 +160,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
out := make(chan cliproxyexecutor.StreamChunk)
|
out := make(chan cliproxyexecutor.StreamChunk)
|
||||||
@@ -165,6 +173,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
|
|||||||
var param any
|
var param any
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Bytes()
|
line := scanner.Bytes()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, line)
|
||||||
chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
||||||
for i := range chunks {
|
for i := range chunks {
|
||||||
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
@@ -33,9 +34,13 @@ var geminiOauthScopes = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GeminiCLIExecutor talks to the Cloud Code Assist endpoint using OAuth credentials from auth metadata.
|
// GeminiCLIExecutor talks to the Cloud Code Assist endpoint using OAuth credentials from auth metadata.
|
||||||
type GeminiCLIExecutor struct{}
|
type GeminiCLIExecutor struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
func NewGeminiCLIExecutor() *GeminiCLIExecutor { return &GeminiCLIExecutor{} }
|
func NewGeminiCLIExecutor(cfg *config.Config) *GeminiCLIExecutor {
|
||||||
|
return &GeminiCLIExecutor{cfg: cfg}
|
||||||
|
}
|
||||||
|
|
||||||
func (e *GeminiCLIExecutor) Identifier() string { return "gemini-cli" }
|
func (e *GeminiCLIExecutor) Identifier() string { return "gemini-cli" }
|
||||||
|
|
||||||
@@ -91,6 +96,7 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
|
|||||||
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordAPIRequest(ctx, e.cfg, payload)
|
||||||
reqHTTP, errReq := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
|
reqHTTP, errReq := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
|
||||||
if errReq != nil {
|
if errReq != nil {
|
||||||
return cliproxyexecutor.Response{}, errReq
|
return cliproxyexecutor.Response{}, errReq
|
||||||
@@ -105,6 +111,7 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
|
|||||||
}
|
}
|
||||||
data, _ := io.ReadAll(resp.Body)
|
data, _ := io.ReadAll(resp.Body)
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, data)
|
||||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
var param any
|
var param any
|
||||||
out := sdktranslator.TranslateNonStream(respCtx, to, from, attemptModel, bytes.Clone(opts.OriginalRequest), payload, data, ¶m)
|
out := sdktranslator.TranslateNonStream(respCtx, to, from, attemptModel, bytes.Clone(opts.OriginalRequest), payload, data, ¶m)
|
||||||
@@ -117,6 +124,9 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(lastBody) > 0 {
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, lastBody)
|
||||||
|
}
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: lastStatus, msg: string(lastBody)}
|
return cliproxyexecutor.Response{}, statusErr{code: lastStatus, msg: string(lastBody)}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +172,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
|
|||||||
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordAPIRequest(ctx, e.cfg, payload)
|
||||||
reqHTTP, errReq := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
|
reqHTTP, errReq := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
|
||||||
if errReq != nil {
|
if errReq != nil {
|
||||||
return nil, errReq
|
return nil, errReq
|
||||||
@@ -177,6 +188,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
data, _ := io.ReadAll(resp.Body)
|
data, _ := io.ReadAll(resp.Body)
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, data)
|
||||||
lastStatus = resp.StatusCode
|
lastStatus = resp.StatusCode
|
||||||
lastBody = data
|
lastBody = data
|
||||||
if resp.StatusCode == 429 {
|
if resp.StatusCode == 429 {
|
||||||
@@ -196,6 +208,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
|
|||||||
var param any
|
var param any
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Bytes()
|
line := scanner.Bytes()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, line)
|
||||||
if bytes.HasPrefix(line, dataTag) {
|
if bytes.HasPrefix(line, dataTag) {
|
||||||
segments := sdktranslator.TranslateStream(respCtx, to, from, attempt, bytes.Clone(opts.OriginalRequest), reqBody, bytes.Clone(line), ¶m)
|
segments := sdktranslator.TranslateStream(respCtx, to, from, attempt, bytes.Clone(opts.OriginalRequest), reqBody, bytes.Clone(line), ¶m)
|
||||||
for i := range segments {
|
for i := range segments {
|
||||||
@@ -219,6 +232,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
|
|||||||
out <- cliproxyexecutor.StreamChunk{Err: errRead}
|
out <- cliproxyexecutor.StreamChunk{Err: errRead}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, data)
|
||||||
var param any
|
var param any
|
||||||
segments := sdktranslator.TranslateStream(respCtx, to, from, attempt, bytes.Clone(opts.OriginalRequest), reqBody, data, ¶m)
|
segments := sdktranslator.TranslateStream(respCtx, to, from, attempt, bytes.Clone(opts.OriginalRequest), reqBody, data, ¶m)
|
||||||
for i := range segments {
|
for i := range segments {
|
||||||
@@ -325,7 +339,7 @@ func updateGeminiCLITokenMetadata(auth *cliproxyauth.Auth, base map[string]any,
|
|||||||
}
|
}
|
||||||
if raw, err := json.Marshal(tok); err == nil {
|
if raw, err := json.Marshal(tok); err == nil {
|
||||||
var tokenMap map[string]any
|
var tokenMap map[string]any
|
||||||
if err := json.Unmarshal(raw, &tokenMap); err == nil {
|
if err = json.Unmarshal(raw, &tokenMap); err == nil {
|
||||||
for k, v := range tokenMap {
|
for k, v := range tokenMap {
|
||||||
merged[k] = v
|
merged[k] = v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
@@ -20,9 +21,11 @@ const (
|
|||||||
|
|
||||||
// GeminiExecutor is a stateless executor for the official Gemini API using API keys.
|
// GeminiExecutor is a stateless executor for the official Gemini API using API keys.
|
||||||
// If no API key is found on the auth entry, it falls back to the legacy client via ClientAdapter.
|
// If no API key is found on the auth entry, it falls back to the legacy client via ClientAdapter.
|
||||||
type GeminiExecutor struct{}
|
type GeminiExecutor struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
func NewGeminiExecutor() *GeminiExecutor { return &GeminiExecutor{} }
|
func NewGeminiExecutor(cfg *config.Config) *GeminiExecutor { return &GeminiExecutor{cfg: cfg} }
|
||||||
|
|
||||||
func (e *GeminiExecutor) Identifier() string { return "gemini" }
|
func (e *GeminiExecutor) Identifier() string { return "gemini" }
|
||||||
|
|
||||||
@@ -51,6 +54,7 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -73,12 +77,14 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
}
|
}
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, data)
|
||||||
var param any
|
var param any
|
||||||
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
||||||
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
||||||
@@ -101,6 +107,7 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
} else {
|
} else {
|
||||||
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
url = url + fmt.Sprintf("?$alt=%s", opts.Alt)
|
||||||
}
|
}
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -123,6 +130,7 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
out := make(chan cliproxyexecutor.StreamChunk)
|
out := make(chan cliproxyexecutor.StreamChunk)
|
||||||
@@ -135,6 +143,7 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
var param any
|
var param any
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Bytes()
|
line := scanner.Bytes()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, line)
|
||||||
lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
lines := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
||||||
for i := range lines {
|
for i := range lines {
|
||||||
out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])}
|
out <- cliproxyexecutor.StreamChunk{Payload: []byte(lines[i])}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
|
||||||
geminiwebapi "github.com/router-for-me/CLIProxyAPI/v6/internal/client/gemini-web"
|
geminiwebapi "github.com/router-for-me/CLIProxyAPI/v6/internal/client/gemini-web"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
@@ -192,8 +191,8 @@ func (s *geminiWebState) onCookiesRefreshed() {
|
|||||||
func (s *geminiWebState) tokenSnapshot() *gemini.GeminiWebTokenStorage {
|
func (s *geminiWebState) tokenSnapshot() *gemini.GeminiWebTokenStorage {
|
||||||
s.tokenMu.Lock()
|
s.tokenMu.Lock()
|
||||||
defer s.tokenMu.Unlock()
|
defer s.tokenMu.Unlock()
|
||||||
copy := *s.token
|
c := *s.token
|
||||||
return ©
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *geminiWebState) ShouldRefresh(now time.Time, _ *cliproxyauth.Auth) bool {
|
func (s *geminiWebState) ShouldRefresh(now time.Time, _ *cliproxyauth.Auth) bool {
|
||||||
@@ -225,13 +224,9 @@ func (s *geminiWebState) prepare(ctx context.Context, modelName string, rawJSON
|
|||||||
res.translatedRaw = bytes.Clone(rawJSON)
|
res.translatedRaw = bytes.Clone(rawJSON)
|
||||||
if handler, ok := ctx.Value("handler").(interfaces.APIHandler); ok && handler != nil {
|
if handler, ok := ctx.Value("handler").(interfaces.APIHandler); ok && handler != nil {
|
||||||
res.handlerType = handler.HandlerType()
|
res.handlerType = handler.HandlerType()
|
||||||
res.translatedRaw = translator.Request(res.handlerType, constant.GEMINIWEB, modelName, res.translatedRaw, stream)
|
res.translatedRaw = translator.Request(res.handlerType, constant.GeminiWeb, modelName, res.translatedRaw, stream)
|
||||||
}
|
|
||||||
if s.cfg != nil && s.cfg.RequestLog {
|
|
||||||
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil {
|
|
||||||
ginCtx.Set("API_REQUEST", res.translatedRaw)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
recordAPIRequest(ctx, s.cfg, res.translatedRaw)
|
||||||
|
|
||||||
messages, files, mimes, msgFileIdx, err := geminiwebapi.ParseMessagesAndFiles(res.translatedRaw)
|
messages, files, mimes, msgFileIdx, err := geminiwebapi.ParseMessagesAndFiles(res.translatedRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -336,7 +331,7 @@ func (s *geminiWebState) prepare(ctx context.Context, modelName string, rawJSON
|
|||||||
}
|
}
|
||||||
res.uploaded = uploaded
|
res.uploaded = uploaded
|
||||||
|
|
||||||
if err := s.ensureClient(); err != nil {
|
if err = s.ensureClient(); err != nil {
|
||||||
return nil, &interfaces.ErrorMessage{StatusCode: 500, Error: err}
|
return nil, &interfaces.ErrorMessage{StatusCode: 500, Error: err}
|
||||||
}
|
}
|
||||||
chat := s.client.StartChat(model, s.getConfiguredGem(), meta)
|
chat := s.client.StartChat(model, s.getConfiguredGem(), meta)
|
||||||
@@ -443,36 +438,19 @@ func (s *geminiWebState) persistConversation(modelName string, prep *geminiWebPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *geminiWebState) addAPIResponseData(ctx context.Context, line []byte) {
|
func (s *geminiWebState) addAPIResponseData(ctx context.Context, line []byte) {
|
||||||
if s.cfg == nil || !s.cfg.RequestLog {
|
appendAPIResponseChunk(ctx, s.cfg, line)
|
||||||
return
|
|
||||||
}
|
|
||||||
data := bytes.TrimSpace(bytes.Clone(line))
|
|
||||||
if len(data) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil {
|
|
||||||
if existing, exists := ginCtx.Get("API_RESPONSE"); exists {
|
|
||||||
if prev, okBytes := existing.([]byte); okBytes {
|
|
||||||
prev = append(prev, data...)
|
|
||||||
prev = append(prev, []byte("\n\n")...)
|
|
||||||
ginCtx.Set("API_RESPONSE", prev)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ginCtx.Set("API_RESPONSE", data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *geminiWebState) convertToTarget(ctx context.Context, modelName string, prep *geminiWebPrepared, gemBytes []byte) []byte {
|
func (s *geminiWebState) convertToTarget(ctx context.Context, modelName string, prep *geminiWebPrepared, gemBytes []byte) []byte {
|
||||||
if prep == nil || prep.handlerType == "" {
|
if prep == nil || prep.handlerType == "" {
|
||||||
return gemBytes
|
return gemBytes
|
||||||
}
|
}
|
||||||
if !translator.NeedConvert(prep.handlerType, constant.GEMINIWEB) {
|
if !translator.NeedConvert(prep.handlerType, constant.GeminiWeb) {
|
||||||
return gemBytes
|
return gemBytes
|
||||||
}
|
}
|
||||||
var param any
|
var param any
|
||||||
out := translator.ResponseNonStream(prep.handlerType, constant.GEMINIWEB, ctx, modelName, prep.originalRaw, prep.translatedRaw, gemBytes, ¶m)
|
out := translator.ResponseNonStream(prep.handlerType, constant.GeminiWeb, ctx, modelName, prep.originalRaw, prep.translatedRaw, gemBytes, ¶m)
|
||||||
if prep.handlerType == constant.OPENAI && out != "" {
|
if prep.handlerType == constant.OpenAI && out != "" {
|
||||||
newID := fmt.Sprintf("chatcmpl-%x", time.Now().UnixNano())
|
newID := fmt.Sprintf("chatcmpl-%x", time.Now().UnixNano())
|
||||||
if v := gjson.Parse(out).Get("id"); v.Exists() {
|
if v := gjson.Parse(out).Get("id"); v.Exists() {
|
||||||
out, _ = sjson.Set(out, "id", newID)
|
out, _ = sjson.Set(out, "id", newID)
|
||||||
@@ -485,22 +463,22 @@ func (s *geminiWebState) convertStream(ctx context.Context, modelName string, pr
|
|||||||
if prep == nil || prep.handlerType == "" {
|
if prep == nil || prep.handlerType == "" {
|
||||||
return []string{string(gemBytes)}
|
return []string{string(gemBytes)}
|
||||||
}
|
}
|
||||||
if !translator.NeedConvert(prep.handlerType, constant.GEMINIWEB) {
|
if !translator.NeedConvert(prep.handlerType, constant.GeminiWeb) {
|
||||||
return []string{string(gemBytes)}
|
return []string{string(gemBytes)}
|
||||||
}
|
}
|
||||||
var param any
|
var param any
|
||||||
return translator.Response(prep.handlerType, constant.GEMINIWEB, ctx, modelName, prep.originalRaw, prep.translatedRaw, gemBytes, ¶m)
|
return translator.Response(prep.handlerType, constant.GeminiWeb, ctx, modelName, prep.originalRaw, prep.translatedRaw, gemBytes, ¶m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *geminiWebState) doneStream(ctx context.Context, modelName string, prep *geminiWebPrepared) []string {
|
func (s *geminiWebState) doneStream(ctx context.Context, modelName string, prep *geminiWebPrepared) []string {
|
||||||
if prep == nil || prep.handlerType == "" {
|
if prep == nil || prep.handlerType == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !translator.NeedConvert(prep.handlerType, constant.GEMINIWEB) {
|
if !translator.NeedConvert(prep.handlerType, constant.GeminiWeb) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var param any
|
var param any
|
||||||
return translator.Response(prep.handlerType, constant.GEMINIWEB, ctx, modelName, prep.originalRaw, prep.translatedRaw, []byte("[DONE]"), ¶m)
|
return translator.Response(prep.handlerType, constant.GeminiWeb, ctx, modelName, prep.originalRaw, prep.translatedRaw, []byte("[DONE]"), ¶m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *geminiWebState) useReusableContext() bool {
|
func (s *geminiWebState) useReusableContext() bool {
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
|
||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OpenAICompatExecutor implements a stateless executor for OpenAI-compatible providers.
|
// OpenAICompatExecutor implements a stateless executor for OpenAI-compatible providers.
|
||||||
@@ -18,11 +20,12 @@ import (
|
|||||||
// using per-auth credentials (API key) and per-auth HTTP transport (proxy) from context.
|
// using per-auth credentials (API key) and per-auth HTTP transport (proxy) from context.
|
||||||
type OpenAICompatExecutor struct {
|
type OpenAICompatExecutor struct {
|
||||||
provider string
|
provider string
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOpenAICompatExecutor creates an executor bound to a provider key (e.g., "openrouter").
|
// NewOpenAICompatExecutor creates an executor bound to a provider key (e.g., "openrouter").
|
||||||
func NewOpenAICompatExecutor(provider string) *OpenAICompatExecutor {
|
func NewOpenAICompatExecutor(provider string, cfg *config.Config) *OpenAICompatExecutor {
|
||||||
return &OpenAICompatExecutor{provider: provider}
|
return &OpenAICompatExecutor{provider: provider, cfg: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifier implements cliproxyauth.ProviderExecutor.
|
// Identifier implements cliproxyauth.ProviderExecutor.
|
||||||
@@ -45,6 +48,7 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
|
|||||||
translated := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), opts.Stream)
|
translated := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), opts.Stream)
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
||||||
|
recordAPIRequest(ctx, e.cfg, translated)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(translated))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(translated))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -64,12 +68,14 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
|
|||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
}
|
}
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, body)
|
||||||
// Translate response back to source format when needed
|
// Translate response back to source format when needed
|
||||||
var param any
|
var param any
|
||||||
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, body, ¶m)
|
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), translated, body, ¶m)
|
||||||
@@ -86,6 +92,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
translated := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), true)
|
translated := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), true)
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
||||||
|
recordAPIRequest(ctx, e.cfg, translated)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(translated))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(translated))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -107,6 +114,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
out := make(chan cliproxyexecutor.StreamChunk)
|
out := make(chan cliproxyexecutor.StreamChunk)
|
||||||
@@ -119,6 +127,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
var param any
|
var param any
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Bytes()
|
line := scanner.Bytes()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, line)
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -129,7 +138,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err = scanner.Err(); err != nil {
|
||||||
out <- cliproxyexecutor.StreamChunk{Err: err}
|
out <- cliproxyexecutor.StreamChunk{Err: err}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
@@ -18,9 +19,11 @@ import (
|
|||||||
|
|
||||||
// QwenExecutor is a stateless executor for Qwen Code using OpenAI-compatible chat completions.
|
// QwenExecutor is a stateless executor for Qwen Code using OpenAI-compatible chat completions.
|
||||||
// If access token is unavailable, it falls back to legacy via ClientAdapter.
|
// If access token is unavailable, it falls back to legacy via ClientAdapter.
|
||||||
type QwenExecutor struct{}
|
type QwenExecutor struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
func NewQwenExecutor() *QwenExecutor { return &QwenExecutor{} }
|
func NewQwenExecutor(cfg *config.Config) *QwenExecutor { return &QwenExecutor{cfg: cfg} }
|
||||||
|
|
||||||
func (e *QwenExecutor) Identifier() string { return "qwen" }
|
func (e *QwenExecutor) Identifier() string { return "qwen" }
|
||||||
|
|
||||||
@@ -40,6 +43,7 @@ func (e *QwenExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req
|
|||||||
body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
body := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), false)
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -58,12 +62,14 @@ func (e *QwenExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req
|
|||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
return cliproxyexecutor.Response{}, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
}
|
}
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, data)
|
||||||
var param any
|
var param any
|
||||||
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
out := sdktranslator.TranslateNonStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, data, ¶m)
|
||||||
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
return cliproxyexecutor.Response{Payload: []byte(out)}, nil
|
||||||
@@ -90,6 +96,7 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
}
|
}
|
||||||
|
|
||||||
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
url := strings.TrimSuffix(baseURL, "/") + "/chat/completions"
|
||||||
|
recordAPIRequest(ctx, e.cfg, body)
|
||||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -109,6 +116,7 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
b, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, b)
|
||||||
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
return nil, statusErr{code: resp.StatusCode, msg: string(b)}
|
||||||
}
|
}
|
||||||
out := make(chan cliproxyexecutor.StreamChunk)
|
out := make(chan cliproxyexecutor.StreamChunk)
|
||||||
@@ -121,6 +129,7 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
var param any
|
var param any
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Bytes()
|
line := scanner.Bytes()
|
||||||
|
appendAPIResponseChunk(ctx, e.cfg, line)
|
||||||
chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
chunks := sdktranslator.TranslateStream(ctx, to, from, req.Model, bytes.Clone(opts.OriginalRequest), body, bytes.Clone(line), ¶m)
|
||||||
for i := range chunks {
|
for i := range chunks {
|
||||||
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
out <- cliproxyexecutor.StreamChunk{Payload: []byte(chunks[i])}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
CLAUDE,
|
Claude,
|
||||||
ConvertGeminiCLIRequestToClaude,
|
ConvertGeminiCLIRequestToClaude,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertClaudeResponseToGeminiCLI,
|
Stream: ConvertClaudeResponseToGeminiCLI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINI,
|
Gemini,
|
||||||
CLAUDE,
|
Claude,
|
||||||
ConvertGeminiRequestToClaude,
|
ConvertGeminiRequestToClaude,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertClaudeResponseToGemini,
|
Stream: ConvertClaudeResponseToGemini,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI,
|
OpenAI,
|
||||||
CLAUDE,
|
Claude,
|
||||||
ConvertOpenAIRequestToClaude,
|
ConvertOpenAIRequestToClaude,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertClaudeResponseToOpenAI,
|
Stream: ConvertClaudeResponseToOpenAI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI_RESPONSE,
|
OpenaiResponse,
|
||||||
CLAUDE,
|
Claude,
|
||||||
ConvertOpenAIResponsesRequestToClaude,
|
ConvertOpenAIResponsesRequestToClaude,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertClaudeResponseToOpenAIResponses,
|
Stream: ConvertClaudeResponseToOpenAIResponses,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
CLAUDE,
|
Claude,
|
||||||
CODEX,
|
Codex,
|
||||||
ConvertClaudeRequestToCodex,
|
ConvertClaudeRequestToCodex,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertCodexResponseToClaude,
|
Stream: ConvertCodexResponseToClaude,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
CODEX,
|
Codex,
|
||||||
ConvertGeminiCLIRequestToCodex,
|
ConvertGeminiCLIRequestToCodex,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertCodexResponseToGeminiCLI,
|
Stream: ConvertCodexResponseToGeminiCLI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINI,
|
Gemini,
|
||||||
CODEX,
|
Codex,
|
||||||
ConvertGeminiRequestToCodex,
|
ConvertGeminiRequestToCodex,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertCodexResponseToGemini,
|
Stream: ConvertCodexResponseToGemini,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI,
|
OpenAI,
|
||||||
CODEX,
|
Codex,
|
||||||
ConvertOpenAIRequestToCodex,
|
ConvertOpenAIRequestToCodex,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertCodexResponseToOpenAI,
|
Stream: ConvertCodexResponseToOpenAI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI_RESPONSE,
|
OpenaiResponse,
|
||||||
CODEX,
|
Codex,
|
||||||
ConvertOpenAIResponsesRequestToCodex,
|
ConvertOpenAIResponsesRequestToCodex,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertCodexResponseToOpenAIResponses,
|
Stream: ConvertCodexResponseToOpenAIResponses,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
CLAUDE,
|
Claude,
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
ConvertClaudeRequestToCLI,
|
ConvertClaudeRequestToCLI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertGeminiCLIResponseToClaude,
|
Stream: ConvertGeminiCLIResponseToClaude,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINI,
|
Gemini,
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
ConvertGeminiRequestToGeminiCLI,
|
ConvertGeminiRequestToGeminiCLI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertGeminiCliRequestToGemini,
|
Stream: ConvertGeminiCliRequestToGemini,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI,
|
OpenAI,
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
ConvertOpenAIRequestToGeminiCLI,
|
ConvertOpenAIRequestToGeminiCLI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertCliResponseToOpenAI,
|
Stream: ConvertCliResponseToOpenAI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI_RESPONSE,
|
OpenaiResponse,
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
ConvertOpenAIResponsesRequestToGeminiCLI,
|
ConvertOpenAIResponsesRequestToGeminiCLI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertGeminiCLIResponseToOpenAIResponses,
|
Stream: ConvertGeminiCLIResponseToOpenAIResponses,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI,
|
OpenAI,
|
||||||
GEMINIWEB,
|
GeminiWeb,
|
||||||
geminiChat.ConvertOpenAIRequestToGemini,
|
geminiChat.ConvertOpenAIRequestToGemini,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: geminiChat.ConvertGeminiResponseToOpenAI,
|
Stream: geminiChat.ConvertGeminiResponseToOpenAI,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI_RESPONSE,
|
OpenaiResponse,
|
||||||
GEMINIWEB,
|
GeminiWeb,
|
||||||
geminiResponses.ConvertOpenAIResponsesRequestToGemini,
|
geminiResponses.ConvertOpenAIResponsesRequestToGemini,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: geminiResponses.ConvertGeminiResponseToOpenAIResponses,
|
Stream: geminiResponses.ConvertGeminiResponseToOpenAIResponses,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
CLAUDE,
|
Claude,
|
||||||
GEMINI,
|
Gemini,
|
||||||
ConvertClaudeRequestToGemini,
|
ConvertClaudeRequestToGemini,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertGeminiResponseToClaude,
|
Stream: ConvertGeminiResponseToClaude,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
GEMINI,
|
Gemini,
|
||||||
ConvertGeminiCLIRequestToGemini,
|
ConvertGeminiCLIRequestToGemini,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertGeminiResponseToGeminiCLI,
|
Stream: ConvertGeminiResponseToGeminiCLI,
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
// The request converter ensures missing or invalid roles are normalized to valid values.
|
// The request converter ensures missing or invalid roles are normalized to valid values.
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINI,
|
Gemini,
|
||||||
GEMINI,
|
Gemini,
|
||||||
ConvertGeminiRequestToGemini,
|
ConvertGeminiRequestToGemini,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: PassthroughGeminiResponseStream,
|
Stream: PassthroughGeminiResponseStream,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI,
|
OpenAI,
|
||||||
GEMINI,
|
Gemini,
|
||||||
ConvertOpenAIRequestToGemini,
|
ConvertOpenAIRequestToGemini,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertGeminiResponseToOpenAI,
|
Stream: ConvertGeminiResponseToOpenAI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI_RESPONSE,
|
OpenaiResponse,
|
||||||
GEMINI,
|
Gemini,
|
||||||
ConvertOpenAIResponsesRequestToGemini,
|
ConvertOpenAIResponsesRequestToGemini,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertGeminiResponseToOpenAIResponses,
|
Stream: ConvertGeminiResponseToOpenAIResponses,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
CLAUDE,
|
Claude,
|
||||||
OPENAI,
|
OpenAI,
|
||||||
ConvertClaudeRequestToOpenAI,
|
ConvertClaudeRequestToOpenAI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertOpenAIResponseToClaude,
|
Stream: ConvertOpenAIResponseToClaude,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINICLI,
|
GeminiCLI,
|
||||||
OPENAI,
|
OpenAI,
|
||||||
ConvertGeminiCLIRequestToOpenAI,
|
ConvertGeminiCLIRequestToOpenAI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertOpenAIResponseToGeminiCLI,
|
Stream: ConvertOpenAIResponseToGeminiCLI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
GEMINI,
|
Gemini,
|
||||||
OPENAI,
|
OpenAI,
|
||||||
ConvertGeminiRequestToOpenAI,
|
ConvertGeminiRequestToOpenAI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertOpenAIResponseToGemini,
|
Stream: ConvertOpenAIResponseToGemini,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI,
|
OpenAI,
|
||||||
OPENAI,
|
OpenAI,
|
||||||
ConvertOpenAIRequestToOpenAI,
|
ConvertOpenAIRequestToOpenAI,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertOpenAIResponseToOpenAI,
|
Stream: ConvertOpenAIResponseToOpenAI,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
translator.Register(
|
translator.Register(
|
||||||
OPENAI_RESPONSE,
|
OpenaiResponse,
|
||||||
OPENAI,
|
OpenAI,
|
||||||
ConvertOpenAIResponsesRequestToOpenAIChatCompletions,
|
ConvertOpenAIResponsesRequestToOpenAIChatCompletions,
|
||||||
interfaces.TranslateResponse{
|
interfaces.TranslateResponse{
|
||||||
Stream: ConvertOpenAIChatCompletionsResponseToOpenAIResponses,
|
Stream: ConvertOpenAIChatCompletionsResponseToOpenAIResponses,
|
||||||
|
|||||||
@@ -289,9 +289,6 @@ func sanitizeTypeFields(jsonStr string) string {
|
|||||||
break
|
break
|
||||||
} else if typeStr == "number" || typeStr == "integer" {
|
} else if typeStr == "number" || typeStr == "integer" {
|
||||||
preferredType = typeStr
|
preferredType = typeStr
|
||||||
if preferredType == "" {
|
|
||||||
preferredType = typeStr
|
|
||||||
}
|
|
||||||
} else if preferredType == "" {
|
} else if preferredType == "" {
|
||||||
preferredType = typeStr
|
preferredType = typeStr
|
||||||
}
|
}
|
||||||
@@ -323,6 +320,8 @@ func walkForTypeFields(value gjson.Result, path string, paths *[]string) {
|
|||||||
walkForTypeFields(val, childPath, paths)
|
walkForTypeFields(val, childPath, paths)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
default:
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,5 +366,7 @@ func findNestedSchemaPaths(value gjson.Result, path string, fieldsToFind []strin
|
|||||||
findNestedSchemaPaths(val, childPath, fieldsToFind, paths)
|
findNestedSchemaPaths(val, childPath, fieldsToFind, paths)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
default:
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -21,3 +26,38 @@ func SetLogLevel(cfg *config.Config) {
|
|||||||
log.Infof("log level changed from %s to %s (debug=%t)", currentLevel, newLevel, cfg.Debug)
|
log.Infof("log level changed from %s to %s (debug=%t)", currentLevel, newLevel, cfg.Debug)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountAuthFiles returns the number of JSON auth files located under the provided directory.
|
||||||
|
// The function resolves leading tildes to the user's home directory and performs a case-insensitive
|
||||||
|
// match on the ".json" suffix so that files saved with uppercase extensions are also counted.
|
||||||
|
func CountAuthFiles(authDir string) int {
|
||||||
|
if authDir == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(authDir, "~") {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("countAuthFiles: failed to resolve home directory: %v", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
authDir = filepath.Join(home, authDir[1:])
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
walkErr := filepath.WalkDir(authDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("countAuthFiles: error accessing %s: %v", path, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(strings.ToLower(d.Name()), ".json") {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if walkErr != nil {
|
||||||
|
log.Debugf("countAuthFiles: walk error: %v", walkErr)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|||||||
@@ -174,21 +174,19 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle auth directory changes incrementally (.json only)
|
// Handle auth directory changes incrementally (.json only)
|
||||||
if isAuthJSON {
|
log.Infof("auth file changed (%s): %s, processing incrementally", event.Op.String(), filepath.Base(event.Name))
|
||||||
log.Infof("auth file changed (%s): %s, processing incrementally", event.Op.String(), filepath.Base(event.Name))
|
if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
|
w.addOrUpdateClient(event.Name)
|
||||||
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||||
|
// Atomic replace on some platforms may surface as Remove+Create for the target path.
|
||||||
|
// Wait briefly; if the file exists again, treat as update instead of removal.
|
||||||
|
time.Sleep(replaceCheckDelay)
|
||||||
|
if _, statErr := os.Stat(event.Name); statErr == nil {
|
||||||
|
// File exists after a short delay; handle as an update.
|
||||||
w.addOrUpdateClient(event.Name)
|
w.addOrUpdateClient(event.Name)
|
||||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
return
|
||||||
// Atomic replace on some platforms may surface as Remove+Create for the target path.
|
|
||||||
// Wait briefly; if the file exists again, treat as update instead of removal.
|
|
||||||
time.Sleep(replaceCheckDelay)
|
|
||||||
if _, statErr := os.Stat(event.Name); statErr == nil {
|
|
||||||
// File exists after a short delay; handle as an update.
|
|
||||||
w.addOrUpdateClient(event.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.removeClient(event.Name)
|
|
||||||
}
|
}
|
||||||
|
w.removeClient(event.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +299,7 @@ func (w *Watcher) reloadClients() {
|
|||||||
log.Debugf("created %d new API key clients", 0)
|
log.Debugf("created %d new API key clients", 0)
|
||||||
|
|
||||||
// Load file-based clients
|
// Load file-based clients
|
||||||
successfulAuthCount := w.loadFileClients(cfg)
|
authFileCount := w.loadFileClients(cfg)
|
||||||
log.Debugf("loaded %d new file-based clients", 0)
|
log.Debugf("loaded %d new file-based clients", 0)
|
||||||
|
|
||||||
// no legacy file-based clients to unregister
|
// no legacy file-based clients to unregister
|
||||||
@@ -317,7 +315,7 @@ func (w *Watcher) reloadClients() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") {
|
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") {
|
||||||
if data, err := util.ReadAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay); err == nil && len(data) > 0 {
|
if data, errReadAuthFileWithRetry := util.ReadAuthFileWithRetry(path, authFileReadMaxAttempts, authFileReadRetryDelay); errReadAuthFileWithRetry == nil && len(data) > 0 {
|
||||||
sum := sha256.Sum256(data)
|
sum := sha256.Sum256(data)
|
||||||
w.lastAuthHashes[path] = hex.EncodeToString(sum[:])
|
w.lastAuthHashes[path] = hex.EncodeToString(sum[:])
|
||||||
}
|
}
|
||||||
@@ -326,12 +324,12 @@ func (w *Watcher) reloadClients() {
|
|||||||
})
|
})
|
||||||
w.clientsMutex.Unlock()
|
w.clientsMutex.Unlock()
|
||||||
|
|
||||||
totalNewClients := successfulAuthCount + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
totalNewClients := authFileCount + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
||||||
|
|
||||||
log.Infof("full client reload complete - old: %d clients, new: %d clients (%d auth files + %d GL API keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)",
|
log.Infof("full client reload complete - old: %d clients, new: %d clients (%d auth files + %d GL API keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)",
|
||||||
0,
|
0,
|
||||||
totalNewClients,
|
totalNewClients,
|
||||||
successfulAuthCount,
|
authFileCount,
|
||||||
glAPIKeyCount,
|
glAPIKeyCount,
|
||||||
claudeAPIKeyCount,
|
claudeAPIKeyCount,
|
||||||
codexAPIKeyCount,
|
codexAPIKeyCount,
|
||||||
@@ -572,7 +570,7 @@ func (w *Watcher) loadFileClients(cfg *config.Config) int {
|
|||||||
log.Debugf("error accessing path %s: %v", path, err)
|
log.Debugf("error accessing path %s: %v", path, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !info.IsDir() && strings.HasSuffix(info.Name(), ".json") {
|
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") {
|
||||||
authFileCount++
|
authFileCount++
|
||||||
misc.LogCredentialSeparator()
|
misc.LogCredentialSeparator()
|
||||||
log.Debugf("processing auth file %d: %s", authFileCount, filepath.Base(path))
|
log.Debugf("processing auth file %d: %s", authFileCount, filepath.Base(path))
|
||||||
@@ -587,8 +585,8 @@ func (w *Watcher) loadFileClients(cfg *config.Config) int {
|
|||||||
if errWalk != nil {
|
if errWalk != nil {
|
||||||
log.Errorf("error walking auth directory: %v", errWalk)
|
log.Errorf("error walking auth directory: %v", errWalk)
|
||||||
}
|
}
|
||||||
log.Debugf("auth directory scan complete - found %d .json files, %d successful authentications", authFileCount, successfulAuthCount)
|
log.Debugf("auth directory scan complete - found %d .json files, %d readable", authFileCount, successfulAuthCount)
|
||||||
return successfulAuthCount
|
return authFileCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildAPIKeyClients(cfg *config.Config) (int, int, int, int) {
|
func BuildAPIKeyClients(cfg *config.Config) (int, int, int, int) {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (s *FileStore) Save(ctx context.Context, auth *Auth) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("auth filestore: marshal metadata failed: %w", err)
|
return fmt.Errorf("auth filestore: marshal metadata failed: %w", err)
|
||||||
}
|
}
|
||||||
if existing, err := os.ReadFile(path); err == nil {
|
if existing, errReadFile := os.ReadFile(path); errReadFile == nil {
|
||||||
if jsonEqual(existing, raw) {
|
if jsonEqual(existing, raw) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -108,8 +108,8 @@ func deepEqualJSON(a, b any) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for key, subA := range valA {
|
for key, subA := range valA {
|
||||||
subB, ok := valB[key]
|
subB, ok1 := valB[key]
|
||||||
if !ok || !deepEqualJSON(subA, subB) {
|
if !ok1 || !deepEqualJSON(subA, subB) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -795,7 +795,7 @@ func authLastRefreshTimestamp(a *Auth) (time.Time, bool) {
|
|||||||
func lookupMetadataTime(meta map[string]any, keys ...string) (time.Time, bool) {
|
func lookupMetadataTime(meta map[string]any, keys ...string) (time.Time, bool) {
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if val, ok := meta[key]; ok {
|
if val, ok := meta[key]; ok {
|
||||||
if ts, ok := parseTimeValue(val); ok {
|
if ts, ok1 := parseTimeValue(val); ok1 {
|
||||||
return ts, true
|
return ts, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,24 @@ func (a *Auth) AccountInfo() (bool, string) {
|
|||||||
if a == nil {
|
if a == nil {
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
if strings.ToLower(a.Provider) == "gemini-web" {
|
||||||
|
if a.Metadata != nil {
|
||||||
|
if v, ok := a.Metadata["secure_1psid"].(string); ok && v != "" {
|
||||||
|
return true, v
|
||||||
|
}
|
||||||
|
if v, ok := a.Metadata["__Secure-1PSID"].(string); ok && v != "" {
|
||||||
|
return true, v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a.Attributes != nil {
|
||||||
|
if v := a.Attributes["secure_1psid"]; v != "" {
|
||||||
|
return true, v
|
||||||
|
}
|
||||||
|
if v := a.Attributes["api_key"]; v != "" {
|
||||||
|
return true, v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if a.Metadata != nil {
|
if a.Metadata != nil {
|
||||||
if v, ok := a.Metadata["email"].(string); ok {
|
if v, ok := a.Metadata["email"].(string); ok {
|
||||||
return false, v
|
return false, v
|
||||||
@@ -125,7 +143,7 @@ func expirationFromMap(meta map[string]any) (time.Time, bool) {
|
|||||||
}
|
}
|
||||||
for _, key := range expireKeys {
|
for _, key := range expireKeys {
|
||||||
if v, ok := meta[key]; ok {
|
if v, ok := meta[key]; ok {
|
||||||
if ts, ok := parseTimeValue(v); ok {
|
if ts, ok1 := parseTimeValue(v); ok1 {
|
||||||
return ts, true
|
return ts, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +152,7 @@ func expirationFromMap(meta map[string]any) (time.Time, bool) {
|
|||||||
if nested, ok := meta[nestedKey]; ok {
|
if nested, ok := meta[nestedKey]; ok {
|
||||||
switch val := nested.(type) {
|
switch val := nested.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
if ts, ok := expirationFromMap(val); ok {
|
if ts, ok1 := expirationFromMap(val); ok1 {
|
||||||
return ts, true
|
return ts, true
|
||||||
}
|
}
|
||||||
case map[string]string:
|
case map[string]string:
|
||||||
@@ -142,7 +160,7 @@ func expirationFromMap(meta map[string]any) (time.Time, bool) {
|
|||||||
for k, v := range val {
|
for k, v := range val {
|
||||||
temp[k] = v
|
temp[k] = v
|
||||||
}
|
}
|
||||||
if ts, ok := expirationFromMap(temp); ok {
|
if ts, ok1 := expirationFromMap(temp); ok1 {
|
||||||
return ts, true
|
return ts, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor"
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
|
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
|
||||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -172,10 +173,11 @@ func (s *Service) Run(ctx context.Context) error {
|
|||||||
log.Infof("core auth auto-refresh started (interval=%s)", interval)
|
log.Infof("core auth auto-refresh started (interval=%s)", interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalNewClients := tokenResult.SuccessfulAuthed + apiKeyResult.GeminiKeyCount + apiKeyResult.ClaudeKeyCount + apiKeyResult.CodexKeyCount + apiKeyResult.OpenAICompatCount
|
authFileCount := util.CountAuthFiles(s.cfg.AuthDir)
|
||||||
|
totalNewClients := authFileCount + apiKeyResult.GeminiKeyCount + apiKeyResult.ClaudeKeyCount + apiKeyResult.CodexKeyCount + apiKeyResult.OpenAICompatCount
|
||||||
log.Infof("full client load complete - %d clients (%d auth files + %d GL API keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)",
|
log.Infof("full client load complete - %d clients (%d auth files + %d GL API keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)",
|
||||||
totalNewClients,
|
totalNewClients,
|
||||||
tokenResult.SuccessfulAuthed,
|
authFileCount,
|
||||||
apiKeyResult.GeminiKeyCount,
|
apiKeyResult.GeminiKeyCount,
|
||||||
apiKeyResult.ClaudeKeyCount,
|
apiKeyResult.ClaudeKeyCount,
|
||||||
apiKeyResult.CodexKeyCount,
|
apiKeyResult.CodexKeyCount,
|
||||||
@@ -292,19 +294,19 @@ func (s *Service) syncCoreAuthFromAuths(ctx context.Context, auths []*coreauth.A
|
|||||||
// Ensure executors registered per provider: prefer stateless where available.
|
// Ensure executors registered per provider: prefer stateless where available.
|
||||||
switch strings.ToLower(a.Provider) {
|
switch strings.ToLower(a.Provider) {
|
||||||
case "gemini":
|
case "gemini":
|
||||||
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor())
|
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg))
|
||||||
case "gemini-cli":
|
case "gemini-cli":
|
||||||
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor())
|
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg))
|
||||||
case "gemini-web":
|
case "gemini-web":
|
||||||
s.coreManager.RegisterExecutor(executor.NewGeminiWebExecutor(s.cfg))
|
s.coreManager.RegisterExecutor(executor.NewGeminiWebExecutor(s.cfg))
|
||||||
case "claude":
|
case "claude":
|
||||||
s.coreManager.RegisterExecutor(executor.NewClaudeExecutor())
|
s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg))
|
||||||
case "codex":
|
case "codex":
|
||||||
s.coreManager.RegisterExecutor(executor.NewCodexExecutor())
|
s.coreManager.RegisterExecutor(executor.NewCodexExecutor(s.cfg))
|
||||||
case "qwen":
|
case "qwen":
|
||||||
s.coreManager.RegisterExecutor(executor.NewQwenExecutor())
|
s.coreManager.RegisterExecutor(executor.NewQwenExecutor(s.cfg))
|
||||||
default:
|
default:
|
||||||
s.coreManager.RegisterExecutor(executor.NewOpenAICompatExecutor("openai-compatibility"))
|
s.coreManager.RegisterExecutor(executor.NewOpenAICompatExecutor("openai-compatibility", s.cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve existing temporal fields
|
// Preserve existing temporal fields
|
||||||
@@ -316,9 +318,9 @@ func (s *Service) syncCoreAuthFromAuths(ctx context.Context, auths []*coreauth.A
|
|||||||
// Ensure model registry reflects core auth identity
|
// Ensure model registry reflects core auth identity
|
||||||
s.registerModelsForAuth(a)
|
s.registerModelsForAuth(a)
|
||||||
if _, ok := s.coreManager.GetByID(a.ID); ok {
|
if _, ok := s.coreManager.GetByID(a.ID); ok {
|
||||||
s.coreManager.Update(ctx, a)
|
_, _ = s.coreManager.Update(ctx, a)
|
||||||
} else {
|
} else {
|
||||||
s.coreManager.Register(ctx, a)
|
_, _ = s.coreManager.Register(ctx, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Disable removed auths
|
// Disable removed auths
|
||||||
@@ -333,7 +335,7 @@ func (s *Service) syncCoreAuthFromAuths(ctx context.Context, auths []*coreauth.A
|
|||||||
stored.Status = coreauth.StatusDisabled
|
stored.Status = coreauth.StatusDisabled
|
||||||
// Unregister from model registry when disabled
|
// Unregister from model registry when disabled
|
||||||
GlobalModelRegistry().UnregisterClient(stored.ID)
|
GlobalModelRegistry().UnregisterClient(stored.ID)
|
||||||
s.coreManager.Update(ctx, stored)
|
_, _ = s.coreManager.Update(ctx, stored)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user