Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/api_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ func ChannelType2APIType(channelType int) (int, bool) {
apiType = constant.APITypeSub2API
case constant.ChannelTypeNewAPI:
apiType = constant.APITypeNewAPI
case constant.ChannelTypeNadir:
apiType = constant.APITypeNadir
}
if apiType == -1 {
return constant.APITypeOpenAI, false
Expand Down
2 changes: 1 addition & 1 deletion common/endpoint_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func GetEndpointTypesByChannelType(channelType int, modelName string) []constant
fallthrough
case constant.ChannelTypeGemini:
endpointTypes = []constant.EndpointType{constant.EndpointTypeGemini, constant.EndpointTypeOpenAI}
case constant.ChannelTypeOpenRouter: // OpenRouter 只支持 OpenAI 端点
case constant.ChannelTypeOpenRouter, constant.ChannelTypeNadir: // OpenRouter / Nadir 只支持 OpenAI 端点
endpointTypes = []constant.EndpointType{constant.EndpointTypeOpenAI}
case constant.ChannelTypeXai:
endpointTypes = []constant.EndpointType{constant.EndpointTypeOpenAI, constant.EndpointTypeOpenAIResponse}
Expand Down
1 change: 1 addition & 0 deletions constant/api_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ const (
APITypeAdvancedCustom
APITypeSub2API
APITypeNewAPI
APITypeNadir
APITypeDummy // this one is only for count, do not add any channel after this
)
3 changes: 3 additions & 0 deletions constant/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const (
ChannelTypeAdvancedCustom = 58
ChannelTypeSub2API = 59
ChannelTypeNewAPI = 60
ChannelTypeNadir = 61
ChannelTypeDummy // this one is only for count, do not add any channel after this

)
Expand Down Expand Up @@ -124,6 +125,7 @@ var ChannelBaseURLs = []string{
"", //58
"", //59
"", //60
"https://api.getnadir.com", //61
}

var ChannelTypeNames = map[int]string{
Expand Down Expand Up @@ -184,6 +186,7 @@ var ChannelTypeNames = map[int]string{
ChannelTypeAdvancedCustom: "Advanced Custom",
ChannelTypeSub2API: "Sub2API",
ChannelTypeNewAPI: "New API",
ChannelTypeNadir: "Nadir",
}

func GetChannelTypeName(channelType int) string {
Expand Down
5 changes: 5 additions & 0 deletions controller/model_owned_by_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ func TestChannelOwnerNameUsesAdaptorChannelName(t *testing.T) {
channelType: constant.ChannelTypeOpenRouter,
expected: "openrouter",
},
{
name: "nadir",
channelType: constant.ChannelTypeNadir,
expected: "nadir",
},
{
name: "azure fallback",
channelType: constant.ChannelTypeAzure,
Expand Down
14 changes: 14 additions & 0 deletions relay/channel/nadir/constant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nadir

// ModelList is the set of model ids this channel exposes.
//
// Nadir is an OpenAI-compatible router: send "auto" and it classifies the
// prompt, then routes to the cheapest model that clears its quality bar. The
// response `model` field reports the model it actually routed to. Only "auto"
// is listed, because pinning a concrete model here would bypass the routing
// that is the whole point of the channel.
var ModelList = []string{"auto"}

// ChannelName is the adaptor name reported for this channel, and is what
// surfaces as the model owner in /v1/models responses.
var ChannelName = "nadir"
5 changes: 5 additions & 0 deletions relay/channel/openai/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/QuantumNous/new-api/relay/channel"
"github.com/QuantumNous/new-api/relay/channel/ai360"
"github.com/QuantumNous/new-api/relay/channel/lingyiwanwu"
"github.com/QuantumNous/new-api/relay/channel/nadir"
"github.com/QuantumNous/new-api/relaykit/dto"

//"github.com/QuantumNous/new-api/relay/channel/minimax"
Expand Down Expand Up @@ -680,6 +681,8 @@ func (a *Adaptor) GetModelList() []string {
return xinference.ModelList
case constant.ChannelTypeOpenRouter:
return openrouter.ModelList
case constant.ChannelTypeNadir:
return nadir.ModelList
default:
return ModelList
}
Expand All @@ -697,6 +700,8 @@ func (a *Adaptor) GetChannelName() string {
return xinference.ChannelName
case constant.ChannelTypeOpenRouter:
return openrouter.ChannelName
case constant.ChannelTypeNadir:
return nadir.ChannelName
default:
return ChannelName
}
Expand Down
72 changes: 72 additions & 0 deletions relay/channel/openai/nadir_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package openai

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
relaycommon "github.com/QuantumNous/new-api/relay/common"
relayconstant "github.com/QuantumNous/new-api/relay/constant"
"github.com/QuantumNous/new-api/relaykit/types"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// nadirRelayInfo builds the minimal RelayInfo a Nadir request carries, so the
// tests below exercise URL construction and header setup the same way the
// relay does at runtime.
func nadirRelayInfo() *relaycommon.RelayInfo {
return &relaycommon.RelayInfo{
RelayFormat: types.RelayFormatOpenAI,
RelayMode: relayconstant.RelayModeChatCompletions,
RequestURLPath: "/v1/chat/completions",
OriginModelName: "auto",
ChannelMeta: &relaycommon.ChannelMeta{
ApiKey: "sk-test",
ChannelBaseUrl: constant.ChannelBaseURLs[constant.ChannelTypeNadir],
ChannelType: constant.ChannelTypeNadir,
UpstreamModelName: "auto",
},
}
}

// TestNadirChannelRelaysToOpenAIChatCompletionsWithBearerAuth pins the wire
// contract: the request goes to <base>/v1/chat/completions and carries the key
// as a Bearer token, which is what lets Nadir reuse the shared OpenAI adaptor
// with no request translation.
func TestNadirChannelRelaysToOpenAIChatCompletionsWithBearerAuth(t *testing.T) {
adaptor := &Adaptor{}
info := nadirRelayInfo()
adaptor.Init(info)

requestURL, err := adaptor.GetRequestURL(info)
require.NoError(t, err)
assert.Equal(t, "https://api.getnadir.com/v1/chat/completions", requestURL)

gin.SetMode(gin.TestMode)
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
header := http.Header{}

require.NoError(t, adaptor.SetupRequestHeader(c, &header, info))
assert.Equal(t, "Bearer sk-test", header.Get("Authorization"))
}

// TestNadirChannelExposesAutoModelOverOpenAIEndpointOnly pins that the channel
// advertises exactly the "auto" model and claims only the OpenAI endpoint type.
// Claiming more would offer callers endpoints the router does not serve.
func TestNadirChannelExposesAutoModelOverOpenAIEndpointOnly(t *testing.T) {
adaptor := &Adaptor{}
adaptor.Init(nadirRelayInfo())

assert.Equal(t, "nadir", adaptor.GetChannelName())
assert.Equal(t, []string{"auto"}, adaptor.GetModelList())
assert.Equal(
t,
[]constant.EndpointType{constant.EndpointTypeOpenAI},
common.GetEndpointTypesByChannelType(constant.ChannelTypeNadir, "auto"),
)
}
2 changes: 2 additions & 0 deletions relay/relay_adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ func GetAdaptor(apiType int) channel.Adaptor {
return &sub2api.Adaptor{}
case constant.APITypeNewAPI:
return &newapi.Adaptor{}
case constant.APITypeNadir:
return &openai.Adaptor{}
}
return nil
}
Expand Down
1 change: 1 addition & 0 deletions web/scripts/sync-i18n.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const BRAND_AND_LITERAL_KEYS = new Set([
'Mistral',
'MokaAI',
'Moonshot',
'Nadir',
'New API',
'New API &lt;noreply@example.com&gt;',
'NewAPI',
Expand Down
7 changes: 4 additions & 3 deletions web/src/features/channels/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ export const CHANNEL_TYPES = {
58: 'Advanced Custom',
59: 'Sub2API',
60: 'New API',
61: 'Nadir',
} as const

const CHANNEL_TYPE_DISPLAY_ORDER: number[] = [
1, 14, 33, 24, 43, 3, 41, 48, 60, 58, 42, 34, 20, 4, 40, 27, 25, 17, 26, 15,
46, 23, 18, 45, 31, 35, 49, 19, 47, 37, 38, 39, 11, 8, 57, 59, 22, 21, 44, 2,
5, 36, 50, 51, 52, 53, 54, 55, 56,
1, 14, 33, 24, 43, 3, 41, 48, 60, 58, 42, 34, 20, 61, 4, 40, 27, 25, 17, 26,
15, 46, 23, 18, 45, 31, 35, 49, 19, 47, 37, 38, 39, 11, 8, 57, 59, 22, 21, 44,
2, 5, 36, 50, 51, 52, 53, 54, 55, 56,
]

export const CHANNEL_TYPE_OPTIONS: { value: number; label: string }[] = (() => {
Expand Down
10 changes: 10 additions & 0 deletions web/src/features/channels/lib/channel-type-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ export const CHANNEL_TYPE_CONFIGS: Record<number, ChannelTypeConfig> = {
models: 'Use model IDs from OpenRouter',
},
},
61: {
id: 61,
name: CHANNEL_TYPES[61],
icon: 'OpenAI',
defaultBaseUrl: 'https://api.getnadir.com',
hints: {
key: 'Nadir API Key',
models: 'Use "auto" to let Nadir pick the model',
},
},
56: {
id: 56,
name: CHANNEL_TYPES[56],
Expand Down
1 change: 1 addition & 0 deletions web/src/features/channels/lib/channel-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export function getChannelTypeIcon(type: number): string {
40: 'SiliconCloud', // SiliconFlow
44: 'OpenAI', // MokaAI
20: 'OpenRouter', // OpenRouter
61: 'OpenAI', // Nadir (OpenAI-compatible router, no dedicated icon)

// Image/Video generation
2: 'Midjourney', // MjProxy
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@
"MySQL is a production-ready relational database. Keep your credentials secure.": "MySQL is a production-ready relational database. Keep your credentials secure.",
"MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.": "MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.",
"N/A": "N/A",
"Nadir": "Nadir",
"Name": "Name",
"Name *": "Name *",
"Name for this redemption code (1-20 characters)": "Name for this redemption code (1-20 characters)",
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@
"MySQL is a production-ready relational database. Keep your credentials secure.": "MySQL est une base de données relationnelle prête pour la production. Gardez vos identifiants en sécurité.",
"MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.": "MySQL est prêt pour la production. Assurez-vous que des sauvegardes automatisées et un utilisateur dédié avec les privilèges minimaux requis sont configurés.",
"N/A": "N/A",
"Nadir": "Nadir",
"Name": "Nom",
"Name *": "Nom *",
"Name for this redemption code (1-20 characters)": "Nom pour ce code d'échange (1-20 caractères)",
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@
"MySQL is a production-ready relational database. Keep your credentials secure.": "MySQL は本番環境対応のリレーショナルデータベースです。認証情報を安全に管理してください。",
"MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.": "MySQLは本番環境に対応しています。自動バックアップと、最小限必要な権限を持つ専用ユーザーが設定されていることを確認してください。",
"N/A": "該当なし",
"Nadir": "Nadir",
"Name": "名称",
"Name *": "名前 *",
"Name for this redemption code (1-20 characters)": "この引き換えコードの名前(1~20文字)",
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@
"MySQL is a production-ready relational database. Keep your credentials secure.": "MySQL — реляционная база данных, готовая к продакшену. Храните учётные данные в безопасности.",
"MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.": "MySQL готов к работе. Убедитесь, что настроены автоматические резервные копии и выделенный пользователь с минимально необходимыми привилегиями.",
"N/A": "Н/Д",
"Nadir": "Nadir",
"Name": "Название",
"Name *": "Имя *",
"Name for this redemption code (1-20 characters)": "Имя для этого кода активации (1-20 символов)",
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@
"MySQL is a production-ready relational database. Keep your credentials secure.": "MySQL là cơ sở dữ liệu quan hệ sẵn sàng cho production. Hãy giữ thông tin đăng nhập an toàn.",
"MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.": "MySQL đã sẵn sàng để triển khai sản xuất. Đảm bảo sao lưu tự động và một người dùng chuyên dụng với các đặc quyền tối thiểu cần thiết được cấu hình.",
"N/A": "N/A",
"Nadir": "Nadir",
"Name": "Tên",
"Name *": "Tên *",
"Name for this redemption code (1-20 characters)": "Tên cho mã đổi quà này (1-20 ký tự)",
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@
"MySQL is a production-ready relational database. Keep your credentials secure.": "MySQL 是生產就緒的關聯式資料庫。請確保憑證安全。",
"MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.": "MySQL 已準備好投入生產環境。請確保設定了自動備份以及具有最低所需權限的專用用戶。",
"N/A": "不適用",
"Nadir": "Nadir",
"Name": "名稱",
"Name *": "名稱 *",
"Name for this redemption code (1-20 characters)": "此兌換碼的名稱(1-20 個字元)",
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,7 @@
"MySQL is a production-ready relational database. Keep your credentials secure.": "MySQL 是生产就绪的关系数据库。请确保凭据安全。",
"MySQL is production ready. Ensure automated backups and a dedicated user with the minimal required privileges are configured.": "MySQL 已准备好投入生产环境。请确保配置了自动备份以及具有最低所需权限的专用用户。",
"N/A": "不适用",
"Nadir": "Nadir",
"Name": "名称",
"Name *": "名称 *",
"Name for this redemption code (1-20 characters)": "此兑换码的名称(1-20 个字符)",
Expand Down