feat: add New API channel support
This commit is contained in:
@@ -21,6 +21,8 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
// All label/name values are i18n keys; use t(value) when displaying.
|
||||
// ============================================================================
|
||||
|
||||
export const CHANNEL_TYPE_NEW_API = 60
|
||||
|
||||
export const CHANNEL_TYPES = {
|
||||
0: 'Unknown',
|
||||
1: 'OpenAI',
|
||||
@@ -78,12 +80,13 @@ export const CHANNEL_TYPES = {
|
||||
57: 'ChatGPT Subscription (Codex)',
|
||||
58: 'Advanced Custom',
|
||||
59: 'Sub2API',
|
||||
60: 'New API',
|
||||
} as const
|
||||
|
||||
const CHANNEL_TYPE_DISPLAY_ORDER: number[] = [
|
||||
1, 14, 33, 24, 43, 3, 41, 48, 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, 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 }[] = (() => {
|
||||
@@ -381,7 +384,7 @@ export const FIELD_DESCRIPTIONS = {
|
||||
|
||||
export const MODEL_FETCHABLE_TYPES = new Set([
|
||||
1, 4, 14, 17, 20, 23, 24, 25, 26, 27, 31, 34, 35, 40, 42, 43, 47, 48, 57, 58,
|
||||
59,
|
||||
59, 60,
|
||||
])
|
||||
|
||||
export const TYPE_TO_KEY_PROMPT: Record<number, string> = {
|
||||
@@ -394,6 +397,7 @@ export const TYPE_TO_KEY_PROMPT: Record<number, string> = {
|
||||
51: 'Format: Access Key ID|Secret Access Key',
|
||||
57: 'Paste Codex OAuth JSON credential (access_token / refresh_token / account_id)',
|
||||
59: 'Enter API key for this channel',
|
||||
60: 'Enter API key for this channel',
|
||||
}
|
||||
|
||||
export const CHANNEL_TYPE_WARNINGS: Record<number, string> = {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import { describe, test } from 'node:test'
|
||||
|
||||
import {
|
||||
CHANNEL_TYPE_NEW_API,
|
||||
CHANNEL_TYPE_OPTIONS,
|
||||
MODEL_FETCHABLE_TYPES,
|
||||
} from '../../constants'
|
||||
import { CHANNEL_FORM_DEFAULT_VALUES, channelFormSchema } from '../channel-form'
|
||||
import { getChannelTypeConfig } from '../channel-type-config'
|
||||
import { getChannelTypeIcon, getKeyPromptForType } from '../channel-utils'
|
||||
|
||||
function newAPIForm(baseUrl: string) {
|
||||
return {
|
||||
...CHANNEL_FORM_DEFAULT_VALUES,
|
||||
name: 'New API upstream',
|
||||
type: CHANNEL_TYPE_NEW_API,
|
||||
base_url: baseUrl,
|
||||
key: 'test-key',
|
||||
models: 'gpt-5',
|
||||
}
|
||||
}
|
||||
|
||||
describe('New API channel', () => {
|
||||
test('registers selection, ordering, model discovery, and icon metadata', () => {
|
||||
const option = CHANNEL_TYPE_OPTIONS.find(
|
||||
(item) => item.value === CHANNEL_TYPE_NEW_API
|
||||
)
|
||||
|
||||
assert.deepEqual(option, {
|
||||
value: CHANNEL_TYPE_NEW_API,
|
||||
label: 'New API',
|
||||
})
|
||||
assert.equal(
|
||||
CHANNEL_TYPE_OPTIONS.findIndex(
|
||||
(item) => item.value === CHANNEL_TYPE_NEW_API
|
||||
) + 1,
|
||||
CHANNEL_TYPE_OPTIONS.findIndex((item) => item.value === 58)
|
||||
)
|
||||
assert.equal(MODEL_FETCHABLE_TYPES.has(CHANNEL_TYPE_NEW_API), true)
|
||||
assert.equal(getChannelTypeIcon(CHANNEL_TYPE_NEW_API), 'NewAPI')
|
||||
assert.equal(
|
||||
getKeyPromptForType(CHANNEL_TYPE_NEW_API),
|
||||
'Enter API key for this channel'
|
||||
)
|
||||
assert.equal(getChannelTypeConfig(CHANNEL_TYPE_NEW_API).icon, 'NewAPI')
|
||||
})
|
||||
|
||||
test('requires a non-blank Base URL', () => {
|
||||
const blankResult = channelFormSchema.safeParse(newAPIForm(' '))
|
||||
|
||||
assert.equal(blankResult.success, false)
|
||||
if (!blankResult.success) {
|
||||
assert.equal(
|
||||
blankResult.error.issues.some(
|
||||
(issue) =>
|
||||
issue.path[0] === 'base_url' &&
|
||||
issue.message === 'Base URL is required for this channel type'
|
||||
),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
channelFormSchema.safeParse(newAPIForm('https://new-api.example'))
|
||||
.success,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('keeps Sub2API Base URL validation unchanged', () => {
|
||||
const result = channelFormSchema.safeParse({
|
||||
...newAPIForm(''),
|
||||
type: 59,
|
||||
})
|
||||
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
})
|
||||
@@ -19,6 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { z } from 'zod'
|
||||
|
||||
import {
|
||||
CHANNEL_TYPE_NEW_API,
|
||||
CHANNEL_STATUS,
|
||||
ERROR_MESSAGES,
|
||||
MODEL_FETCHABLE_TYPES,
|
||||
@@ -247,7 +248,10 @@ export const channelFormSchema = z
|
||||
upstream_model_update_ignored_models: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if ([3, 8, 36, 45].includes(data.type) && !data.base_url?.trim()) {
|
||||
if (
|
||||
[3, 8, 36, 45, CHANNEL_TYPE_NEW_API].includes(data.type) &&
|
||||
!data.base_url?.trim()
|
||||
) {
|
||||
addRequiredIssue(
|
||||
ctx,
|
||||
'base_url',
|
||||
|
||||
@@ -154,6 +154,16 @@ export const CHANNEL_TYPE_CONFIGS: Record<number, ChannelTypeConfig> = {
|
||||
models: 'Models fetched from upstream /v1/models',
|
||||
},
|
||||
},
|
||||
60: {
|
||||
id: 60,
|
||||
name: CHANNEL_TYPES[60],
|
||||
icon: 'NewAPI',
|
||||
hints: {
|
||||
baseUrl: 'Base URL is required for this channel type',
|
||||
key: 'Enter API key for this channel',
|
||||
models: 'Models',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,6 +53,7 @@ export function getChannelTypeIcon(type: number): string {
|
||||
8: 'OpenAI', // Custom
|
||||
58: 'NewAPI', // Advanced Custom
|
||||
59: 'Sub2API', // Sub2API
|
||||
60: 'NewAPI', // New API
|
||||
3: 'Azure', // Azure
|
||||
|
||||
// Anthropic
|
||||
|
||||
Reference in New Issue
Block a user