diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index e0904ab6e..ad35bb9b9 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -1377,9 +1377,11 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { claims, _ := codex.ParseJWTToken(tokenResp.IDToken) email := "" accountID := "" + planType := "" if claims != nil { email = claims.GetUserEmail() accountID = claims.GetAccountID() + planType = strings.TrimSpace(claims.CodexAuthInfo.ChatgptPlanType) } // Build bundle compatible with existing storage bundle := &codex.CodexAuthBundle{ @@ -1396,10 +1398,11 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { // Create token storage and persist tokenStorage := openaiAuth.CreateTokenStorage(bundle) + fileName := codex.CredentialFileName(tokenStorage.Email, planType, true) record := &coreauth.Auth{ - ID: fmt.Sprintf("codex-%s.json", tokenStorage.Email), + ID: fileName, Provider: "codex", - FileName: fmt.Sprintf("codex-%s.json", tokenStorage.Email), + FileName: fileName, Storage: tokenStorage, Metadata: map[string]any{ "email": tokenStorage.Email, diff --git a/internal/auth/codex/filename.go b/internal/auth/codex/filename.go new file mode 100644 index 000000000..fcf028024 --- /dev/null +++ b/internal/auth/codex/filename.go @@ -0,0 +1,55 @@ +package codex + +import ( + "fmt" + "strings" + "unicode" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// CredentialFileName returns the filename used to persist Codex OAuth credentials. +// When planType is available (e.g. "plus", "team"), it is appended after the email +// as a suffix to disambiguate subscriptions. +func CredentialFileName(email, planType string, includeProviderPrefix bool) string { + email = strings.TrimSpace(email) + plan := normalizePlanTypeForFilename(planType) + + prefix := "" + if includeProviderPrefix { + prefix = "codex-" + } + + if plan == "" { + return fmt.Sprintf("%s%s.json", prefix, email) + } + return fmt.Sprintf("%s%s-%s.json", prefix, email, plan) +} + +func normalizePlanTypeForFilename(planType string) string { + planType = strings.TrimSpace(planType) + if planType == "" { + return "" + } + + parts := strings.FieldsFunc(planType, func(r rune) bool { + return !unicode.IsLetter(r) && !unicode.IsDigit(r) + }) + if len(parts) == 0 { + return "" + } + + for i, part := range parts { + parts[i] = titleToken(part) + } + return strings.Join(parts, "-") +} + +func titleToken(token string) string { + token = strings.TrimSpace(token) + if token == "" { + return "" + } + return cases.Title(language.English).String(token) +} diff --git a/sdk/auth/codex.go b/sdk/auth/codex.go index 999925251..af57f1801 100644 --- a/sdk/auth/codex.go +++ b/sdk/auth/codex.go @@ -186,7 +186,13 @@ waitForCallback: return nil, fmt.Errorf("codex token storage missing account information") } - fileName := fmt.Sprintf("codex-%s.json", tokenStorage.Email) + planType := "" + if tokenStorage.IDToken != "" { + if claims, errParse := codex.ParseJWTToken(tokenStorage.IDToken); errParse == nil && claims != nil { + planType = strings.TrimSpace(claims.CodexAuthInfo.ChatgptPlanType) + } + } + fileName := codex.CredentialFileName(tokenStorage.Email, planType, true) metadata := map[string]any{ "email": tokenStorage.Email, }