Merge pull request #1897 from QuantumNous/openrouter-enterprise

feat: 添加 openrouter-enterprise 支持
This commit is contained in:
Calcium-Ion 2025-09-28 15:31:01 +08:00 committed by GitHub
commit 1ba2cb0b92
5 changed files with 76 additions and 0 deletions

View File

@ -19,4 +19,12 @@ 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"`
}
func (s *ChannelOtherSettings) IsOpenRouterEnterprise() bool {
if s == nil || s.OpenRouterEnterprise == nil {
return false
}
return *s.OpenRouterEnterprise
}

View File

@ -265,6 +265,7 @@ func doRequest(c *gin.Context, req *http.Request, info *common.RelayInfo) (*http
resp, err := client.Do(req)
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"))
}
if resp == nil {

View File

@ -12,6 +12,7 @@ import (
"one-api/constant"
"one-api/dto"
"one-api/logger"
"one-api/relay/channel/openrouter"
relaycommon "one-api/relay/common"
"one-api/relay/helper"
"one-api/service"
@ -185,10 +186,27 @@ func OpenaiHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Respo
if common.DebugEnabled {
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)
if err != nil {
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
}
if oaiError := simpleResponse.GetOpenAIError(); oaiError != nil && oaiError.Type != "" {
return nil, types.WithOpenAIError(*oaiError, resp.StatusCode)
}

View File

@ -1,5 +1,7 @@
package openrouter
import "encoding/json"
type RequestReasoning struct {
// One of the following (not both):
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.
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"`
}

View File

@ -164,6 +164,8 @@ const EditChannelModal = (props) => {
settings: '',
// Vertex: settings.vertex_key_type
vertex_key_type: 'json',
//
is_enterprise_account: false,
};
const [batch, setBatch] = useState(false);
const [multiToSingle, setMultiToSingle] = useState(false);
@ -189,6 +191,7 @@ const EditChannelModal = (props) => {
const [channelSearchValue, setChannelSearchValue] = useState('');
const [useManualInput, setUseManualInput] = useState(false); // 使
const [keyMode, setKeyMode] = useState('append'); // replace append
const [isEnterpriseAccount, setIsEnterpriseAccount] = useState(false); //
// 2FA
const [twoFAState, setTwoFAState] = useState({
@ -437,15 +440,19 @@ const EditChannelModal = (props) => {
parsedSettings.azure_responses_version || '';
// Vertex
data.vertex_key_type = parsedSettings.vertex_key_type || 'json';
//
data.is_enterprise_account = parsedSettings.openrouter_enterprise === true;
} catch (error) {
console.error('解析其他设置失败:', error);
data.azure_responses_version = '';
data.region = '';
data.vertex_key_type = 'json';
data.is_enterprise_account = false;
}
} else {
// settings json
data.vertex_key_type = 'json';
data.is_enterprise_account = false;
}
setInputs(data);
@ -457,6 +464,8 @@ const EditChannelModal = (props) => {
} else {
setAutoBan(true);
}
//
setIsEnterpriseAccount(data.is_enterprise_account || false);
setBasicModels(getChannelModels(data.type));
// channelSettings
setChannelSettings({
@ -716,6 +725,8 @@ const EditChannelModal = (props) => {
});
//
setKeyMode('append');
//
setIsEnterpriseAccount(false);
// key_mode
if (formApiRef.current) {
formApiRef.current.setValue('key_mode', undefined);
@ -879,6 +890,21 @@ const EditChannelModal = (props) => {
};
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);
}
}
// truefalse
settings.openrouter_enterprise = localInputs.is_enterprise_account === true;
localInputs.settings = JSON.stringify(settings);
}
//
delete localInputs.force_format;
delete localInputs.thinking_to_content;
@ -886,6 +912,7 @@ const EditChannelModal = (props) => {
delete localInputs.pass_through_body_enabled;
delete localInputs.system_prompt;
delete localInputs.system_prompt_override;
delete localInputs.is_enterprise_account;
// vertex_key_type
delete localInputs.vertex_key_type;
@ -1203,6 +1230,21 @@ const EditChannelModal = (props) => {
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
field='name'
label={t('名称')}