feat: gate Claude inference_geo passthrough behind channel setting and add field docs

This commit is contained in:
Seefs
2026-02-21 14:25:58 +08:00
parent 2c5af0df36
commit a546871a80
6 changed files with 101 additions and 26 deletions

View File

@@ -29,6 +29,7 @@ type ChannelOtherSettings struct {
OpenRouterEnterprise *bool `json:"openrouter_enterprise,omitempty"` OpenRouterEnterprise *bool `json:"openrouter_enterprise,omitempty"`
ClaudeBetaQuery bool `json:"claude_beta_query,omitempty"` // Claude 渠道是否强制追加 ?beta=true ClaudeBetaQuery bool `json:"claude_beta_query,omitempty"` // Claude 渠道是否强制追加 ?beta=true
AllowServiceTier bool `json:"allow_service_tier,omitempty"` // 是否允许 service_tier 透传(默认过滤以避免额外计费) 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 无法使用) DisableStore bool `json:"disable_store,omitempty"` // 是否禁用 store 透传(默认允许透传,禁用后可能导致 Codex 无法使用)
AllowSafetyIdentifier bool `json:"allow_safety_identifier,omitempty"` // 是否允许 safety_identifier 透传(默认过滤以保护用户隐私) AllowSafetyIdentifier bool `json:"allow_safety_identifier,omitempty"` // 是否允许 safety_identifier 透传(默认过滤以保护用户隐私)
AllowIncludeObfuscation bool `json:"allow_include_obfuscation,omitempty"` // 是否允许 stream_options.include_obfuscation 透传(默认过滤以避免关闭流混淆保护) AllowIncludeObfuscation bool `json:"allow_include_obfuscation,omitempty"` // 是否允许 stream_options.include_obfuscation 透传(默认过滤以避免关闭流混淆保护)

View File

@@ -194,8 +194,9 @@ type ClaudeRequest struct {
Prompt string `json:"prompt,omitempty"` Prompt string `json:"prompt,omitempty"`
System any `json:"system,omitempty"` System any `json:"system,omitempty"`
Messages []ClaudeMessage `json:"messages,omitempty"` Messages []ClaudeMessage `json:"messages,omitempty"`
// https://platform.claude.com/docs/en/build-with-claude/data-residency#inference-geo // InferenceGeo controls Claude data residency region.
// InferenceGeo string `json:"inference_geo,omitempty"` // 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"` MaxTokens uint `json:"max_tokens,omitempty"`
MaxTokensToSample uint `json:"max_tokens_to_sample,omitempty"` MaxTokensToSample uint `json:"max_tokens_to_sample,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"` StopSequences []string `json:"stop_sequences,omitempty"`
@@ -212,7 +213,8 @@ type ClaudeRequest struct {
Thinking *Thinking `json:"thinking,omitempty"` Thinking *Thinking `json:"thinking,omitempty"`
McpServers json.RawMessage `json:"mcp_servers,omitempty"` McpServers json.RawMessage `json:"mcp_servers,omitempty"`
Metadata json.RawMessage `json:"metadata,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"` ServiceTier string `json:"service_tier,omitempty"`
} }

View File

@@ -56,18 +56,20 @@ type GeneralOpenAIRequest struct {
ToolChoice any `json:"tool_choice,omitempty"` ToolChoice any `json:"tool_choice,omitempty"`
FunctionCall json.RawMessage `json:"function_call,omitempty"` FunctionCall json.RawMessage `json:"function_call,omitempty"`
User string `json:"user,omitempty"` User string `json:"user,omitempty"`
ServiceTier string `json:"service_tier,omitempty"` // ServiceTier specifies upstream service level and may affect billing.
LogProbs bool `json:"logprobs,omitempty"` // This field is filtered by default and can be enabled via channel setting allow_service_tier.
TopLogProbs int `json:"top_logprobs,omitempty"` ServiceTier string `json:"service_tier,omitempty"`
Dimensions int `json:"dimensions,omitempty"` LogProbs bool `json:"logprobs,omitempty"`
Modalities json.RawMessage `json:"modalities,omitempty"` TopLogProbs int `json:"top_logprobs,omitempty"`
Audio json.RawMessage `json:"audio,omitempty"` Dimensions int `json:"dimensions,omitempty"`
Modalities json.RawMessage `json:"modalities,omitempty"`
Audio json.RawMessage `json:"audio,omitempty"`
// 安全标识符,用于帮助 OpenAI 检测可能违反使用政策的应用程序用户 // 安全标识符,用于帮助 OpenAI 检测可能违反使用政策的应用程序用户
// 注意:此字段会向 OpenAI 发送用户标识信息,默认过滤以保护用户隐私 // 注意:此字段会向 OpenAI 发送用户标识信息,默认过滤,可通过 allow_safety_identifier 开启
SafetyIdentifier string `json:"safety_identifier,omitempty"` 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. // Whether or not to store the output of this chat completion request for use in our model distillation or evals products.
// 是否存储此次请求数据供 OpenAI 用于评估和优化产品 // 是否存储此次请求数据供 OpenAI 用于评估和优化产品
// 注意:默认过滤此字段以保护用户隐私,但过滤后可能导致 Codex 无法正常使用 // 注意:默认允许透传,可通过 disable_store 禁用;禁用后可能导致 Codex 无法正常使用
Store json.RawMessage `json:"store,omitempty"` 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 // 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"` PromptCacheKey string `json:"prompt_cache_key,omitempty"`
@@ -263,7 +265,8 @@ type FunctionRequest struct {
type StreamOptions struct { type StreamOptions struct {
IncludeUsage bool `json:"include_usage,omitempty"` IncludeUsage bool `json:"include_usage,omitempty"`
// for /v1/responses // 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"` IncludeObfuscation bool `json:"include_obfuscation,omitempty"`
} }
@@ -817,23 +820,28 @@ type OpenAIResponsesRequest struct {
ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"` ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"`
PreviousResponseID string `json:"previous_response_id,omitempty"` PreviousResponseID string `json:"previous_response_id,omitempty"`
Reasoning *Reasoning `json:"reasoning,omitempty"` Reasoning *Reasoning `json:"reasoning,omitempty"`
// 服务层级字段,用于指定 API 服务等级。允许透传可能导致实际计费高于预期,默认应过滤 // ServiceTier specifies upstream service level and may affect billing.
ServiceTier string `json:"service_tier,omitempty"` // 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"` Store json.RawMessage `json:"store,omitempty"`
PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"` PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"`
PromptCacheRetention json.RawMessage `json:"prompt_cache_retention,omitempty"` PromptCacheRetention json.RawMessage `json:"prompt_cache_retention,omitempty"`
SafetyIdentifier string `json:"safety_identifier,omitempty"` // SafetyIdentifier carries client identity for policy abuse detection.
Stream bool `json:"stream,omitempty"` // This field is filtered by default and can be enabled via channel setting allow_safety_identifier.
StreamOptions *StreamOptions `json:"stream_options,omitempty"` SafetyIdentifier string `json:"safety_identifier,omitempty"`
Temperature *float64 `json:"temperature,omitempty"` Stream bool `json:"stream,omitempty"`
Text json.RawMessage `json:"text,omitempty"` StreamOptions *StreamOptions `json:"stream_options,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"` Temperature *float64 `json:"temperature,omitempty"`
Tools json.RawMessage `json:"tools,omitempty"` // 需要处理的参数很少MCP 参数太多不确定,所以用 map Text json.RawMessage `json:"text,omitempty"`
TopP *float64 `json:"top_p,omitempty"` ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
Truncation string `json:"truncation,omitempty"` Tools json.RawMessage `json:"tools,omitempty"` // 需要处理的参数很少MCP 参数太多不确定,所以用 map
User string `json:"user,omitempty"` TopP *float64 `json:"top_p,omitempty"`
MaxToolCalls uint `json:"max_tool_calls,omitempty"` Truncation string `json:"truncation,omitempty"`
Prompt json.RawMessage `json:"prompt,omitempty"` User string `json:"user,omitempty"`
MaxToolCalls uint `json:"max_tool_calls,omitempty"`
Prompt json.RawMessage `json:"prompt,omitempty"`
// qwen // qwen
EnableThinking json.RawMessage `json:"enable_thinking,omitempty"` EnableThinking json.RawMessage `json:"enable_thinking,omitempty"`
// perplexity // perplexity

View File

@@ -812,6 +812,39 @@ func TestRemoveDisabledFieldsSkipWhenGlobalPassThroughEnabled(t *testing.T) {
assertJSONEqual(t, input, string(out)) 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) { func assertJSONEqual(t *testing.T, want, got string) {
t.Helper() t.Helper()

View File

@@ -700,6 +700,7 @@ func FailTaskInfo(reason string) *TaskInfo {
// RemoveDisabledFields 从请求 JSON 数据中移除渠道设置中禁用的字段 // RemoveDisabledFields 从请求 JSON 数据中移除渠道设置中禁用的字段
// service_tier: 服务层级字段可能导致额外计费OpenAI、Claude、Responses API 支持) // service_tier: 服务层级字段可能导致额外计费OpenAI、Claude、Responses API 支持)
// inference_geo: Claude 数据驻留推理区域字段(仅 Claude 支持,默认过滤)
// store: 数据存储授权字段,涉及用户隐私(仅 OpenAI、Responses API 支持,默认允许透传,禁用后可能导致 Codex 无法使用) // store: 数据存储授权字段,涉及用户隐私(仅 OpenAI、Responses API 支持,默认允许透传,禁用后可能导致 Codex 无法使用)
// safety_identifier: 安全标识符,用于向 OpenAI 报告违规用户(仅 OpenAI 支持,涉及用户隐私) // safety_identifier: 安全标识符,用于向 OpenAI 报告违规用户(仅 OpenAI 支持,涉及用户隐私)
// stream_options.include_obfuscation: 响应流混淆控制字段(仅 OpenAI Responses API 支持) // stream_options.include_obfuscation: 响应流混淆控制字段(仅 OpenAI Responses API 支持)
@@ -721,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 使用) // 默认允许 store 透传,除非明确禁用(禁用可能影响 Codex 使用)
if channelOtherSettings.DisableStore { if channelOtherSettings.DisableStore {
if _, exists := data["store"]; exists { if _, exists := data["store"]; exists {

View File

@@ -171,6 +171,7 @@ const EditChannelModal = (props) => {
disable_store: false, // false = 允许透传(默认开启) disable_store: false, // false = 允许透传(默认开启)
allow_safety_identifier: false, allow_safety_identifier: false,
allow_include_obfuscation: false, allow_include_obfuscation: false,
allow_inference_geo: false,
claude_beta_query: false, claude_beta_query: false,
}; };
const [batch, setBatch] = useState(false); const [batch, setBatch] = useState(false);
@@ -637,6 +638,8 @@ const EditChannelModal = (props) => {
parsedSettings.allow_safety_identifier || false; parsedSettings.allow_safety_identifier || false;
data.allow_include_obfuscation = data.allow_include_obfuscation =
parsedSettings.allow_include_obfuscation || false; parsedSettings.allow_include_obfuscation || false;
data.allow_inference_geo =
parsedSettings.allow_inference_geo || false;
data.claude_beta_query = parsedSettings.claude_beta_query || false; data.claude_beta_query = parsedSettings.claude_beta_query || false;
} catch (error) { } catch (error) {
console.error('解析其他设置失败:', error); console.error('解析其他设置失败:', error);
@@ -649,6 +652,7 @@ const EditChannelModal = (props) => {
data.disable_store = false; data.disable_store = false;
data.allow_safety_identifier = false; data.allow_safety_identifier = false;
data.allow_include_obfuscation = false; data.allow_include_obfuscation = false;
data.allow_inference_geo = false;
data.claude_beta_query = false; data.claude_beta_query = false;
} }
} else { } else {
@@ -660,6 +664,7 @@ const EditChannelModal = (props) => {
data.disable_store = false; data.disable_store = false;
data.allow_safety_identifier = false; data.allow_safety_identifier = false;
data.allow_include_obfuscation = false; data.allow_include_obfuscation = false;
data.allow_inference_geo = false;
data.claude_beta_query = false; data.claude_beta_query = false;
} }
@@ -1406,6 +1411,7 @@ const EditChannelModal = (props) => {
localInputs.allow_include_obfuscation === true; localInputs.allow_include_obfuscation === true;
} }
if (localInputs.type === 14) { if (localInputs.type === 14) {
settings.allow_inference_geo = localInputs.allow_inference_geo === true;
settings.claude_beta_query = localInputs.claude_beta_query === true; settings.claude_beta_query = localInputs.claude_beta_query === true;
} }
} }
@@ -1429,6 +1435,7 @@ const EditChannelModal = (props) => {
delete localInputs.disable_store; delete localInputs.disable_store;
delete localInputs.allow_safety_identifier; delete localInputs.allow_safety_identifier;
delete localInputs.allow_include_obfuscation; delete localInputs.allow_include_obfuscation;
delete localInputs.allow_inference_geo;
delete localInputs.claude_beta_query; delete localInputs.claude_beta_query;
let res; let res;
@@ -3322,6 +3329,22 @@ const EditChannelModal = (props) => {
'service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用', 'service_tier 字段用于指定服务层级,允许透传可能导致实际计费高于预期。默认关闭以避免额外费用',
)} )}
/> />
<Form.Switch
field='allow_inference_geo'
label={t('允许 inference_geo 透传')}
checkedText={t('开')}
uncheckedText={t('关')}
onChange={(value) =>
handleChannelOtherSettingsChange(
'allow_inference_geo',
value,
)
}
extraText={t(
'inference_geo 字段用于控制 Claude 数据驻留推理区域。默认关闭以避免未经授权透传地域信息',
)}
/>
</> </>
)} )}
</Card> </Card>