Merge pull request #112 from CJackHwang/codex/fix-token-expiration-handling

Attempt token refresh for biz_code failures; report config writability and handle token write errors
This commit is contained in:
CJACK.
2026-03-20 23:56:40 +08:00
committed by GitHub
3 changed files with 87 additions and 5 deletions

View File

@@ -89,7 +89,15 @@ func runAccountTestsConcurrently(accounts []config.Account, maxConcurrency int,
func (h *Handler) testAccount(ctx context.Context, acc config.Account, model, message string) map[string]any {
start := time.Now()
identifier := acc.Identifier()
result := map[string]any{"account": identifier, "success": false, "response_time": 0, "message": "", "model": model, "session_count": 0}
result := map[string]any{
"account": identifier,
"success": false,
"response_time": 0,
"message": "",
"model": model,
"session_count": 0,
"config_writable": !h.Store.IsEnvBacked(),
}
defer func() {
status := "failed"
if ok, _ := result["success"].(bool); ok {
@@ -105,7 +113,10 @@ func (h *Handler) testAccount(ctx context.Context, acc config.Account, model, me
return result
}
token = newToken
_ = h.Store.UpdateAccountToken(acc.Identifier(), token)
if err := h.Store.UpdateAccountToken(acc.Identifier(), token); err != nil {
result["message"] = "登录成功但写入配置失败: " + err.Error()
return result
}
}
authCtx := &authn.RequestAuth{UseConfigToken: false, DeepSeekToken: token}
sessionID, err := h.DS.CreateSession(ctx, authCtx, 1)
@@ -117,7 +128,10 @@ func (h *Handler) testAccount(ctx context.Context, acc config.Account, model, me
}
token = newToken
authCtx.DeepSeekToken = token
_ = h.Store.UpdateAccountToken(acc.Identifier(), token)
if err := h.Store.UpdateAccountToken(acc.Identifier(), token); err != nil {
result["message"] = "刷新 token 成功但写入配置失败: " + err.Error()
return result
}
sessionID, err = h.DS.CreateSession(ctx, authCtx, 1)
if err != nil {
result["message"] = "创建会话失败: " + err.Error()

View File

@@ -73,7 +73,7 @@ func (c *Client) CreateSession(ctx context.Context, a *auth.RequestAuth, maxAtte
}
config.Logger.Warn("[create_session] failed", "status", status, "code", code, "biz_code", bizCode, "msg", msg, "biz_msg", bizMsg, "use_config_token", a.UseConfigToken, "account", a.AccountID)
if a.UseConfigToken {
if isTokenInvalid(status, code, bizCode, msg, bizMsg) && !refreshed {
if !refreshed && shouldAttemptRefresh(status, code, bizCode, msg, bizMsg) {
if c.Auth.RefreshToken(ctx, a) {
refreshed = true
continue
@@ -118,7 +118,7 @@ func (c *Client) GetPow(ctx context.Context, a *auth.RequestAuth, maxAttempts in
}
config.Logger.Warn("[get_pow] failed", "status", status, "code", code, "biz_code", bizCode, "msg", msg, "biz_msg", bizMsg, "use_config_token", a.UseConfigToken, "account", a.AccountID)
if a.UseConfigToken {
if isTokenInvalid(status, code, bizCode, msg, bizMsg) && !refreshed {
if !refreshed && shouldAttemptRefresh(status, code, bizCode, msg, bizMsg) {
if c.Auth.RefreshToken(ctx, a) {
refreshed = true
continue
@@ -160,6 +160,47 @@ func isTokenInvalid(status int, code int, bizCode int, msg string, bizMsg string
strings.Contains(msg, "invalid jwt")
}
func shouldAttemptRefresh(status int, code int, bizCode int, msg string, bizMsg string) bool {
if isTokenInvalid(status, code, bizCode, msg, bizMsg) {
return true
}
// Some DeepSeek failures come back as HTTP 200/code=0 but with non-zero biz_code.
// Only attempt refresh when these biz failures still look auth-related.
return status == http.StatusOK &&
code == 0 &&
bizCode != 0 &&
isAuthIndicativeBizFailure(msg, bizMsg)
}
func isAuthIndicativeBizFailure(msg string, bizMsg string) bool {
combined := strings.ToLower(strings.TrimSpace(msg) + " " + strings.TrimSpace(bizMsg))
authKeywords := []string{
"auth",
"authorization",
"credential",
"expired",
"invalid jwt",
"jwt",
"login",
"not login",
"session expired",
"token",
"unauthorized",
"登录",
"未登录",
"认证",
"凭证",
"会话过期",
"令牌",
}
for _, keyword := range authKeywords {
if strings.Contains(combined, keyword) {
return true
}
}
return false
}
func extractResponseStatus(resp map[string]any) (code int, bizCode int, msg string, bizMsg string) {
code = intFrom(resp["code"])
msg, _ = resp["msg"].(string)

View File

@@ -0,0 +1,27 @@
package deepseek
import "testing"
func TestShouldAttemptRefreshOnTokenInvalidSignal(t *testing.T) {
if !shouldAttemptRefresh(401, 0, 0, "unauthorized", "") {
t.Fatal("expected refresh when response indicates invalid token")
}
}
func TestShouldAttemptRefreshOnAuthIndicativeBizCodeFailure(t *testing.T) {
if !shouldAttemptRefresh(200, 0, 400123, "", "login expired, token invalid") {
t.Fatal("expected refresh on auth-indicative biz_code failure")
}
}
func TestShouldAttemptRefreshFalseOnNonAuthBizCodeFailure(t *testing.T) {
if shouldAttemptRefresh(200, 0, 400123, "", "session create failed: quota reached") {
t.Fatal("did not expect refresh on non-auth biz_code failure")
}
}
func TestShouldAttemptRefreshFalseOnGenericServerError(t *testing.T) {
if shouldAttemptRefresh(500, 500, 0, "internal error", "") {
t.Fatal("did not expect refresh on generic server error")
}
}