Merge pull request #1897 from QuantumNous/openrouter-enterprise
feat: 添加 openrouter-enterprise 支持
This commit is contained in:
commit
1ba2cb0b92
@ -19,4 +19,12 @@ const (
|
|||||||
type ChannelOtherSettings struct {
|
type ChannelOtherSettings struct {
|
||||||
AzureResponsesVersion string `json:"azure_responses_version,omitempty"`
|
AzureResponsesVersion string `json:"azure_responses_version,omitempty"`
|
||||||
VertexKeyType VertexKeyType `json:"vertex_key_type,omitempty"` // "json" or "api_key"
|
VertexKeyType VertexKeyType `json:"vertex_key_type,omitempty"` // "json" or "api_key"
|
||||||
|
OpenRouterEnterprise *bool `json:"openrouter_enterprise,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChannelOtherSettings) IsOpenRouterEnterprise() bool {
|
||||||
|
if s == nil || s.OpenRouterEnterprise == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *s.OpenRouterEnterprise
|
||||||
}
|
}
|
||||||
|
|||||||
@ -265,6 +265,7 @@ func doRequest(c *gin.Context, req *http.Request, info *common.RelayInfo) (*http
|
|||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.LogError(c, "do request failed: "+err.Error())
|
||||||
return nil, types.NewError(err, types.ErrorCodeDoRequestFailed, types.ErrOptionWithHideErrMsg("upstream error: do request failed"))
|
return nil, types.NewError(err, types.ErrorCodeDoRequestFailed, types.ErrOptionWithHideErrMsg("upstream error: do request failed"))
|
||||||
}
|
}
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"one-api/constant"
|
"one-api/constant"
|
||||||
"one-api/dto"
|
"one-api/dto"
|
||||||
"one-api/logger"
|
"one-api/logger"
|
||||||
|
"one-api/relay/channel/openrouter"
|
||||||
relaycommon "one-api/relay/common"
|
relaycommon "one-api/relay/common"
|
||||||
"one-api/relay/helper"
|
"one-api/relay/helper"
|
||||||
"one-api/service"
|
"one-api/service"
|
||||||
@ -185,10 +186,27 @@ func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Respo
|
|||||||
if common.DebugEnabled {
|
if common.DebugEnabled {
|
||||||
println("upstream response body:", string(responseBody))
|
println("upstream response body:", string(responseBody))
|
||||||
}
|
}
|
||||||
|
// Unmarshal to simpleResponse
|
||||||
|
if info.ChannelType == constant.ChannelTypeOpenRouter && info.ChannelOtherSettings.IsOpenRouterEnterprise() {
|
||||||
|
// 尝试解析为 openrouter enterprise
|
||||||
|
var enterpriseResponse openrouter.OpenRouterEnterpriseResponse
|
||||||
|
err = common.Unmarshal(responseBody, &enterpriseResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
if enterpriseResponse.Success {
|
||||||
|
responseBody = enterpriseResponse.Data
|
||||||
|
} else {
|
||||||
|
logger.LogError(c, fmt.Sprintf("openrouter enterprise response success=false, data: %s", enterpriseResponse.Data))
|
||||||
|
return nil, types.NewOpenAIError(fmt.Errorf("openrouter response success=false"), types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = common.Unmarshal(responseBody, &simpleResponse)
|
err = common.Unmarshal(responseBody, &simpleResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oaiError := simpleResponse.GetOpenAIError(); oaiError != nil && oaiError.Type != "" {
|
if oaiError := simpleResponse.GetOpenAIError(); oaiError != nil && oaiError.Type != "" {
|
||||||
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
|
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package openrouter
|
package openrouter
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
type RequestReasoning struct {
|
type RequestReasoning struct {
|
||||||
// One of the following (not both):
|
// One of the following (not both):
|
||||||
Effort string `json:"effort,omitempty"` // Can be "high", "medium", or "low" (OpenAI-style)
|
Effort string `json:"effort,omitempty"` // Can be "high", "medium", or "low" (OpenAI-style)
|
||||||
@ -7,3 +9,8 @@ type RequestReasoning struct {
|
|||||||
// Optional: Default is false. All models support this.
|
// Optional: Default is false. All models support this.
|
||||||
Exclude bool `json:"exclude,omitempty"` // Set to true to exclude reasoning tokens from response
|
Exclude bool `json:"exclude,omitempty"` // Set to true to exclude reasoning tokens from response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OpenRouterEnterpriseResponse struct {
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|||||||
@ -164,6 +164,8 @@ const EditChannelModal = (props) => {
|
|||||||
settings: '',
|
settings: '',
|
||||||
// 仅 Vertex: 密钥格式(存入 settings.vertex_key_type)
|
// 仅 Vertex: 密钥格式(存入 settings.vertex_key_type)
|
||||||
vertex_key_type: 'json',
|
vertex_key_type: 'json',
|
||||||
|
// 企业账户设置
|
||||||
|
is_enterprise_account: false,
|
||||||
};
|
};
|
||||||
const [batch, setBatch] = useState(false);
|
const [batch, setBatch] = useState(false);
|
||||||
const [multiToSingle, setMultiToSingle] = useState(false);
|
const [multiToSingle, setMultiToSingle] = useState(false);
|
||||||
@ -189,6 +191,7 @@ const EditChannelModal = (props) => {
|
|||||||
const [channelSearchValue, setChannelSearchValue] = useState('');
|
const [channelSearchValue, setChannelSearchValue] = useState('');
|
||||||
const [useManualInput, setUseManualInput] = useState(false); // 是否使用手动输入模式
|
const [useManualInput, setUseManualInput] = useState(false); // 是否使用手动输入模式
|
||||||
const [keyMode, setKeyMode] = useState('append'); // 密钥模式:replace(覆盖)或 append(追加)
|
const [keyMode, setKeyMode] = useState('append'); // 密钥模式:replace(覆盖)或 append(追加)
|
||||||
|
const [isEnterpriseAccount, setIsEnterpriseAccount] = useState(false); // 是否为企业账户
|
||||||
|
|
||||||
// 2FA验证查看密钥相关状态
|
// 2FA验证查看密钥相关状态
|
||||||
const [twoFAState, setTwoFAState] = useState({
|
const [twoFAState, setTwoFAState] = useState({
|
||||||
@ -437,15 +440,19 @@ const EditChannelModal = (props) => {
|
|||||||
parsedSettings.azure_responses_version || '';
|
parsedSettings.azure_responses_version || '';
|
||||||
// 读取 Vertex 密钥格式
|
// 读取 Vertex 密钥格式
|
||||||
data.vertex_key_type = parsedSettings.vertex_key_type || 'json';
|
data.vertex_key_type = parsedSettings.vertex_key_type || 'json';
|
||||||
|
// 读取企业账户设置
|
||||||
|
data.is_enterprise_account = parsedSettings.openrouter_enterprise === true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解析其他设置失败:', error);
|
console.error('解析其他设置失败:', error);
|
||||||
data.azure_responses_version = '';
|
data.azure_responses_version = '';
|
||||||
data.region = '';
|
data.region = '';
|
||||||
data.vertex_key_type = 'json';
|
data.vertex_key_type = 'json';
|
||||||
|
data.is_enterprise_account = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 兼容历史数据:老渠道没有 settings 时,默认按 json 展示
|
// 兼容历史数据:老渠道没有 settings 时,默认按 json 展示
|
||||||
data.vertex_key_type = 'json';
|
data.vertex_key_type = 'json';
|
||||||
|
data.is_enterprise_account = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInputs(data);
|
setInputs(data);
|
||||||
@ -457,6 +464,8 @@ const EditChannelModal = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
setAutoBan(true);
|
setAutoBan(true);
|
||||||
}
|
}
|
||||||
|
// 同步企业账户状态
|
||||||
|
setIsEnterpriseAccount(data.is_enterprise_account || false);
|
||||||
setBasicModels(getChannelModels(data.type));
|
setBasicModels(getChannelModels(data.type));
|
||||||
// 同步更新channelSettings状态显示
|
// 同步更新channelSettings状态显示
|
||||||
setChannelSettings({
|
setChannelSettings({
|
||||||
@ -716,6 +725,8 @@ const EditChannelModal = (props) => {
|
|||||||
});
|
});
|
||||||
// 重置密钥模式状态
|
// 重置密钥模式状态
|
||||||
setKeyMode('append');
|
setKeyMode('append');
|
||||||
|
// 重置企业账户状态
|
||||||
|
setIsEnterpriseAccount(false);
|
||||||
// 清空表单中的key_mode字段
|
// 清空表单中的key_mode字段
|
||||||
if (formApiRef.current) {
|
if (formApiRef.current) {
|
||||||
formApiRef.current.setValue('key_mode', undefined);
|
formApiRef.current.setValue('key_mode', undefined);
|
||||||
@ -879,6 +890,21 @@ const EditChannelModal = (props) => {
|
|||||||
};
|
};
|
||||||
localInputs.setting = JSON.stringify(channelExtraSettings);
|
localInputs.setting = JSON.stringify(channelExtraSettings);
|
||||||
|
|
||||||
|
// 处理type === 20的企业账户设置
|
||||||
|
if (localInputs.type === 20) {
|
||||||
|
let settings = {};
|
||||||
|
if (localInputs.settings) {
|
||||||
|
try {
|
||||||
|
settings = JSON.parse(localInputs.settings);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析settings失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置企业账户标识,无论是true还是false都要传到后端
|
||||||
|
settings.openrouter_enterprise = localInputs.is_enterprise_account === true;
|
||||||
|
localInputs.settings = JSON.stringify(settings);
|
||||||
|
}
|
||||||
|
|
||||||
// 清理不需要发送到后端的字段
|
// 清理不需要发送到后端的字段
|
||||||
delete localInputs.force_format;
|
delete localInputs.force_format;
|
||||||
delete localInputs.thinking_to_content;
|
delete localInputs.thinking_to_content;
|
||||||
@ -886,6 +912,7 @@ const EditChannelModal = (props) => {
|
|||||||
delete localInputs.pass_through_body_enabled;
|
delete localInputs.pass_through_body_enabled;
|
||||||
delete localInputs.system_prompt;
|
delete localInputs.system_prompt;
|
||||||
delete localInputs.system_prompt_override;
|
delete localInputs.system_prompt_override;
|
||||||
|
delete localInputs.is_enterprise_account;
|
||||||
// 顶层的 vertex_key_type 不应发送给后端
|
// 顶层的 vertex_key_type 不应发送给后端
|
||||||
delete localInputs.vertex_key_type;
|
delete localInputs.vertex_key_type;
|
||||||
|
|
||||||
@ -1203,6 +1230,21 @@ const EditChannelModal = (props) => {
|
|||||||
onChange={(value) => handleInputChange('type', value)}
|
onChange={(value) => handleInputChange('type', value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{inputs.type === 20 && (
|
||||||
|
<Form.Switch
|
||||||
|
field='is_enterprise_account'
|
||||||
|
label={t('是否为企业账户')}
|
||||||
|
checkedText={t('是')}
|
||||||
|
uncheckedText={t('否')}
|
||||||
|
onChange={(value) => {
|
||||||
|
setIsEnterpriseAccount(value);
|
||||||
|
handleInputChange('is_enterprise_account', value);
|
||||||
|
}}
|
||||||
|
extraText={t('企业账户为特殊返回格式,需要特殊处理,如果非企业账户,请勿勾选')}
|
||||||
|
initValue={inputs.is_enterprise_account}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Form.Input
|
<Form.Input
|
||||||
field='name'
|
field='name'
|
||||||
label={t('名称')}
|
label={t('名称')}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user