diff --git a/internal/api/handlers/claude/code_handlers.go b/internal/api/handlers/claude/code_handlers.go index d3f0e61b..75ce2c81 100644 --- a/internal/api/handlers/claude/code_handlers.go +++ b/internal/api/handlers/claude/code_handlers.go @@ -197,6 +197,14 @@ outLoop: log.Debugf("http status code %d, switch client, %s", errInfo.StatusCode, util.HideAPIKey(cliClient.GetEmail())) retryCount++ continue outLoop + case 401: + log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail())) + err := cliClient.RefreshTokens(cliCtx) + if err != nil { + log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail())) + } + retryCount++ + continue outLoop default: // Forward other errors directly to the client c.Status(errInfo.StatusCode) diff --git a/internal/api/handlers/gemini/gemini-cli_handlers.go b/internal/api/handlers/gemini/gemini-cli_handlers.go index 012bb708..8906db47 100644 --- a/internal/api/handlers/gemini/gemini-cli_handlers.go +++ b/internal/api/handlers/gemini/gemini-cli_handlers.go @@ -275,6 +275,14 @@ func (h *GeminiCLIAPIHandler) handleInternalGenerateContent(c *gin.Context, rawJ log.Debugf("http status code %d, switch client", err.StatusCode) retryCount++ continue + case 401: + log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail())) + errRefreshTokens := cliClient.RefreshTokens(cliCtx) + if errRefreshTokens != nil { + log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail())) + } + retryCount++ + continue default: // Forward other errors directly to the client c.Status(err.StatusCode) diff --git a/internal/api/handlers/gemini/gemini_handlers.go b/internal/api/handlers/gemini/gemini_handlers.go index d4066ca4..907ae1a6 100644 --- a/internal/api/handlers/gemini/gemini_handlers.go +++ b/internal/api/handlers/gemini/gemini_handlers.go @@ -17,6 +17,7 @@ import ( . "github.com/luispater/CLIProxyAPI/internal/constant" "github.com/luispater/CLIProxyAPI/internal/interfaces" "github.com/luispater/CLIProxyAPI/internal/registry" + "github.com/luispater/CLIProxyAPI/internal/util" log "github.com/sirupsen/logrus" ) @@ -387,6 +388,14 @@ func (h *GeminiAPIHandler) handleGenerateContent(c *gin.Context, modelName strin log.Debugf("http status code %d, switch client", err.StatusCode) retryCount++ continue + case 401: + log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail())) + errRefreshTokens := cliClient.RefreshTokens(cliCtx) + if errRefreshTokens != nil { + log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail())) + } + retryCount++ + continue default: // Forward other errors directly to the client c.Status(err.StatusCode) diff --git a/internal/api/handlers/openai/openai_handlers.go b/internal/api/handlers/openai/openai_handlers.go index e2ab9796..b97d5233 100644 --- a/internal/api/handlers/openai/openai_handlers.go +++ b/internal/api/handlers/openai/openai_handlers.go @@ -18,6 +18,7 @@ import ( . "github.com/luispater/CLIProxyAPI/internal/constant" "github.com/luispater/CLIProxyAPI/internal/interfaces" "github.com/luispater/CLIProxyAPI/internal/registry" + "github.com/luispater/CLIProxyAPI/internal/util" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/tidwall/sjson" @@ -409,6 +410,14 @@ func (h *OpenAIAPIHandler) handleNonStreamingResponse(c *gin.Context, rawJSON [] log.Debugf("http status code %d, switch client", err.StatusCode) retryCount++ continue + case 401: + log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail())) + errRefreshTokens := cliClient.RefreshTokens(cliCtx) + if errRefreshTokens != nil { + log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail())) + } + retryCount++ + continue default: // Forward other errors directly to the client c.Status(err.StatusCode) diff --git a/internal/client/gemini-cli_client.go b/internal/client/gemini-cli_client.go index 4b1409c4..aa3365c3 100644 --- a/internal/client/gemini-cli_client.go +++ b/internal/client/gemini-cli_client.go @@ -860,3 +860,8 @@ func (c *GeminiCLIClient) GetUserAgent() string { func (c *GeminiCLIClient) GetRequestMutex() *sync.Mutex { return nil } + +func (c *GeminiCLIClient) RefreshTokens(ctx context.Context) error { + // API keys don't need refreshing + return nil +} diff --git a/internal/client/gemini_client.go b/internal/client/gemini_client.go index 86cabb79..be3ee6f9 100644 --- a/internal/client/gemini_client.go +++ b/internal/client/gemini_client.go @@ -434,3 +434,8 @@ func (c *GeminiClient) GetUserAgent() string { func (c *GeminiClient) GetRequestMutex() *sync.Mutex { return nil } + +func (c *GeminiClient) RefreshTokens(ctx context.Context) error { + // API keys don't need refreshing + return nil +} diff --git a/internal/interfaces/client.go b/internal/interfaces/client.go index 28065901..a9bd5987 100644 --- a/internal/interfaces/client.go +++ b/internal/interfaces/client.go @@ -51,4 +51,6 @@ type Client interface { // Provider returns the name of the AI service provider (e.g., "gemini", "claude"). Provider() string + + RefreshTokens(ctx context.Context) error }