From f995e31d04f4660fce439aa6a4eab24774f53edd Mon Sep 17 00:00:00 2001 From: Seefs <40468931+seefs001@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:08:16 +0800 Subject: [PATCH] Revert "feat: add Claude Code channel support with OAuth integration" --- common/api_type.go | 2 - constant/api_type.go | 1 - constant/channel.go | 2 - controller/claude_oauth.go | 73 ----- go.mod | 1 - go.sum | 2 - main.go | 3 - relay/channel/claude_code/adaptor.go | 158 ----------- relay/channel/claude_code/constants.go | 14 - relay/channel/claude_code/dto.go | 4 - relay/relay_adaptor.go | 3 - router/api-router.go | 3 - service/claude_oauth.go | 171 ------------ service/claude_token_refresh.go | 94 ------- .../operation_setting/operation_setting.go | 3 - .../channels/modals/EditChannelModal.jsx | 257 ++---------------- web/src/constants/channel.constants.js | 8 - web/src/helpers/render.js | 1 - 18 files changed, 26 insertions(+), 774 deletions(-) delete mode 100644 controller/claude_oauth.go delete mode 100644 relay/channel/claude_code/adaptor.go delete mode 100644 relay/channel/claude_code/constants.go delete mode 100644 relay/channel/claude_code/dto.go delete mode 100644 service/claude_oauth.go delete mode 100644 service/claude_token_refresh.go diff --git a/common/api_type.go b/common/api_type.go index c31f2e2c..f045866a 100644 --- a/common/api_type.go +++ b/common/api_type.go @@ -65,8 +65,6 @@ func ChannelType2APIType(channelType int) (int, bool) { apiType = constant.APITypeCoze case constant.ChannelTypeJimeng: apiType = constant.APITypeJimeng - case constant.ChannelTypeClaudeCode: - apiType = constant.APITypeClaudeCode } if apiType == -1 { return constant.APITypeOpenAI, false diff --git a/constant/api_type.go b/constant/api_type.go index bca5e311..6ba5f257 100644 --- a/constant/api_type.go +++ b/constant/api_type.go @@ -31,6 +31,5 @@ const ( APITypeXai APITypeCoze APITypeJimeng - APITypeClaudeCode APITypeDummy // this one is only for count, do not add any channel after this ) diff --git a/constant/channel.go b/constant/channel.go index cc71caf3..2e1cc5b0 100644 --- a/constant/channel.go +++ b/constant/channel.go @@ -50,7 +50,6 @@ const ( ChannelTypeKling = 50 ChannelTypeJimeng = 51 ChannelTypeVidu = 52 - ChannelTypeClaudeCode = 53 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://visual.volcengineapi.com", //51 "https://api.vidu.cn", //52 - "https://api.anthropic.com", //53 } diff --git a/controller/claude_oauth.go b/controller/claude_oauth.go deleted file mode 100644 index de711b93..00000000 --- a/controller/claude_oauth.go +++ /dev/null @@ -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, - }) -} diff --git a/go.mod b/go.mod index bae7a4e8..94873c88 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,6 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/arch v0.12.0 // 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/text v0.22.0 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/go.sum b/go.sum index 8ded1a03..74eecd4c 100644 --- a/go.sum +++ b/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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= diff --git a/main.go b/main.go index f49995c2..ca3da601 100644 --- a/main.go +++ b/main.go @@ -86,9 +86,6 @@ func main() { // 数据看板 go model.UpdateQuotaData() - // Start Claude Code token refresh scheduler - service.StartClaudeTokenRefreshScheduler() - if os.Getenv("CHANNEL_UPDATE_FREQUENCY") != "" { frequency, err := strconv.Atoi(os.Getenv("CHANNEL_UPDATE_FREQUENCY")) if err != nil { diff --git a/relay/channel/claude_code/adaptor.go b/relay/channel/claude_code/adaptor.go deleted file mode 100644 index 7a0be927..00000000 --- a/relay/channel/claude_code/adaptor.go +++ /dev/null @@ -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 -} diff --git a/relay/channel/claude_code/constants.go b/relay/channel/claude_code/constants.go deleted file mode 100644 index 82695be2..00000000 --- a/relay/channel/claude_code/constants.go +++ /dev/null @@ -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" diff --git a/relay/channel/claude_code/dto.go b/relay/channel/claude_code/dto.go deleted file mode 100644 index 68bb9269..00000000 --- a/relay/channel/claude_code/dto.go +++ /dev/null @@ -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 \ No newline at end of file diff --git a/relay/relay_adaptor.go b/relay/relay_adaptor.go index 2456c77f..cc9c5bbb 100644 --- a/relay/relay_adaptor.go +++ b/relay/relay_adaptor.go @@ -9,7 +9,6 @@ import ( "one-api/relay/channel/baidu" "one-api/relay/channel/baidu_v2" "one-api/relay/channel/claude" - "one-api/relay/channel/claude_code" "one-api/relay/channel/cloudflare" "one-api/relay/channel/cohere" "one-api/relay/channel/coze" @@ -99,8 +98,6 @@ func GetAdaptor(apiType int) channel.Adaptor { return &coze.Adaptor{} case constant.APITypeJimeng: return &jimeng.Adaptor{} - case constant.APITypeClaudeCode: - return &claude_code.Adaptor{} } return nil } diff --git a/router/api-router.go b/router/api-router.go index 702fc99f..bc49803a 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -120,9 +120,6 @@ func SetApiRouter(router *gin.Engine) { channelRoute.POST("/batch/tag", controller.BatchSetChannelTag) channelRoute.GET("/tag/models", controller.GetTagModels) 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.Use(middleware.UserAuth()) diff --git a/service/claude_oauth.go b/service/claude_oauth.go deleted file mode 100644 index b0e1f84d..00000000 --- a/service/claude_oauth.go +++ /dev/null @@ -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 -} diff --git a/service/claude_token_refresh.go b/service/claude_token_refresh.go deleted file mode 100644 index 5dc35367..00000000 --- a/service/claude_token_refresh.go +++ /dev/null @@ -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 -} diff --git a/setting/operation_setting/operation_setting.go b/setting/operation_setting/operation_setting.go index 29b77d66..ef330d1a 100644 --- a/setting/operation_setting/operation_setting.go +++ b/setting/operation_setting/operation_setting.go @@ -13,9 +13,6 @@ var AutomaticDisableKeywords = []string{ "The security token included in the request is invalid", "Operation not allowed", "Your account is not authorized", - // Claude Code - "Invalid bearer token", - "OAuth authentication is currently not allowed for this endpoint", } func AutomaticDisableKeywordsToString() string { diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index cb09b3c9..37e9af75 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -17,6 +17,8 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ +import React, { useEffect, useState, useRef, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { API, showError, @@ -24,42 +26,38 @@ import { showSuccess, verifyJSON, } from '../../../../helpers'; +import { useIsMobile } from '../../../../hooks/common/useIsMobile.js'; +import { CHANNEL_OPTIONS } from '../../../../constants'; import { - Avatar, - Banner, - Button, - Card, - Checkbox, - Col, - Form, - Highlight, - ImagePreview, - Input, - Modal, - Row, SideSheet, Space, Spin, - Tag, + Button, Typography, + Checkbox, + Banner, + Modal, + ImagePreview, + Card, + Tag, + Avatar, + Form, + Row, + Col, + Highlight, } from '@douyinfe/semi-ui'; import { getChannelModels, copy, getChannelIcon, getModelCategories, selectFilter } from '../../../../helpers'; import ModelSelectModal from './ModelSelectModal'; import JSONEditor from '../../../common/JSONEditor'; -import { CHANNEL_OPTIONS, CLAUDE_CODE_DEFAULT_SYSTEM_PROMPT } from '../../../../constants'; import { - IconBolt, - IconClose, - IconCode, - IconGlobe, IconSave, + IconClose, IconServer, IconSetting, + IconCode, + IconGlobe, + IconBolt, } 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; @@ -95,8 +93,6 @@ function type2secretPrompt(type) { return '按照如下格式输入: AccessKey|SecretKey, 如果上游是New API,则直接输ApiKey'; case 51: return '按照如下格式输入: Access Key ID|Secret Access Key'; - case 53: - return '按照如下格式输入:AccessToken|RefreshToken'; default: return '请输入渠道对应的鉴权密钥'; } @@ -149,10 +145,6 @@ const EditChannelModal = (props) => { const [customModel, setCustomModel] = useState(''); const [modalImageUrl, setModalImageUrl] = useState(''); 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 [fetchedModels, setFetchedModels] = useState([]); const formApiRef = useRef(null); @@ -361,24 +353,6 @@ const EditChannelModal = (props) => { 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); if (formApiRef.current) { 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(() => { const modelMap = new Map(); @@ -880,7 +788,7 @@ const EditChannelModal = (props) => { const batchExtra = batchAllowed ? ( { const checked = e.target.checked; @@ -1216,49 +1124,6 @@ const EditChannelModal = (props) => { /> )} > - ) : inputs.type === 53 ? ( - <> - { - handleInputChange('access_token', value); - // 同时更新key字段,格式为access_token|refresh_token - const refreshToken = inputs.refresh_token || ''; - handleInputChange('key', `${value}|${refreshToken}`); - }} - suffix={ - - {t('生成OAuth授权码')} - - } - extraText={batchExtra} - showClear - /> - { - handleInputChange('refresh_token', value); - // 同时更新key字段,格式为access_token|refresh_token - const accessToken = inputs.access_token || ''; - handleInputChange('key', `${accessToken}|${value}`); - }} - extraText={batchExtra} - showClear - /> - > ) : ( { { - 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 - showClear={inputs.type !== 53} - extraText={inputs.type === 53 ? t('Claude Code渠道系统提示词固定为官方CLI身份,不可修改') : t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')} + showClear + extraText={t('用户优先:如果用户在请求中指定了系统提示词,将优先使用用户的设置')} /> @@ -1803,70 +1660,8 @@ const EditChannelModal = (props) => { }} onCancel={() => setModelModalVisible(false)} /> - - {/* OAuth Authorization Modal */} - { - setShowOAuthModal(false); - setAuthorizationCode(''); - setOauthParams(null); - }} - onOk={handleExchangeCode} - okText={isExchangingCode ? t('交换中...') : t('确认')} - cancelText={t('取消')} - confirmLoading={isExchangingCode} - width={600} - > - - - {t('请访问以下授权地址:')} - - { - if (oauthParams?.auth_url) { - window.open(oauthParams.auth_url, '_blank'); - } - }} - > - {oauthParams?.auth_url || t('正在生成授权地址...')} - - - - {t('复制链接')} - - - - - - - {t('授权后,请将获得的授权码粘贴到下方:')} - - - - - - > ); }; -export default EditChannelModal; \ No newline at end of file +export default EditChannelModal; \ No newline at end of file diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js index 6035548e..43372a25 100644 --- a/web/src/constants/channel.constants.js +++ b/web/src/constants/channel.constants.js @@ -159,14 +159,6 @@ export const CHANNEL_OPTIONS = [ color: 'purple', label: 'Vidu', }, - { - value: 53, - color: 'indigo', - label: 'Claude Code', - }, ]; 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."; diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 7886f03b..1178d5f9 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -358,7 +358,6 @@ export function getChannelIcon(channelType) { return ; case 14: // Anthropic Claude case 33: // AWS Claude - case 53: // Claude Code return ; case 41: // Vertex AI return ;