diff --git a/dto/channel_settings.go b/dto/channel_settings.go index 74bceb281a..72fdf460c6 100644 --- a/dto/channel_settings.go +++ b/dto/channel_settings.go @@ -24,14 +24,16 @@ const ( ) type ChannelOtherSettings struct { - AzureResponsesVersion string `json:"azure_responses_version,omitempty"` - VertexKeyType VertexKeyType `json:"vertex_key_type,omitempty"` // "json" or "api_key" - OpenRouterEnterprise *bool `json:"openrouter_enterprise,omitempty"` - ClaudeBetaQuery bool `json:"claude_beta_query,omitempty"` // Claude 渠道是否强制追加 ?beta=true - AllowServiceTier bool `json:"allow_service_tier,omitempty"` // 是否允许 service_tier 透传(默认过滤以避免额外计费) - DisableStore bool `json:"disable_store,omitempty"` // 是否禁用 store 透传(默认允许透传,禁用后可能导致 Codex 无法使用) - AllowSafetyIdentifier bool `json:"allow_safety_identifier,omitempty"` // 是否允许 safety_identifier 透传(默认过滤以保护用户隐私) - AwsKeyType AwsKeyType `json:"aws_key_type,omitempty"` + AzureResponsesVersion string `json:"azure_responses_version,omitempty"` + VertexKeyType VertexKeyType `json:"vertex_key_type,omitempty"` // "json" or "api_key" + OpenRouterEnterprise *bool `json:"openrouter_enterprise,omitempty"` + ClaudeBetaQuery bool `json:"claude_beta_query,omitempty"` // Claude 渠道是否强制追加 ?beta=true + AllowServiceTier bool `json:"allow_service_tier,omitempty"` // 是否允许 service_tier 透传(默认过滤以避免额外计费) + AllowInferenceGeo bool `json:"allow_inference_geo,omitempty"` // 是否允许 inference_geo 透传(仅 Claude,默认过滤以满足数据驻留合规) + DisableStore bool `json:"disable_store,omitempty"` // 是否禁用 store 透传(默认允许透传,禁用后可能导致 Codex 无法使用) + AllowSafetyIdentifier bool `json:"allow_safety_identifier,omitempty"` // 是否允许 safety_identifier 透传(默认过滤以保护用户隐私) + AllowIncludeObfuscation bool `json:"allow_include_obfuscation,omitempty"` // 是否允许 stream_options.include_obfuscation 透传(默认过滤以避免关闭流混淆保护) + AwsKeyType AwsKeyType `json:"aws_key_type,omitempty"` } func (s *ChannelOtherSettings) IsOpenRouterEnterprise() bool { diff --git a/dto/claude.go b/dto/claude.go index 8b6b495f63..32e31710b1 100644 --- a/dto/claude.go +++ b/dto/claude.go @@ -190,10 +190,13 @@ type ClaudeToolChoice struct { } type ClaudeRequest struct { - Model string `json:"model"` - Prompt string `json:"prompt,omitempty"` - System any `json:"system,omitempty"` - Messages []ClaudeMessage `json:"messages,omitempty"` + Model string `json:"model"` + Prompt string `json:"prompt,omitempty"` + System any `json:"system,omitempty"` + Messages []ClaudeMessage `json:"messages,omitempty"` + // InferenceGeo controls Claude data residency region. + // This field is filtered by default and can be enabled via channel setting allow_inference_geo. + InferenceGeo string `json:"inference_geo,omitempty"` MaxTokens uint `json:"max_tokens,omitempty"` MaxTokensToSample uint `json:"max_tokens_to_sample,omitempty"` StopSequences []string `json:"stop_sequences,omitempty"` @@ -210,7 +213,8 @@ type ClaudeRequest struct { Thinking *Thinking `json:"thinking,omitempty"` McpServers json.RawMessage `json:"mcp_servers,omitempty"` Metadata json.RawMessage `json:"metadata,omitempty"` - // 服务层级字段,用于指定 API 服务等级。允许透传可能导致实际计费高于预期,默认应过滤 + // ServiceTier specifies upstream service level and may affect billing. + // This field is filtered by default and can be enabled via channel setting allow_service_tier. ServiceTier string `json:"service_tier,omitempty"` } diff --git a/dto/gemini.go b/dto/gemini.go index 0fd74c639d..51490fc4c8 100644 --- a/dto/gemini.go +++ b/dto/gemini.go @@ -324,25 +324,26 @@ type GeminiChatTool struct { } type GeminiChatGenerationConfig struct { - Temperature *float64 `json:"temperature,omitempty"` - TopP float64 `json:"topP,omitempty"` - TopK float64 `json:"topK,omitempty"` - MaxOutputTokens uint `json:"maxOutputTokens,omitempty"` - CandidateCount int `json:"candidateCount,omitempty"` - StopSequences []string `json:"stopSequences,omitempty"` - ResponseMimeType string `json:"responseMimeType,omitempty"` - ResponseSchema any `json:"responseSchema,omitempty"` - ResponseJsonSchema json.RawMessage `json:"responseJsonSchema,omitempty"` - PresencePenalty *float32 `json:"presencePenalty,omitempty"` - FrequencyPenalty *float32 `json:"frequencyPenalty,omitempty"` - ResponseLogprobs bool `json:"responseLogprobs,omitempty"` - Logprobs *int32 `json:"logprobs,omitempty"` - MediaResolution MediaResolution `json:"mediaResolution,omitempty"` - Seed int64 `json:"seed,omitempty"` - ResponseModalities []string `json:"responseModalities,omitempty"` - ThinkingConfig *GeminiThinkingConfig `json:"thinkingConfig,omitempty"` - SpeechConfig json.RawMessage `json:"speechConfig,omitempty"` // RawMessage to allow flexible speech config - ImageConfig json.RawMessage `json:"imageConfig,omitempty"` // RawMessage to allow flexible image config + Temperature *float64 `json:"temperature,omitempty"` + TopP float64 `json:"topP,omitempty"` + TopK float64 `json:"topK,omitempty"` + MaxOutputTokens uint `json:"maxOutputTokens,omitempty"` + CandidateCount int `json:"candidateCount,omitempty"` + StopSequences []string `json:"stopSequences,omitempty"` + ResponseMimeType string `json:"responseMimeType,omitempty"` + ResponseSchema any `json:"responseSchema,omitempty"` + ResponseJsonSchema json.RawMessage `json:"responseJsonSchema,omitempty"` + PresencePenalty *float32 `json:"presencePenalty,omitempty"` + FrequencyPenalty *float32 `json:"frequencyPenalty,omitempty"` + ResponseLogprobs bool `json:"responseLogprobs,omitempty"` + Logprobs *int32 `json:"logprobs,omitempty"` + EnableEnhancedCivicAnswers *bool `json:"enableEnhancedCivicAnswers,omitempty"` + MediaResolution MediaResolution `json:"mediaResolution,omitempty"` + Seed int64 `json:"seed,omitempty"` + ResponseModalities []string `json:"responseModalities,omitempty"` + ThinkingConfig *GeminiThinkingConfig `json:"thinkingConfig,omitempty"` + SpeechConfig json.RawMessage `json:"speechConfig,omitempty"` // RawMessage to allow flexible speech config + ImageConfig json.RawMessage `json:"imageConfig,omitempty"` // RawMessage to allow flexible image config } // UnmarshalJSON allows GeminiChatGenerationConfig to accept both snake_case and camelCase fields. @@ -350,22 +351,23 @@ func (c *GeminiChatGenerationConfig) UnmarshalJSON(data []byte) error { type Alias GeminiChatGenerationConfig var aux struct { Alias - TopPSnake float64 `json:"top_p,omitempty"` - TopKSnake float64 `json:"top_k,omitempty"` - MaxOutputTokensSnake uint `json:"max_output_tokens,omitempty"` - CandidateCountSnake int `json:"candidate_count,omitempty"` - StopSequencesSnake []string `json:"stop_sequences,omitempty"` - ResponseMimeTypeSnake string `json:"response_mime_type,omitempty"` - ResponseSchemaSnake any `json:"response_schema,omitempty"` - ResponseJsonSchemaSnake json.RawMessage `json:"response_json_schema,omitempty"` - PresencePenaltySnake *float32 `json:"presence_penalty,omitempty"` - FrequencyPenaltySnake *float32 `json:"frequency_penalty,omitempty"` - ResponseLogprobsSnake bool `json:"response_logprobs,omitempty"` - MediaResolutionSnake MediaResolution `json:"media_resolution,omitempty"` - ResponseModalitiesSnake []string `json:"response_modalities,omitempty"` - ThinkingConfigSnake *GeminiThinkingConfig `json:"thinking_config,omitempty"` - SpeechConfigSnake json.RawMessage `json:"speech_config,omitempty"` - ImageConfigSnake json.RawMessage `json:"image_config,omitempty"` + TopPSnake float64 `json:"top_p,omitempty"` + TopKSnake float64 `json:"top_k,omitempty"` + MaxOutputTokensSnake uint `json:"max_output_tokens,omitempty"` + CandidateCountSnake int `json:"candidate_count,omitempty"` + StopSequencesSnake []string `json:"stop_sequences,omitempty"` + ResponseMimeTypeSnake string `json:"response_mime_type,omitempty"` + ResponseSchemaSnake any `json:"response_schema,omitempty"` + ResponseJsonSchemaSnake json.RawMessage `json:"response_json_schema,omitempty"` + PresencePenaltySnake *float32 `json:"presence_penalty,omitempty"` + FrequencyPenaltySnake *float32 `json:"frequency_penalty,omitempty"` + ResponseLogprobsSnake bool `json:"response_logprobs,omitempty"` + EnableEnhancedCivicAnswersSnake *bool `json:"enable_enhanced_civic_answers,omitempty"` + MediaResolutionSnake MediaResolution `json:"media_resolution,omitempty"` + ResponseModalitiesSnake []string `json:"response_modalities,omitempty"` + ThinkingConfigSnake *GeminiThinkingConfig `json:"thinking_config,omitempty"` + SpeechConfigSnake json.RawMessage `json:"speech_config,omitempty"` + ImageConfigSnake json.RawMessage `json:"image_config,omitempty"` } if err := common.Unmarshal(data, &aux); err != nil { @@ -408,6 +410,9 @@ func (c *GeminiChatGenerationConfig) UnmarshalJSON(data []byte) error { if aux.ResponseLogprobsSnake { c.ResponseLogprobs = aux.ResponseLogprobsSnake } + if aux.EnableEnhancedCivicAnswersSnake != nil { + c.EnableEnhancedCivicAnswers = aux.EnableEnhancedCivicAnswersSnake + } if aux.MediaResolutionSnake != "" { c.MediaResolution = aux.MediaResolutionSnake } diff --git a/dto/openai_request.go b/dto/openai_request.go index 9113a086e6..c0a69a376e 100644 --- a/dto/openai_request.go +++ b/dto/openai_request.go @@ -54,18 +54,22 @@ type GeneralOpenAIRequest struct { ParallelTooCalls *bool `json:"parallel_tool_calls,omitempty"` Tools []ToolCallRequest `json:"tools,omitempty"` ToolChoice any `json:"tool_choice,omitempty"` + FunctionCall json.RawMessage `json:"function_call,omitempty"` User string `json:"user,omitempty"` - LogProbs bool `json:"logprobs,omitempty"` - TopLogProbs int `json:"top_logprobs,omitempty"` - Dimensions int `json:"dimensions,omitempty"` - Modalities json.RawMessage `json:"modalities,omitempty"` - Audio json.RawMessage `json:"audio,omitempty"` + // ServiceTier specifies upstream service level and may affect billing. + // This field is filtered by default and can be enabled via channel setting allow_service_tier. + ServiceTier string `json:"service_tier,omitempty"` + LogProbs bool `json:"logprobs,omitempty"` + TopLogProbs int `json:"top_logprobs,omitempty"` + Dimensions int `json:"dimensions,omitempty"` + Modalities json.RawMessage `json:"modalities,omitempty"` + Audio json.RawMessage `json:"audio,omitempty"` // 安全标识符,用于帮助 OpenAI 检测可能违反使用政策的应用程序用户 - // 注意:此字段会向 OpenAI 发送用户标识信息,默认过滤以保护用户隐私 + // 注意:此字段会向 OpenAI 发送用户标识信息,默认过滤,可通过 allow_safety_identifier 开启 SafetyIdentifier string `json:"safety_identifier,omitempty"` // Whether or not to store the output of this chat completion request for use in our model distillation or evals products. // 是否存储此次请求数据供 OpenAI 用于评估和优化产品 - // 注意:默认过滤此字段以保护用户隐私,但过滤后可能导致 Codex 无法正常使用 + // 注意:默认允许透传,可通过 disable_store 禁用;禁用后可能导致 Codex 无法正常使用 Store json.RawMessage `json:"store,omitempty"` // Used by OpenAI to cache responses for similar requests to optimize your cache hit rates. Replaces the user field PromptCacheKey string `json:"prompt_cache_key,omitempty"` @@ -261,6 +265,9 @@ type FunctionRequest struct { type StreamOptions struct { IncludeUsage bool `json:"include_usage,omitempty"` + // IncludeObfuscation is only for /v1/responses stream payload. + // This field is filtered by default and can be enabled via channel setting allow_include_obfuscation. + IncludeObfuscation bool `json:"include_obfuscation,omitempty"` } func (r *GeneralOpenAIRequest) GetMaxTokens() uint { @@ -799,30 +806,42 @@ type WebSearchOptions struct { // https://platform.openai.com/docs/api-reference/responses/create type OpenAIResponsesRequest struct { - Model string `json:"model"` - Input json.RawMessage `json:"input,omitempty"` - Include json.RawMessage `json:"include,omitempty"` + Model string `json:"model"` + Input json.RawMessage `json:"input,omitempty"` + Include json.RawMessage `json:"include,omitempty"` + // 在后台运行推理,暂时还不支持依赖的接口 + // Background json.RawMessage `json:"background,omitempty"` + Conversation json.RawMessage `json:"conversation,omitempty"` + ContextManagement json.RawMessage `json:"context_management,omitempty"` Instructions json.RawMessage `json:"instructions,omitempty"` MaxOutputTokens uint `json:"max_output_tokens,omitempty"` + TopLogProbs *int `json:"top_logprobs,omitempty"` Metadata json.RawMessage `json:"metadata,omitempty"` ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"` PreviousResponseID string `json:"previous_response_id,omitempty"` Reasoning *Reasoning `json:"reasoning,omitempty"` - // 服务层级字段,用于指定 API 服务等级。允许透传可能导致实际计费高于预期,默认应过滤 - ServiceTier string `json:"service_tier,omitempty"` + // ServiceTier specifies upstream service level and may affect billing. + // This field is filtered by default and can be enabled via channel setting allow_service_tier. + ServiceTier string `json:"service_tier,omitempty"` + // Store controls whether upstream may store request/response data. + // This field is allowed by default and can be disabled via channel setting disable_store. Store json.RawMessage `json:"store,omitempty"` PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"` PromptCacheRetention json.RawMessage `json:"prompt_cache_retention,omitempty"` - Stream bool `json:"stream,omitempty"` - Temperature *float64 `json:"temperature,omitempty"` - Text json.RawMessage `json:"text,omitempty"` - ToolChoice json.RawMessage `json:"tool_choice,omitempty"` - Tools json.RawMessage `json:"tools,omitempty"` // 需要处理的参数很少,MCP 参数太多不确定,所以用 map - TopP *float64 `json:"top_p,omitempty"` - Truncation string `json:"truncation,omitempty"` - User string `json:"user,omitempty"` - MaxToolCalls uint `json:"max_tool_calls,omitempty"` - Prompt json.RawMessage `json:"prompt,omitempty"` + // SafetyIdentifier carries client identity for policy abuse detection. + // This field is filtered by default and can be enabled via channel setting allow_safety_identifier. + SafetyIdentifier string `json:"safety_identifier,omitempty"` + Stream bool `json:"stream,omitempty"` + StreamOptions *StreamOptions `json:"stream_options,omitempty"` + Temperature *float64 `json:"temperature,omitempty"` + Text json.RawMessage `json:"text,omitempty"` + ToolChoice json.RawMessage `json:"tool_choice,omitempty"` + Tools json.RawMessage `json:"tools,omitempty"` // 需要处理的参数很少,MCP 参数太多不确定,所以用 map + TopP *float64 `json:"top_p,omitempty"` + Truncation string `json:"truncation,omitempty"` + User string `json:"user,omitempty"` + MaxToolCalls uint `json:"max_tool_calls,omitempty"` + Prompt json.RawMessage `json:"prompt,omitempty"` // qwen EnableThinking json.RawMessage `json:"enable_thinking,omitempty"` // perplexity diff --git a/relay/chat_completions_via_responses.go b/relay/chat_completions_via_responses.go index 38dae3c566..6412b7d246 100644 --- a/relay/chat_completions_via_responses.go +++ b/relay/chat_completions_via_responses.go @@ -76,7 +76,7 @@ func chatCompletionsViaResponses(c *gin.Context, info *relaycommon.RelayInfo, ad return nil, types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) } - chatJSON, err = relaycommon.RemoveDisabledFields(chatJSON, info.ChannelOtherSettings) + chatJSON, err = relaycommon.RemoveDisabledFields(chatJSON, info.ChannelOtherSettings, info.ChannelSetting.PassThroughBodyEnabled) if err != nil { return nil, types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) } @@ -120,7 +120,7 @@ func chatCompletionsViaResponses(c *gin.Context, info *relaycommon.RelayInfo, ad return nil, types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) } - jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings) + jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings, info.ChannelSetting.PassThroughBodyEnabled) if err != nil { return nil, types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) } diff --git a/relay/claude_handler.go b/relay/claude_handler.go index 81adb276ae..9b08781c8f 100644 --- a/relay/claude_handler.go +++ b/relay/claude_handler.go @@ -146,7 +146,7 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ } // remove disabled fields for Claude API - jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings) + jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings, info.ChannelSetting.PassThroughBodyEnabled) if err != nil { return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) } diff --git a/relay/common/override_test.go b/relay/common/override_test.go index 021df3f60e..c83cddff7c 100644 --- a/relay/common/override_test.go +++ b/relay/common/override_test.go @@ -4,6 +4,9 @@ import ( "encoding/json" "reflect" "testing" + + "github.com/QuantumNous/new-api/dto" + "github.com/QuantumNous/new-api/setting/model_setting" ) func TestApplyParamOverrideTrimPrefix(t *testing.T) { @@ -772,6 +775,76 @@ func TestApplyParamOverrideToUpper(t *testing.T) { assertJSONEqual(t, `{"model":"GPT-4"}`, string(out)) } +func TestRemoveDisabledFieldsSkipWhenChannelPassThroughEnabled(t *testing.T) { + input := `{ + "service_tier":"flex", + "safety_identifier":"user-123", + "store":true, + "stream_options":{"include_obfuscation":false} + }` + settings := dto.ChannelOtherSettings{} + + out, err := RemoveDisabledFields([]byte(input), settings, true) + if err != nil { + t.Fatalf("RemoveDisabledFields returned error: %v", err) + } + assertJSONEqual(t, input, string(out)) +} + +func TestRemoveDisabledFieldsSkipWhenGlobalPassThroughEnabled(t *testing.T) { + original := model_setting.GetGlobalSettings().PassThroughRequestEnabled + model_setting.GetGlobalSettings().PassThroughRequestEnabled = true + t.Cleanup(func() { + model_setting.GetGlobalSettings().PassThroughRequestEnabled = original + }) + + input := `{ + "service_tier":"flex", + "safety_identifier":"user-123", + "stream_options":{"include_obfuscation":false} + }` + settings := dto.ChannelOtherSettings{} + + out, err := RemoveDisabledFields([]byte(input), settings, false) + if err != nil { + t.Fatalf("RemoveDisabledFields returned error: %v", err) + } + assertJSONEqual(t, input, string(out)) +} + +func TestRemoveDisabledFieldsDefaultFiltering(t *testing.T) { + input := `{ + "service_tier":"flex", + "inference_geo":"eu", + "safety_identifier":"user-123", + "store":true, + "stream_options":{"include_obfuscation":false} + }` + settings := dto.ChannelOtherSettings{} + + out, err := RemoveDisabledFields([]byte(input), settings, false) + if err != nil { + t.Fatalf("RemoveDisabledFields returned error: %v", err) + } + assertJSONEqual(t, `{"store":true}`, string(out)) +} + +func TestRemoveDisabledFieldsAllowInferenceGeo(t *testing.T) { + input := `{ + "inference_geo":"eu", + "store":true + }` + settings := dto.ChannelOtherSettings{ + AllowInferenceGeo: true, + } + + out, err := RemoveDisabledFields([]byte(input), settings, false) + if err != nil { + t.Fatalf("RemoveDisabledFields returned error: %v", err) + } + assertJSONEqual(t, `{"inference_geo":"eu","store":true}`, string(out)) +} + func assertJSONEqual(t *testing.T, want, got string) { t.Helper() diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index 81b7d21d6b..c5c5a883f9 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -700,9 +700,15 @@ func FailTaskInfo(reason string) *TaskInfo { // RemoveDisabledFields 从请求 JSON 数据中移除渠道设置中禁用的字段 // service_tier: 服务层级字段,可能导致额外计费(OpenAI、Claude、Responses API 支持) +// inference_geo: Claude 数据驻留推理区域字段(仅 Claude 支持,默认过滤) // store: 数据存储授权字段,涉及用户隐私(仅 OpenAI、Responses API 支持,默认允许透传,禁用后可能导致 Codex 无法使用) // safety_identifier: 安全标识符,用于向 OpenAI 报告违规用户(仅 OpenAI 支持,涉及用户隐私) -func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOtherSettings) ([]byte, error) { +// stream_options.include_obfuscation: 响应流混淆控制字段(仅 OpenAI Responses API 支持) +func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOtherSettings, channelPassThroughEnabled bool) ([]byte, error) { + if model_setting.GetGlobalSettings().PassThroughRequestEnabled || channelPassThroughEnabled { + return jsonData, nil + } + var data map[string]interface{} if err := common.Unmarshal(jsonData, &data); err != nil { common.SysError("RemoveDisabledFields Unmarshal error :" + err.Error()) @@ -716,6 +722,13 @@ func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOther } } + // 默认移除 inference_geo,除非明确允许(避免在未授权情况下透传数据驻留区域) + if !channelOtherSettings.AllowInferenceGeo { + if _, exists := data["inference_geo"]; exists { + delete(data, "inference_geo") + } + } + // 默认允许 store 透传,除非明确禁用(禁用可能影响 Codex 使用) if channelOtherSettings.DisableStore { if _, exists := data["store"]; exists { @@ -730,6 +743,22 @@ func RemoveDisabledFields(jsonData []byte, channelOtherSettings dto.ChannelOther } } + // 默认移除 stream_options.include_obfuscation,除非明确允许(避免关闭响应流混淆保护) + if !channelOtherSettings.AllowIncludeObfuscation { + if streamOptionsAny, exists := data["stream_options"]; exists { + if streamOptions, ok := streamOptionsAny.(map[string]interface{}); ok { + if _, includeExists := streamOptions["include_obfuscation"]; includeExists { + delete(streamOptions, "include_obfuscation") + } + if len(streamOptions) == 0 { + delete(data, "stream_options") + } else { + data["stream_options"] = streamOptions + } + } + } + } + jsonDataAfter, err := common.Marshal(data) if err != nil { common.SysError("RemoveDisabledFields Marshal error :" + err.Error()) diff --git a/relay/compatible_handler.go b/relay/compatible_handler.go index e7adddbbfe..5fff8a9186 100644 --- a/relay/compatible_handler.go +++ b/relay/compatible_handler.go @@ -165,7 +165,7 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types } // remove disabled fields for OpenAI API - jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings) + jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings, info.ChannelSetting.PassThroughBodyEnabled) if err != nil { return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) } diff --git a/relay/responses_handler.go b/relay/responses_handler.go index 04fc3470e4..b3169e7261 100644 --- a/relay/responses_handler.go +++ b/relay/responses_handler.go @@ -89,7 +89,7 @@ func ResponsesHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError * } // remove disabled fields for OpenAI Responses API - jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings) + jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings, info.ChannelSetting.PassThroughBodyEnabled) if err != nil { return types.NewError(err, types.ErrorCodeConvertRequestFailed, types.ErrOptionWithSkipRetry()) } diff --git a/web/src/components/settings/personal/cards/NotificationSettings.jsx b/web/src/components/settings/personal/cards/NotificationSettings.jsx index 964a730e41..e57e39d63d 100644 --- a/web/src/components/settings/personal/cards/NotificationSettings.jsx +++ b/web/src/components/settings/personal/cards/NotificationSettings.jsx @@ -86,6 +86,7 @@ const NotificationSettings = ({ channel: true, models: true, deployment: true, + subscription: true, redemption: true, user: true, setting: true, @@ -169,6 +170,7 @@ const NotificationSettings = ({ channel: true, models: true, deployment: true, + subscription: true, redemption: true, user: true, setting: true, @@ -296,6 +298,11 @@ const NotificationSettings = ({ title: t('模型部署'), description: t('模型部署管理'), }, + { + key: 'subscription', + title: t('订阅管理'), + description: t('订阅套餐管理'), + }, { key: 'redemption', title: t('兑换码管理'), diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index 6e85ca982c..931a42efbc 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -170,6 +170,8 @@ const EditChannelModal = (props) => { allow_service_tier: false, disable_store: false, // false = 允许透传(默认开启) allow_safety_identifier: false, + allow_include_obfuscation: false, + allow_inference_geo: false, claude_beta_query: false, }; const [batch, setBatch] = useState(false); @@ -634,6 +636,10 @@ const EditChannelModal = (props) => { data.disable_store = parsedSettings.disable_store || false; data.allow_safety_identifier = parsedSettings.allow_safety_identifier || false; + data.allow_include_obfuscation = + parsedSettings.allow_include_obfuscation || false; + data.allow_inference_geo = + parsedSettings.allow_inference_geo || false; data.claude_beta_query = parsedSettings.claude_beta_query || false; } catch (error) { console.error('解析其他设置失败:', error); @@ -645,6 +651,8 @@ const EditChannelModal = (props) => { data.allow_service_tier = false; data.disable_store = false; data.allow_safety_identifier = false; + data.allow_include_obfuscation = false; + data.allow_inference_geo = false; data.claude_beta_query = false; } } else { @@ -655,6 +663,8 @@ const EditChannelModal = (props) => { data.allow_service_tier = false; data.disable_store = false; data.allow_safety_identifier = false; + data.allow_include_obfuscation = false; + data.allow_inference_geo = false; data.claude_beta_query = false; } @@ -1392,13 +1402,16 @@ const EditChannelModal = (props) => { // type === 1 (OpenAI) 或 type === 14 (Claude): 设置字段透传控制(显式保存布尔值) if (localInputs.type === 1 || localInputs.type === 14) { settings.allow_service_tier = localInputs.allow_service_tier === true; - // 仅 OpenAI 渠道需要 store 和 safety_identifier + // 仅 OpenAI 渠道需要 store / safety_identifier / include_obfuscation if (localInputs.type === 1) { settings.disable_store = localInputs.disable_store === true; settings.allow_safety_identifier = localInputs.allow_safety_identifier === true; + settings.allow_include_obfuscation = + localInputs.allow_include_obfuscation === true; } if (localInputs.type === 14) { + settings.allow_inference_geo = localInputs.allow_inference_geo === true; settings.claude_beta_query = localInputs.claude_beta_query === true; } } @@ -1421,6 +1434,8 @@ const EditChannelModal = (props) => { delete localInputs.allow_service_tier; delete localInputs.disable_store; delete localInputs.allow_safety_identifier; + delete localInputs.allow_include_obfuscation; + delete localInputs.allow_inference_geo; delete localInputs.claude_beta_query; let res; @@ -3271,6 +3286,24 @@ const EditChannelModal = (props) => { 'safety_identifier 字段用于帮助 OpenAI 识别可能违反使用政策的应用程序用户。默认关闭以保护用户隐私', )} /> + + + handleChannelOtherSettingsChange( + 'allow_include_obfuscation', + value, + ) + } + extraText={t( + 'include_obfuscation 用于控制 Responses 流混淆字段。默认关闭以避免客户端关闭该安全保护', + )} + /> )} @@ -3296,6 +3329,22 @@ const EditChannelModal = (props) => { 'service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用', )} /> + + + handleChannelOtherSettingsChange( + 'allow_inference_geo', + value, + ) + } + extraText={t( + 'inference_geo 字段用于控制 Claude 数据驻留推理区域。默认关闭以避免未经授权透传地域信息', + )} + /> )}