Bläddra i källkod

Revert "feat: add Claude Code channel support with OAuth integration"

Seefs 5 månader sedan
förälder
incheckning
f995e31d04

+ 0 - 2
common/api_type.go

@@ -65,8 +65,6 @@ func ChannelType2APIType(channelType int) (int, bool) {
 		apiType = constant.APITypeCoze
 		apiType = constant.APITypeCoze
 	case constant.ChannelTypeJimeng:
 	case constant.ChannelTypeJimeng:
 		apiType = constant.APITypeJimeng
 		apiType = constant.APITypeJimeng
-	case constant.ChannelTypeClaudeCode:
-		apiType = constant.APITypeClaudeCode
 	}
 	}
 	if apiType == -1 {
 	if apiType == -1 {
 		return constant.APITypeOpenAI, false
 		return constant.APITypeOpenAI, false

+ 0 - 1
constant/api_type.go

@@ -31,6 +31,5 @@ const (
 	APITypeXai
 	APITypeXai
 	APITypeCoze
 	APITypeCoze
 	APITypeJimeng
 	APITypeJimeng
-	APITypeClaudeCode
 	APITypeDummy // this one is only for count, do not add any channel after this
 	APITypeDummy // this one is only for count, do not add any channel after this
 )
 )

+ 0 - 2
constant/channel.go

@@ -50,7 +50,6 @@ const (
 	ChannelTypeKling          = 50
 	ChannelTypeKling          = 50
 	ChannelTypeJimeng         = 51
 	ChannelTypeJimeng         = 51
 	ChannelTypeVidu           = 52
 	ChannelTypeVidu           = 52
-	ChannelTypeClaudeCode     = 53
 	ChannelTypeDummy          // this one is only for count, do not add any channel after this
 	ChannelTypeDummy          // this one is only for count, do not add any channel after this
 
 
 )
 )
@@ -109,5 +108,4 @@ var ChannelBaseURLs = []string{
 	"https://api.klingai.com",                   //50
 	"https://api.klingai.com",                   //50
 	"https://visual.volcengineapi.com",          //51
 	"https://visual.volcengineapi.com",          //51
 	"https://api.vidu.cn",                       //52
 	"https://api.vidu.cn",                       //52
-	"https://api.anthropic.com",                 //53
 }
 }

+ 0 - 73
controller/claude_oauth.go

@@ -1,73 +0,0 @@
-package controller
-
-import (
-	"net/http"
-	"one-api/common"
-	"one-api/service"
-
-	"github.com/gin-gonic/gin"
-)
-
-// ExchangeCodeRequest 授权码交换请求
-type ExchangeCodeRequest struct {
-	AuthorizationCode string `json:"authorization_code" binding:"required"`
-	CodeVerifier      string `json:"code_verifier" binding:"required"`
-	State             string `json:"state" binding:"required"`
-}
-
-// GenerateClaudeOAuthURL 生成Claude OAuth授权URL
-func GenerateClaudeOAuthURL(c *gin.Context) {
-	params, err := service.GenerateOAuthParams()
-	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"success": false,
-			"message": "生成OAuth授权URL失败: " + err.Error(),
-		})
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{
-		"success": true,
-		"message": "生成OAuth授权URL成功",
-		"data":    params,
-	})
-}
-
-// ExchangeClaudeOAuthCode 交换Claude OAuth授权码
-func ExchangeClaudeOAuthCode(c *gin.Context) {
-	var req ExchangeCodeRequest
-	if err := c.ShouldBindJSON(&req); err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{
-			"success": false,
-			"message": "请求参数错误: " + err.Error(),
-		})
-		return
-	}
-
-	// 解析授权码
-	cleanedCode, err := service.ParseAuthorizationCode(req.AuthorizationCode)
-	if err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{
-			"success": false,
-			"message": err.Error(),
-		})
-		return
-	}
-
-	// 交换token
-	tokenResult, err := service.ExchangeCode(cleanedCode, req.CodeVerifier, req.State, nil)
-	if err != nil {
-		common.SysError("Claude OAuth token exchange failed: " + err.Error())
-		c.JSON(http.StatusInternalServerError, gin.H{
-			"success": false,
-			"message": "授权码交换失败: " + err.Error(),
-		})
-		return
-	}
-
-	c.JSON(http.StatusOK, gin.H{
-		"success": true,
-		"message": "授权码交换成功",
-		"data":    tokenResult,
-	})
-}

+ 0 - 1
go.mod

@@ -87,7 +87,6 @@ require (
 	github.com/yusufpapurcu/wmi v1.2.3 // indirect
 	github.com/yusufpapurcu/wmi v1.2.3 // indirect
 	golang.org/x/arch v0.12.0 // indirect
 	golang.org/x/arch v0.12.0 // indirect
 	golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
 	golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
-	golang.org/x/oauth2 v0.30.0 // indirect
 	golang.org/x/sys v0.30.0 // indirect
 	golang.org/x/sys v0.30.0 // indirect
 	golang.org/x/text v0.22.0 // indirect
 	golang.org/x/text v0.22.0 // indirect
 	google.golang.org/protobuf v1.34.2 // indirect
 	google.golang.org/protobuf v1.34.2 // indirect

+ 0 - 2
go.sum

@@ -231,8 +231,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
 golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
 golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
 golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
-golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
-golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
 golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
 golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=

+ 0 - 3
main.go

@@ -86,9 +86,6 @@ func main() {
 	// 数据看板
 	// 数据看板
 	go model.UpdateQuotaData()
 	go model.UpdateQuotaData()
 
 
-	// Start Claude Code token refresh scheduler
-	service.StartClaudeTokenRefreshScheduler()
-
 	if os.Getenv("CHANNEL_UPDATE_FREQUENCY") != "" {
 	if os.Getenv("CHANNEL_UPDATE_FREQUENCY") != "" {
 		frequency, err := strconv.Atoi(os.Getenv("CHANNEL_UPDATE_FREQUENCY"))
 		frequency, err := strconv.Atoi(os.Getenv("CHANNEL_UPDATE_FREQUENCY"))
 		if err != nil {
 		if err != nil {

+ 0 - 158
relay/channel/claude_code/adaptor.go

@@ -1,158 +0,0 @@
-package claude_code
-
-import (
-	"errors"
-	"fmt"
-	"io"
-	"net/http"
-	"one-api/dto"
-	"one-api/relay/channel"
-	"one-api/relay/channel/claude"
-	relaycommon "one-api/relay/common"
-	"one-api/types"
-	"strings"
-
-	"github.com/gin-gonic/gin"
-)
-
-const (
-	RequestModeCompletion = 1
-	RequestModeMessage    = 2
-	DefaultSystemPrompt   = "You are Claude Code, Anthropic's official CLI for Claude."
-)
-
-type Adaptor struct {
-	RequestMode int
-}
-
-func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.ClaudeRequest) (any, error) {
-	// Use configured system prompt if available, otherwise use default
-	if info.ChannelSetting.SystemPrompt != "" {
-		request.System = info.ChannelSetting.SystemPrompt
-	} else {
-		request.System = DefaultSystemPrompt
-	}
-
-	return request, nil
-}
-
-func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
-	return nil, errors.New("not implemented")
-}
-
-func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
-	return nil, errors.New("not implemented")
-}
-
-func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
-	if strings.HasPrefix(info.UpstreamModelName, "claude-2") || strings.HasPrefix(info.UpstreamModelName, "claude-instant") {
-		a.RequestMode = RequestModeCompletion
-	} else {
-		a.RequestMode = RequestModeMessage
-	}
-}
-
-func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
-	if a.RequestMode == RequestModeMessage {
-		return fmt.Sprintf("%s/v1/messages", info.BaseUrl), nil
-	} else {
-		return fmt.Sprintf("%s/v1/complete", info.BaseUrl), nil
-	}
-}
-
-func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
-	channel.SetupApiRequestHeader(info, c, req)
-
-	// Parse accesstoken|refreshtoken format and use only the access token
-	accessToken := info.ApiKey
-	if strings.Contains(info.ApiKey, "|") {
-		parts := strings.Split(info.ApiKey, "|")
-		if len(parts) >= 1 {
-			accessToken = parts[0]
-		}
-	}
-
-	// Claude Code specific headers - force override
-	req.Set("Authorization", "Bearer "+accessToken)
-	// 只有在没有设置的情况下才设置 anthropic-version
-	if req.Get("anthropic-version") == "" {
-		req.Set("anthropic-version", "2023-06-01")
-	}
-	req.Set("content-type", "application/json")
-
-	// 只有在 user-agent 不包含 claude-cli 时才设置
-	userAgent := req.Get("user-agent")
-	if userAgent == "" || !strings.Contains(strings.ToLower(userAgent), "claude-cli") {
-		req.Set("user-agent", "claude-cli/1.0.61 (external, cli)")
-	}
-
-	// 只有在 anthropic-beta 不包含 claude-code 时才设置
-	anthropicBeta := req.Get("anthropic-beta")
-	if anthropicBeta == "" || !strings.Contains(strings.ToLower(anthropicBeta), "claude-code") {
-		req.Set("anthropic-beta", "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14")
-	}
-	// if Anthropic-Dangerous-Direct-Browser-Access
-	anthropicDangerousDirectBrowserAccess := req.Get("anthropic-dangerous-direct-browser-access")
-	if anthropicDangerousDirectBrowserAccess == "" {
-		req.Set("anthropic-dangerous-direct-browser-access", "true")
-	}
-
-	return nil
-}
-
-func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
-	if request == nil {
-		return nil, errors.New("request is nil")
-	}
-
-	if a.RequestMode == RequestModeCompletion {
-		return claude.RequestOpenAI2ClaudeComplete(*request), nil
-	} else {
-		claudeRequest, err := claude.RequestOpenAI2ClaudeMessage(*request)
-		if err != nil {
-			return nil, err
-		}
-
-		// Use configured system prompt if available, otherwise use default
-		if info.ChannelSetting.SystemPrompt != "" {
-			claudeRequest.System = info.ChannelSetting.SystemPrompt
-		} else {
-			claudeRequest.System = DefaultSystemPrompt
-		}
-
-		return claudeRequest, nil
-	}
-}
-
-func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
-	return nil, nil
-}
-
-func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
-	return nil, errors.New("not implemented")
-}
-
-func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
-	return nil, errors.New("not implemented")
-}
-
-func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
-	return channel.DoApiRequest(a, c, info, requestBody)
-}
-
-func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) {
-	if info.IsStream {
-		err, usage = claude.ClaudeStreamHandler(c, resp, info, a.RequestMode)
-	} else {
-		err, usage = claude.ClaudeHandler(c, resp, a.RequestMode, info)
-	}
-	return
-}
-
-func (a *Adaptor) GetModelList() []string {
-	return ModelList
-}
-
-func (a *Adaptor) GetChannelName() string {
-	return ChannelName
-}

+ 0 - 14
relay/channel/claude_code/constants.go

@@ -1,14 +0,0 @@
-package claude_code
-
-var ModelList = []string{
-	"claude-3-5-haiku-20241022",
-	"claude-3-5-sonnet-20241022",
-	"claude-3-7-sonnet-20250219",
-	"claude-3-7-sonnet-20250219-thinking",
-	"claude-sonnet-4-20250514",
-	"claude-sonnet-4-20250514-thinking",
-	"claude-opus-4-20250514",
-	"claude-opus-4-20250514-thinking",
-}
-
-var ChannelName = "claude_code"

+ 0 - 4
relay/channel/claude_code/dto.go

@@ -1,4 +0,0 @@
-package claude_code
-
-// Claude Code uses the same DTO structures as Claude since it's based on the same API
-// This file is kept for consistency with the channel structure pattern

+ 0 - 3
relay/relay_adaptor.go

@@ -9,7 +9,6 @@ import (
 	"one-api/relay/channel/baidu"
 	"one-api/relay/channel/baidu"
 	"one-api/relay/channel/baidu_v2"
 	"one-api/relay/channel/baidu_v2"
 	"one-api/relay/channel/claude"
 	"one-api/relay/channel/claude"
-	"one-api/relay/channel/claude_code"
 	"one-api/relay/channel/cloudflare"
 	"one-api/relay/channel/cloudflare"
 	"one-api/relay/channel/cohere"
 	"one-api/relay/channel/cohere"
 	"one-api/relay/channel/coze"
 	"one-api/relay/channel/coze"
@@ -99,8 +98,6 @@ func GetAdaptor(apiType int) channel.Adaptor {
 		return &coze.Adaptor{}
 		return &coze.Adaptor{}
 	case constant.APITypeJimeng:
 	case constant.APITypeJimeng:
 		return &jimeng.Adaptor{}
 		return &jimeng.Adaptor{}
-	case constant.APITypeClaudeCode:
-		return &claude_code.Adaptor{}
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 0 - 3
router/api-router.go

@@ -120,9 +120,6 @@ func SetApiRouter(router *gin.Engine) {
 			channelRoute.POST("/batch/tag", controller.BatchSetChannelTag)
 			channelRoute.POST("/batch/tag", controller.BatchSetChannelTag)
 			channelRoute.GET("/tag/models", controller.GetTagModels)
 			channelRoute.GET("/tag/models", controller.GetTagModels)
 			channelRoute.POST("/copy/:id", controller.CopyChannel)
 			channelRoute.POST("/copy/:id", controller.CopyChannel)
-			// Claude OAuth路由
-			channelRoute.GET("/claude/oauth/url", controller.GenerateClaudeOAuthURL)
-			channelRoute.POST("/claude/oauth/exchange", controller.ExchangeClaudeOAuthCode)
 		}
 		}
 		tokenRoute := apiRouter.Group("/token")
 		tokenRoute := apiRouter.Group("/token")
 		tokenRoute.Use(middleware.UserAuth())
 		tokenRoute.Use(middleware.UserAuth())

+ 0 - 171
service/claude_oauth.go

@@ -1,171 +0,0 @@
-package service
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"os"
-	"strings"
-	"time"
-
-	"golang.org/x/oauth2"
-)
-
-const (
-	// Default OAuth configuration values
-	DefaultAuthorizeURL = "https://claude.ai/oauth/authorize"
-	DefaultTokenURL     = "https://console.anthropic.com/v1/oauth/token"
-	DefaultClientID     = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
-	DefaultRedirectURI  = "https://console.anthropic.com/oauth/code/callback"
-	DefaultScopes       = "user:inference"
-)
-
-// getOAuthValues returns OAuth configuration values from environment variables or defaults
-func getOAuthValues() (authorizeURL, tokenURL, clientID, redirectURI, scopes string) {
-	authorizeURL = os.Getenv("CLAUDE_AUTHORIZE_URL")
-	if authorizeURL == "" {
-		authorizeURL = DefaultAuthorizeURL
-	}
-
-	tokenURL = os.Getenv("CLAUDE_TOKEN_URL")
-	if tokenURL == "" {
-		tokenURL = DefaultTokenURL
-	}
-
-	clientID = os.Getenv("CLAUDE_CLIENT_ID")
-	if clientID == "" {
-		clientID = DefaultClientID
-	}
-
-	redirectURI = os.Getenv("CLAUDE_REDIRECT_URI")
-	if redirectURI == "" {
-		redirectURI = DefaultRedirectURI
-	}
-
-	scopes = os.Getenv("CLAUDE_SCOPES")
-	if scopes == "" {
-		scopes = DefaultScopes
-	}
-
-	return
-}
-
-type OAuth2Credentials struct {
-	AuthURL       string `json:"auth_url"`
-	CodeVerifier  string `json:"code_verifier"`
-	State         string `json:"state"`
-	CodeChallenge string `json:"code_challenge"`
-}
-
-// GetClaudeOAuthConfig returns the Claude OAuth2 configuration
-func GetClaudeOAuthConfig() *oauth2.Config {
-	authorizeURL, tokenURL, clientID, redirectURI, scopes := getOAuthValues()
-
-	return &oauth2.Config{
-		ClientID:    clientID,
-		RedirectURL: redirectURI,
-		Scopes:      strings.Split(scopes, " "),
-		Endpoint: oauth2.Endpoint{
-			AuthURL:  authorizeURL,
-			TokenURL: tokenURL,
-		},
-	}
-}
-
-// getOAuthConfig is kept for backward compatibility
-func getOAuthConfig() *oauth2.Config {
-	return GetClaudeOAuthConfig()
-}
-
-// GenerateOAuthParams generates OAuth authorization URL and related parameters
-func GenerateOAuthParams() (*OAuth2Credentials, error) {
-	config := getOAuthConfig()
-
-	// Generate PKCE parameters
-	codeVerifier := oauth2.GenerateVerifier()
-	state := oauth2.GenerateVerifier() // Reuse generator as state
-
-	// Generate authorization URL
-	authURL := config.AuthCodeURL(state,
-		oauth2.S256ChallengeOption(codeVerifier),
-		oauth2.SetAuthURLParam("code", "true"), // Claude-specific parameter
-	)
-
-	return &OAuth2Credentials{
-		AuthURL:       authURL,
-		CodeVerifier:  codeVerifier,
-		State:         state,
-		CodeChallenge: oauth2.S256ChallengeFromVerifier(codeVerifier),
-	}, nil
-}
-
-// ExchangeCode
-func ExchangeCode(authorizationCode, codeVerifier, state string, client *http.Client) (*oauth2.Token, error) {
-	config := getOAuthConfig()
-
-	if strings.Contains(authorizationCode, "#") {
-		parts := strings.Split(authorizationCode, "#")
-		if len(parts) > 0 {
-			authorizationCode = parts[0]
-		}
-	}
-
-	ctx := context.Background()
-	if client != nil {
-		ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
-	}
-
-	token, err := config.Exchange(ctx, authorizationCode,
-		oauth2.VerifierOption(codeVerifier),
-		oauth2.SetAuthURLParam("state", state),
-	)
-	if err != nil {
-		return nil, fmt.Errorf("token exchange failed: %w", err)
-	}
-
-	return token, nil
-}
-
-func ParseAuthorizationCode(input string) (string, error) {
-	if input == "" {
-		return "", fmt.Errorf("please provide a valid authorization code")
-	}
-	// URLs are not allowed
-	if strings.Contains(input, "http") || strings.Contains(input, "https") {
-		return "", fmt.Errorf("authorization code cannot contain URLs")
-	}
-
-	return input, nil
-}
-
-// GetClaudeHTTPClient returns a configured HTTP client for Claude OAuth operations
-func GetClaudeHTTPClient() *http.Client {
-	return &http.Client{
-		Timeout: 30 * time.Second,
-	}
-}
-
-// RefreshClaudeToken refreshes a Claude OAuth token using the refresh token
-func RefreshClaudeToken(accessToken, refreshToken string) (*oauth2.Token, error) {
-	config := GetClaudeOAuthConfig()
-
-	// Create token from current values
-	currentToken := &oauth2.Token{
-		AccessToken:  accessToken,
-		RefreshToken: refreshToken,
-		TokenType:    "Bearer",
-	}
-
-	ctx := context.Background()
-	if client := GetClaudeHTTPClient(); client != nil {
-		ctx = context.WithValue(ctx, oauth2.HTTPClient, client)
-	}
-
-	// Refresh the token
-	newToken, err := config.TokenSource(ctx, currentToken).Token()
-	if err != nil {
-		return nil, fmt.Errorf("failed to refresh Claude token: %w", err)
-	}
-
-	return newToken, nil
-}

+ 0 - 94
service/claude_token_refresh.go

@@ -1,94 +0,0 @@
-package service
-
-import (
-	"fmt"
-	"one-api/common"
-	"one-api/constant"
-	"one-api/model"
-	"strings"
-	"time"
-
-	"github.com/bytedance/gopkg/util/gopool"
-)
-
-// StartClaudeTokenRefreshScheduler starts the scheduled token refresh for Claude Code channels
-func StartClaudeTokenRefreshScheduler() {
-	ticker := time.NewTicker(5 * time.Minute)
-	gopool.Go(func() {
-		defer ticker.Stop()
-		for range ticker.C {
-			RefreshClaudeCodeTokens()
-		}
-	})
-	common.SysLog("Claude Code token refresh scheduler started (5 minute interval)")
-}
-
-// RefreshClaudeCodeTokens refreshes tokens for all active Claude Code channels
-func RefreshClaudeCodeTokens() {
-	var channels []model.Channel
-
-	// Get all active Claude Code channels
-	err := model.DB.Where("type = ? AND status = ?", constant.ChannelTypeClaudeCode, common.ChannelStatusEnabled).Find(&channels).Error
-	if err != nil {
-		common.SysError("Failed to get Claude Code channels: " + err.Error())
-		return
-	}
-
-	refreshCount := 0
-	for _, channel := range channels {
-		if refreshTokenForChannel(&channel) {
-			refreshCount++
-		}
-	}
-
-	if refreshCount > 0 {
-		common.SysLog(fmt.Sprintf("Successfully refreshed %d Claude Code channel tokens", refreshCount))
-	}
-}
-
-// refreshTokenForChannel attempts to refresh token for a single channel
-func refreshTokenForChannel(channel *model.Channel) bool {
-	// Parse key in format: accesstoken|refreshtoken
-	if channel.Key == "" || !strings.Contains(channel.Key, "|") {
-		common.SysError(fmt.Sprintf("Channel %d has invalid key format, expected accesstoken|refreshtoken", channel.Id))
-		return false
-	}
-
-	parts := strings.Split(channel.Key, "|")
-	if len(parts) < 2 {
-		common.SysError(fmt.Sprintf("Channel %d has invalid key format, expected accesstoken|refreshtoken", channel.Id))
-		return false
-	}
-
-	accessToken := parts[0]
-	refreshToken := parts[1]
-
-	if refreshToken == "" {
-		common.SysError(fmt.Sprintf("Channel %d has empty refresh token", channel.Id))
-		return false
-	}
-
-	// Check if token needs refresh (refresh 30 minutes before expiry)
-	// if !shouldRefreshToken(accessToken) {
-	// 	return false
-	// }
-
-	// Use shared refresh function
-	newToken, err := RefreshClaudeToken(accessToken, refreshToken)
-	if err != nil {
-		common.SysError(fmt.Sprintf("Failed to refresh token for channel %d: %s", channel.Id, err.Error()))
-		return false
-	}
-
-	// Update channel with new tokens
-	newKey := fmt.Sprintf("%s|%s", newToken.AccessToken, newToken.RefreshToken)
-
-	err = model.DB.Model(channel).Update("key", newKey).Error
-	if err != nil {
-		common.SysError(fmt.Sprintf("Failed to update channel %d with new token: %s", channel.Id, err.Error()))
-		return false
-	}
-
-	common.SysLog(fmt.Sprintf("Successfully refreshed token for Claude Code channel %d (%s)", channel.Id, channel.Name))
-	return true
-}

+ 0 - 3
setting/operation_setting/operation_setting.go

@@ -13,9 +13,6 @@ var AutomaticDisableKeywords = []string{
 	"The security token included in the request is invalid",
 	"The security token included in the request is invalid",
 	"Operation not allowed",
 	"Operation not allowed",
 	"Your account is not authorized",
 	"Your account is not authorized",
-	// Claude Code
-	"Invalid bearer token",
-	"OAuth authentication is currently not allowed for this endpoint",
 }
 }
 
 
 func AutomaticDisableKeywordsToString() string {
 func AutomaticDisableKeywordsToString() string {

+ 26 - 231
web/src/components/table/channels/modals/EditChannelModal.jsx

@@ -17,6 +17,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact [email protected]
 For commercial licensing, please contact [email protected]
 */
 */
 
 
+import React, { useEffect, useState, useRef, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
 import {
 import {
   API,
   API,
   showError,
   showError,
@@ -24,42 +26,38 @@ import {
   showSuccess,
   showSuccess,
   verifyJSON,
   verifyJSON,
 } from '../../../../helpers';
 } from '../../../../helpers';
+import { useIsMobile } from '../../../../hooks/common/useIsMobile.js';
+import { CHANNEL_OPTIONS } from '../../../../constants';
 import {
 import {
-  Avatar,
-  Banner,
-  Button,
-  Card,
-  Checkbox,
-  Col,
-  Form,
-  Highlight,
-  ImagePreview,
-  Input,
-  Modal,
-  Row,
   SideSheet,
   SideSheet,
   Space,
   Space,
   Spin,
   Spin,
-  Tag,
+  Button,
   Typography,
   Typography,
+  Checkbox,
+  Banner,
+  Modal,
+  ImagePreview,
+  Card,
+  Tag,
+  Avatar,
+  Form,
+  Row,
+  Col,
+  Highlight,
 } from '@douyinfe/semi-ui';
 } from '@douyinfe/semi-ui';
 import { getChannelModels, copy, getChannelIcon, getModelCategories, selectFilter } from '../../../../helpers';
 import { getChannelModels, copy, getChannelIcon, getModelCategories, selectFilter } from '../../../../helpers';
 import ModelSelectModal from './ModelSelectModal';
 import ModelSelectModal from './ModelSelectModal';
 import JSONEditor from '../../../common/JSONEditor';
 import JSONEditor from '../../../common/JSONEditor';
-import { CHANNEL_OPTIONS, CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT } from '../../../../constants';
 import {
 import {
-  IconBolt,
-  IconClose,
-  IconCode,
-  IconGlobe,
   IconSave,
   IconSave,
+  IconClose,
   IconServer,
   IconServer,
   IconSetting,
   IconSetting,
+  IconCode,
+  IconGlobe,
+  IconBolt,
 } from '@douyinfe/semi-icons';
 } from '@douyinfe/semi-icons';
-import React, { useEffect, useMemo, useRef, useState } from 'react';
-
-import { useIsMobile } from '../../../../hooks/common/useIsMobile.js';
-import { useTranslation } from 'react-i18next';
 
 
 const { Text, Title } = Typography;
 const { Text, Title } = Typography;
 
 
@@ -95,8 +93,6 @@ function type2secretPrompt(type) {
       return '按照如下格式输入: AccessKey|SecretKey, 如果上游是New API,则直接输ApiKey';
       return '按照如下格式输入: AccessKey|SecretKey, 如果上游是New API,则直接输ApiKey';
     case 51:
     case 51:
       return '按照如下格式输入: Access Key ID|Secret Access Key';
       return '按照如下格式输入: Access Key ID|Secret Access Key';
-    case 53:
-      return '按照如下格式输入:AccessToken|RefreshToken';
     default:
     default:
       return '请输入渠道对应的鉴权密钥';
       return '请输入渠道对应的鉴权密钥';
   }
   }
@@ -149,10 +145,6 @@ const EditChannelModal = (props) => {
   const [customModel, setCustomModel] = useState('');
   const [customModel, setCustomModel] = useState('');
   const [modalImageUrl, setModalImageUrl] = useState('');
   const [modalImageUrl, setModalImageUrl] = useState('');
   const [isModalOpenurl, setIsModalOpenurl] = useState(false);
   const [isModalOpenurl, setIsModalOpenurl] = useState(false);
-  const [showOAuthModal, setShowOAuthModal] = useState(false);
-  const [authorizationCode, setAuthorizationCode] = useState('');
-  const [oauthParams, setOauthParams] = useState(null);
-  const [isExchangingCode, setIsExchangingCode] = useState(false);
   const [modelModalVisible, setModelModalVisible] = useState(false);
   const [modelModalVisible, setModelModalVisible] = useState(false);
   const [fetchedModels, setFetchedModels] = useState([]);
   const [fetchedModels, setFetchedModels] = useState([]);
   const formApiRef = useRef(null);
   const formApiRef = useRef(null);
@@ -361,24 +353,6 @@ const EditChannelModal = (props) => {
         data.system_prompt = '';
         data.system_prompt = '';
       }
       }
 
 
-      // 特殊处理Claude Code渠道的密钥拆分和系统提示词
-      if (data.type === 53) {
-        // 拆分密钥
-        if (data.key) {
-          const keyParts = data.key.split('|');
-          if (keyParts.length === 2) {
-            data.access_token = keyParts[0];
-            data.refresh_token = keyParts[1];
-          } else {
-            // 如果没有 | 分隔符,表示只有access token
-            data.access_token = data.key;
-            data.refresh_token = '';
-          }
-        }
-        // 强制设置固定系统提示词
-        data.system_prompt = CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT;
-      }
-
       setInputs(data);
       setInputs(data);
       if (formApiRef.current) {
       if (formApiRef.current) {
         formApiRef.current.setValues(data);
         formApiRef.current.setValues(data);
@@ -502,72 +476,6 @@ const EditChannelModal = (props) => {
     }
     }
   };
   };
 
 
-  // 生成OAuth授权URL
-  const handleGenerateOAuth = async () => {
-    try {
-      setLoading(true);
-      const res = await API.get('/api/channel/claude/oauth/url');
-      if (res.data.success) {
-        setOauthParams(res.data.data);
-        setShowOAuthModal(true);
-        showSuccess(t('OAuth授权URL生成成功'));
-      } else {
-        showError(res.data.message || t('生成OAuth授权URL失败'));
-      }
-    } catch (error) {
-      showError(t('生成OAuth授权URL失败:') + error.message);
-    } finally {
-      setLoading(false);
-    }
-  };
-
-  // 交换授权码
-  const handleExchangeCode = async () => {
-    if (!authorizationCode.trim()) {
-      showError(t('请输入授权码'));
-      return;
-    }
-
-    if (!oauthParams) {
-      showError(t('OAuth参数丢失,请重新生成'));
-      return;
-    }
-
-    try {
-      setIsExchangingCode(true);
-      const res = await API.post('/api/channel/claude/oauth/exchange', {
-        authorization_code: authorizationCode,
-        code_verifier: oauthParams.code_verifier,
-        state: oauthParams.state,
-      });
-
-      if (res.data.success) {
-        const tokenData = res.data.data;
-        // 自动填充access token和refresh token
-        handleInputChange('access_token', tokenData.access_token);
-        handleInputChange('refresh_token', tokenData.refresh_token);
-        handleInputChange('key', `${tokenData.access_token}|${tokenData.refresh_token}`);
-
-        // 更新表单字段
-        if (formApiRef.current) {
-          formApiRef.current.setValue('access_token', tokenData.access_token);
-          formApiRef.current.setValue('refresh_token', tokenData.refresh_token);
-        }
-
-        setShowOAuthModal(false);
-        setAuthorizationCode('');
-        setOauthParams(null);
-        showSuccess(t('授权码交换成功,已自动填充tokens'));
-      } else {
-        showError(res.data.message || t('授权码交换失败'));
-      }
-    } catch (error) {
-      showError(t('授权码交换失败:') + error.message);
-    } finally {
-      setIsExchangingCode(false);
-    }
-  };
-
   useEffect(() => {
   useEffect(() => {
     const modelMap = new Map();
     const modelMap = new Map();
 
 
@@ -880,7 +788,7 @@ const EditChannelModal = (props) => {
   const batchExtra = batchAllowed ? (
   const batchExtra = batchAllowed ? (
     <Space>
     <Space>
       <Checkbox
       <Checkbox
-        disabled={isEdit || inputs.type === 53}
+        disabled={isEdit}
         checked={batch}
         checked={batch}
         onChange={(e) => {
         onChange={(e) => {
           const checked = e.target.checked;
           const checked = e.target.checked;
@@ -1216,49 +1124,6 @@ const EditChannelModal = (props) => {
                             />
                             />
                           )}
                           )}
                         </>
                         </>
-                      ) : inputs.type === 53 ? (
-                        <>
-                          <Form.Input
-                            field='access_token'
-                            label={isEdit ? t('Access Token(编辑模式下,保存的密钥不会显示)') : t('Access Token')}
-                            placeholder={t('sk-ant-xxx')}
-                            rules={isEdit ? [] : [{ required: true, message: t('请输入Access Token') }]}
-                            autoComplete='new-password'
-                            onChange={(value) => {
-                              handleInputChange('access_token', value);
-                              // 同时更新key字段,格式为access_token|refresh_token
-                              const refreshToken = inputs.refresh_token || '';
-                              handleInputChange('key', `${value}|${refreshToken}`);
-                            }}
-                            suffix={
-                              <Button
-                                size="small"
-                                type="primary"
-                                theme="light"
-                                onClick={handleGenerateOAuth}
-                              >
-                                {t('生成OAuth授权码')}
-                              </Button>
-                            }
-                            extraText={batchExtra}
-                            showClear
-                          />
-                          <Form.Input
-                            field='refresh_token'
-                            label={isEdit ? t('Refresh Token(编辑模式下,保存的密钥不会显示)') : t('Refresh Token')}
-                            placeholder={t('sk-ant-xxx(可选)')}
-                            rules={[]}
-                            autoComplete='new-password'
-                            onChange={(value) => {
-                              handleInputChange('refresh_token', value);
-                              // 同时更新key字段,格式为access_token|refresh_token
-                              const accessToken = inputs.access_token || '';
-                              handleInputChange('key', `${accessToken}|${value}`);
-                            }}
-                            extraText={batchExtra}
-                            showClear
-                          />
-                        </>
                       ) : (
                       ) : (
                         <Form.Input
                         <Form.Input
                           field='key'
                           field='key'
@@ -1767,19 +1632,11 @@ const EditChannelModal = (props) => {
                   <Form.TextArea
                   <Form.TextArea
                     field='system_prompt'
                     field='system_prompt'
                     label={t('系统提示词')}
                     label={t('系统提示词')}
-                    placeholder={inputs.type === 53 ? CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT : t('输入系统提示词,用户的系统提示词将优先于此设置')}
-                    onChange={(value) => {
-                      if (inputs.type === 53) {
-                        // Claude Code渠道系统提示词固定,不允许修改
-                        return;
-                      }
-                      handleChannelSettingsChange('system_prompt', value);
-                    }}
-                    disabled={inputs.type === 53}
-                    value={inputs.type === 53 ? CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT : undefined}
+                    placeholder={t('输入系统提示词,用户的系统提示词将优先于此设置')}
+                    onChange={(value) => handleChannelSettingsChange('system_prompt', value)}
                     autosize
                     autosize
-                    showClear={inputs.type !== 53}
-                    extraText={inputs.type === 53 ? t('Claude Code渠道系统提示词固定为官方CLI身份,不可修改') : t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')}
+                    showClear
+                    extraText={t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')}
                   />
                   />
                 </Card>
                 </Card>
               </div>
               </div>
@@ -1803,70 +1660,8 @@ const EditChannelModal = (props) => {
         }}
         }}
         onCancel={() => setModelModalVisible(false)}
         onCancel={() => setModelModalVisible(false)}
       />
       />
-
-      {/* OAuth Authorization Modal */}
-      <Modal
-        title={t('生成Claude Code OAuth授权码')}
-        visible={showOAuthModal}
-        onCancel={() => {
-          setShowOAuthModal(false);
-          setAuthorizationCode('');
-          setOauthParams(null);
-        }}
-        onOk={handleExchangeCode}
-        okText={isExchangingCode ? t('交换中...') : t('确认')}
-        cancelText={t('取消')}
-        confirmLoading={isExchangingCode}
-        width={600}
-      >
-        <div className="space-y-4">
-          <div>
-            <Text className="text-sm font-medium mb-2 block">{t('请访问以下授权地址:')}</Text>
-            <div className="p-3 bg-gray-50 rounded-lg border">
-              <Text
-                link
-                underline
-                className="text-sm font-mono break-all cursor-pointer text-blue-600 hover:text-blue-800"
-                onClick={() => {
-                  if (oauthParams?.auth_url) {
-                    window.open(oauthParams.auth_url, '_blank');
-                  }
-                }}
-              >
-                {oauthParams?.auth_url || t('正在生成授权地址...')}
-              </Text>
-              <div className="mt-2">
-                <Text
-                  copyable={{ content: oauthParams?.auth_url }}
-                  type="tertiary"
-                  size="small"
-                >
-                  {t('复制链接')}
-                </Text>
-              </div>
-            </div>
-          </div>
-
-          <div>
-            <Text className="text-sm font-medium mb-2 block">{t('授权后,请将获得的授权码粘贴到下方:')}</Text>
-            <Input
-              value={authorizationCode}
-              onChange={setAuthorizationCode}
-              placeholder={t('请输入授权码')}
-              showClear
-              style={{ width: '100%' }}
-            />
-          </div>
-
-          <Banner
-            type="info"
-            description={t('获得授权码后,系统将自动换取access token和refresh token并填充到表单中。')}
-            className="!rounded-lg"
-          />
-        </div>
-      </Modal>
     </>
     </>
   );
   );
 };
 };
 
 
-export default EditChannelModal;
+export default EditChannelModal; 

+ 0 - 8
web/src/constants/channel.constants.js

@@ -159,14 +159,6 @@ export const CHANNEL_OPTIONS = [
     color: 'purple',
     color: 'purple',
     label: 'Vidu',
     label: 'Vidu',
   },
   },
-  {
-    value: 53,
-    color: 'indigo',
-    label: 'Claude Code',
-  },
 ];
 ];
 
 
 export const MODEL_TABLE_PAGE_SIZE = 10;
 export const MODEL_TABLE_PAGE_SIZE = 10;
-
-// Claude Code 相关常量
-export const CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";

+ 0 - 1
web/src/helpers/render.js

@@ -358,7 +358,6 @@ export function getChannelIcon(channelType) {
       return <Ollama size={iconSize} />;
       return <Ollama size={iconSize} />;
     case 14: // Anthropic Claude
     case 14: // Anthropic Claude
     case 33: // AWS Claude
     case 33: // AWS Claude
-    case 53: // Claude Code
       return <Claude.Color size={iconSize} />;
       return <Claude.Color size={iconSize} />;
     case 41: // Vertex AI
     case 41: // Vertex AI
       return <Gemini.Color size={iconSize} />;
       return <Gemini.Color size={iconSize} />;