feat: add New API channel support
This commit is contained in:
@@ -22,7 +22,10 @@ func AlphaSearchHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError
|
||||
info.InitChannelMeta(c)
|
||||
|
||||
switch info.ChannelType {
|
||||
case constant.ChannelTypeSub2API, constant.ChannelTypeCodex, constant.ChannelTypeAdvancedCustom:
|
||||
case constant.ChannelTypeSub2API,
|
||||
constant.ChannelTypeNewAPI,
|
||||
constant.ChannelTypeCodex,
|
||||
constant.ChannelTypeAdvancedCustom:
|
||||
default:
|
||||
// Allow retry onto another channel that may support this endpoint.
|
||||
return types.NewError(
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package newapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/QuantumNous/new-api/dto"
|
||||
"github.com/QuantumNous/new-api/relay/channel"
|
||||
"github.com/QuantumNous/new-api/relay/channel/claude"
|
||||
"github.com/QuantumNous/new-api/relay/channel/gemini"
|
||||
"github.com/QuantumNous/new-api/relay/channel/openai"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
relayconstant "github.com/QuantumNous/new-api/relay/constant"
|
||||
"github.com/QuantumNous/new-api/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Adaptor struct {
|
||||
openaiAdaptor openai.Adaptor
|
||||
claudeAdaptor claude.Adaptor
|
||||
geminiAdaptor gemini.Adaptor
|
||||
}
|
||||
|
||||
func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
|
||||
a.openaiAdaptor.Init(info)
|
||||
a.claudeAdaptor.Init(info)
|
||||
a.geminiAdaptor.Init(info)
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||
if info.RelayMode == relayconstant.RelayModeAlphaSearch {
|
||||
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, "/v1/alpha/search", info.ChannelType), nil
|
||||
}
|
||||
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, info.RequestURLPath, info.ChannelType), nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
|
||||
channel.SetupApiRequestHeader(info, c, req)
|
||||
req.Set("Authorization", "Bearer "+info.ApiKey)
|
||||
|
||||
switch info.RelayFormat {
|
||||
case types.RelayFormatClaude:
|
||||
req.Set("x-api-key", info.ApiKey)
|
||||
if req.Get("anthropic-version") == "" {
|
||||
anthropicVersion := c.Request.Header.Get("anthropic-version")
|
||||
if anthropicVersion == "" {
|
||||
anthropicVersion = "2023-06-01"
|
||||
}
|
||||
req.Set("anthropic-version", anthropicVersion)
|
||||
}
|
||||
case types.RelayFormatGemini:
|
||||
req.Set("x-goog-api-key", info.ApiKey)
|
||||
}
|
||||
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")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.ClaudeRequest) (any, error) {
|
||||
if request == nil {
|
||||
return nil, errors.New("request is nil")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertGeminiRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeminiChatRequest) (any, error) {
|
||||
if request == nil {
|
||||
return nil, errors.New("request is nil")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
|
||||
return nil, errors.New("endpoint not supported")
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
|
||||
return nil, errors.New("endpoint not supported")
|
||||
}
|
||||
|
||||
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) {
|
||||
switch info.RelayFormat {
|
||||
case types.RelayFormatClaude:
|
||||
return a.claudeAdaptor.DoResponse(c, resp, info)
|
||||
case types.RelayFormatGemini:
|
||||
return a.geminiAdaptor.DoResponse(c, resp, info)
|
||||
default:
|
||||
return a.openaiAdaptor.DoResponse(c, resp, info)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetModelList() []string {
|
||||
return ModelList
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetChannelName() string {
|
||||
return ChannelName
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package newapi
|
||||
|
||||
const ChannelName = "newapi"
|
||||
|
||||
// ModelList is empty because models are fetched dynamically from upstream /v1/models.
|
||||
var ModelList = []string{}
|
||||
@@ -1,115 +1,11 @@
|
||||
package sub2api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/QuantumNous/new-api/dto"
|
||||
"github.com/QuantumNous/new-api/relay/channel"
|
||||
"github.com/QuantumNous/new-api/relay/channel/claude"
|
||||
"github.com/QuantumNous/new-api/relay/channel/gemini"
|
||||
"github.com/QuantumNous/new-api/relay/channel/openai"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
relayconstant "github.com/QuantumNous/new-api/relay/constant"
|
||||
"github.com/QuantumNous/new-api/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/QuantumNous/new-api/relay/channel/newapi"
|
||||
)
|
||||
|
||||
type Adaptor struct {
|
||||
openaiAdaptor openai.Adaptor
|
||||
claudeAdaptor claude.Adaptor
|
||||
geminiAdaptor gemini.Adaptor
|
||||
}
|
||||
|
||||
func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
|
||||
a.openaiAdaptor.Init(info)
|
||||
a.claudeAdaptor.Init(info)
|
||||
a.geminiAdaptor.Init(info)
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||
if info.RelayMode == relayconstant.RelayModeAlphaSearch {
|
||||
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, "/v1/alpha/search", info.ChannelType), nil
|
||||
}
|
||||
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, info.RequestURLPath, info.ChannelType), nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
|
||||
channel.SetupApiRequestHeader(info, c, req)
|
||||
req.Set("Authorization", "Bearer "+info.ApiKey)
|
||||
|
||||
switch info.RelayFormat {
|
||||
case types.RelayFormatClaude:
|
||||
req.Set("x-api-key", info.ApiKey)
|
||||
if req.Get("anthropic-version") == "" {
|
||||
anthropicVersion := c.Request.Header.Get("anthropic-version")
|
||||
if anthropicVersion == "" {
|
||||
anthropicVersion = "2023-06-01"
|
||||
}
|
||||
req.Set("anthropic-version", anthropicVersion)
|
||||
}
|
||||
case types.RelayFormatGemini:
|
||||
req.Set("x-goog-api-key", info.ApiKey)
|
||||
}
|
||||
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")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertOpenAIResponsesRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.OpenAIResponsesRequest) (any, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.ClaudeRequest) (any, error) {
|
||||
if request == nil {
|
||||
return nil, errors.New("request is nil")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertGeminiRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeminiChatRequest) (any, error) {
|
||||
if request == nil {
|
||||
return nil, errors.New("request is nil")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
|
||||
return nil, errors.New("endpoint not supported")
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
|
||||
return nil, errors.New("endpoint not supported")
|
||||
}
|
||||
|
||||
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) {
|
||||
switch info.RelayFormat {
|
||||
case types.RelayFormatClaude:
|
||||
return a.claudeAdaptor.DoResponse(c, resp, info)
|
||||
case types.RelayFormatGemini:
|
||||
return a.geminiAdaptor.DoResponse(c, resp, info)
|
||||
default:
|
||||
return a.openaiAdaptor.DoResponse(c, resp, info)
|
||||
}
|
||||
newapi.Adaptor
|
||||
}
|
||||
|
||||
func (a *Adaptor) GetModelList() []string {
|
||||
|
||||
@@ -25,3 +25,22 @@ func TestGetRequestURLAlphaSearch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://sub2api.example/v1/alpha/search", url)
|
||||
}
|
||||
|
||||
func TestAdaptorInheritsNewAPIResponsesCompactSupport(t *testing.T) {
|
||||
adaptor := &Adaptor{}
|
||||
info := &relaycommon.RelayInfo{
|
||||
ChannelMeta: &relaycommon.ChannelMeta{
|
||||
ChannelType: constant.ChannelTypeSub2API,
|
||||
ChannelBaseUrl: "https://sub2api.example",
|
||||
},
|
||||
RequestURLPath: "/v1/responses/compact",
|
||||
RelayMode: relayconstant.RelayModeResponsesCompact,
|
||||
}
|
||||
|
||||
url, err := adaptor.GetRequestURL(info)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://sub2api.example/v1/responses/compact", url)
|
||||
assert.Equal(t, "sub2api", adaptor.GetChannelName())
|
||||
assert.Empty(t, adaptor.GetModelList())
|
||||
}
|
||||
|
||||
@@ -343,6 +343,7 @@ var streamSupportedChannels = map[int]bool{
|
||||
constant.ChannelTypeSiliconFlow: true,
|
||||
constant.ChannelTypeAdvancedCustom: true,
|
||||
constant.ChannelTypeSub2API: true,
|
||||
constant.ChannelTypeNewAPI: true,
|
||||
constant.ChannelTypeTencent: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/relay/channel/mistral"
|
||||
"github.com/QuantumNous/new-api/relay/channel/mokaai"
|
||||
"github.com/QuantumNous/new-api/relay/channel/moonshot"
|
||||
"github.com/QuantumNous/new-api/relay/channel/newapi"
|
||||
"github.com/QuantumNous/new-api/relay/channel/ollama"
|
||||
"github.com/QuantumNous/new-api/relay/channel/openai"
|
||||
"github.com/QuantumNous/new-api/relay/channel/palm"
|
||||
@@ -126,6 +127,8 @@ func GetAdaptor(apiType int) channel.Adaptor {
|
||||
return &advancedcustom.Adaptor{}
|
||||
case constant.APITypeSub2API:
|
||||
return &sub2api.Adaptor{}
|
||||
case constant.APITypeNewAPI:
|
||||
return &newapi.Adaptor{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
appconstant "github.com/QuantumNous/new-api/constant"
|
||||
"github.com/QuantumNous/new-api/dto"
|
||||
"github.com/QuantumNous/new-api/logger"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
@@ -22,17 +21,14 @@ import (
|
||||
|
||||
func ResponsesHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types.NewAPIError) {
|
||||
info.InitChannelMeta(c)
|
||||
if info.RelayMode == relayconstant.RelayModeResponsesCompact {
|
||||
switch info.ApiType {
|
||||
case appconstant.APITypeOpenAI, appconstant.APITypeCodex:
|
||||
default:
|
||||
return types.NewErrorWithStatusCode(
|
||||
fmt.Errorf("unsupported endpoint %q for api type %d", "/v1/responses/compact", info.ApiType),
|
||||
types.ErrorCodeInvalidRequest,
|
||||
http.StatusBadRequest,
|
||||
types.ErrOptionWithSkipRetry(),
|
||||
)
|
||||
}
|
||||
if info.RelayMode == relayconstant.RelayModeResponsesCompact &&
|
||||
!common.IsResponsesCompactAPIType(info.ApiType) {
|
||||
return types.NewErrorWithStatusCode(
|
||||
fmt.Errorf("unsupported endpoint %q for api type %d", "/v1/responses/compact", info.ApiType),
|
||||
types.ErrorCodeInvalidRequest,
|
||||
http.StatusBadRequest,
|
||||
types.ErrOptionWithSkipRetry(),
|
||||
)
|
||||
}
|
||||
|
||||
var responsesReq *dto.OpenAIResponsesRequest
|
||||
|
||||
Reference in New Issue
Block a user