fix builtin provider flags (#1125)
This commit is contained in:
@@ -99,6 +99,7 @@ function providerPresetToGroup(p: any, models?: string[]): AvailableGroup {
|
|||||||
base_url: p.base_url,
|
base_url: p.base_url,
|
||||||
models: models || p.models,
|
models: models || p.models,
|
||||||
api_key: '',
|
api_key: '',
|
||||||
|
...(p.builtin ? { builtin: true } : {}),
|
||||||
...(envMapping?.base_url_env ? { base_url_env: envMapping.base_url_env } : {}),
|
...(envMapping?.base_url_env ? { base_url_env: envMapping.base_url_env } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +182,15 @@ function providerKeyForCustom(name: string): string {
|
|||||||
return `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`
|
return `custom:${name.trim().toLowerCase().replace(/ /g, '-')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerKeyWithoutCustomPrefix(providerKey: string): string {
|
||||||
|
return providerKey.startsWith('custom:') ? providerKey.slice('custom:'.length) : providerKey
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBuiltinProviderKey(providerKey: string): boolean {
|
||||||
|
const normalized = providerKeyWithoutCustomPrefix(providerKey)
|
||||||
|
return PROVIDER_PRESETS.some((preset: any) => preset.value === normalized && preset.builtin === true)
|
||||||
|
}
|
||||||
|
|
||||||
function providerShouldFetchLiveModels(providerKey: string): boolean {
|
function providerShouldFetchLiveModels(providerKey: string): boolean {
|
||||||
return providerKey === 'openrouter' ||
|
return providerKey === 'openrouter' ||
|
||||||
providerKey === 'cliproxyapi' ||
|
providerKey === 'cliproxyapi' ||
|
||||||
@@ -402,13 +412,13 @@ async function buildAvailableForProfile(
|
|||||||
const fetched = await cachedProviderModels(fetchCache, baseUrl, cp.api_key)
|
const fetched = await cachedProviderModels(fetchCache, baseUrl, cp.api_key)
|
||||||
if (fetched.length > 0) models = [...new Set([...models, ...fetched])]
|
if (fetched.length > 0) models = [...new Set([...models, ...fetched])]
|
||||||
}
|
}
|
||||||
return { providerKey, label: cp.name, base_url: baseUrl, models, api_key: cp.api_key || '' }
|
return { providerKey, label: cp.name, base_url: baseUrl, models, api_key: cp.api_key || '', builtin: isBuiltinProviderKey(providerKey) }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
for (const result of customFetches) {
|
for (const result of customFetches) {
|
||||||
if (result.status === 'fulfilled' && result.value?.models.length) {
|
if (result.status === 'fulfilled' && result.value?.models.length) {
|
||||||
const { providerKey, label, base_url, models, api_key } = result.value
|
const { providerKey, label, base_url, models, api_key, builtin } = result.value
|
||||||
addGroup(providerKey, label, base_url, models, api_key)
|
addGroup(providerKey, label, base_url, models, api_key, builtin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,7 +665,7 @@ export async function getAvailable(ctx: any) {
|
|||||||
if (cp.api_key) {
|
if (cp.api_key) {
|
||||||
try { const fetched = await fetchProviderModels(baseUrl, cp.api_key); if (fetched.length > 0) models = [...new Set([cp.model, ...fetched])] } catch { }
|
try { const fetched = await fetchProviderModels(baseUrl, cp.api_key); if (fetched.length > 0) models = [...new Set([cp.model, ...fetched])] } catch { }
|
||||||
}
|
}
|
||||||
return { providerKey, label: cp.name, base_url: baseUrl, models, api_key: cp.api_key || '' }
|
return { providerKey, label: cp.name, base_url: baseUrl, models, api_key: cp.api_key || '', builtin: isBuiltinProviderKey(providerKey) }
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [
|
|||||||
base_url: 'https://api.apikey.fun',
|
base_url: 'https://api.apikey.fun',
|
||||||
api_mode: "anthropic_messages",
|
api_mode: "anthropic_messages",
|
||||||
models: [
|
models: [
|
||||||
|
'claude-opus-4-8',
|
||||||
'claude-opus-4-7',
|
'claude-opus-4-7',
|
||||||
'claude-opus-4-6',
|
'claude-opus-4-6',
|
||||||
'claude-sonnet-4-6',
|
'claude-sonnet-4-6',
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ vi.mock('../../packages/server/src/services/config-helpers', () => ({
|
|||||||
fetchProviderModels: mockFetchProviderModels,
|
fetchProviderModels: mockFetchProviderModels,
|
||||||
buildModelGroups: mockBuildModelGroups,
|
buildModelGroups: mockBuildModelGroups,
|
||||||
PROVIDER_ENV_MAP: {
|
PROVIDER_ENV_MAP: {
|
||||||
|
'fun-codex': { api_key_env: '', base_url_env: '' },
|
||||||
deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
|
deepseek: { api_key_env: 'DEEPSEEK_API_KEY', base_url_env: 'DEEPSEEK_BASE_URL' },
|
||||||
lmstudio: { api_key_env: 'LM_API_KEY', base_url_env: 'LM_BASE_URL' },
|
lmstudio: { api_key_env: 'LM_API_KEY', base_url_env: 'LM_BASE_URL' },
|
||||||
'xai-oauth': { api_key_env: '', base_url_env: '' },
|
'xai-oauth': { api_key_env: '', base_url_env: '' },
|
||||||
@@ -56,29 +57,40 @@ vi.mock('../../packages/server/src/shared/providers', () => ({
|
|||||||
openrouter: ['openrouter/auto'],
|
openrouter: ['openrouter/auto'],
|
||||||
}),
|
}),
|
||||||
PROVIDER_PRESETS: [
|
PROVIDER_PRESETS: [
|
||||||
|
{
|
||||||
|
value: 'fun-codex',
|
||||||
|
label: 'Codex-apikey.fun',
|
||||||
|
base_url: 'https://api.apikey.fun/v1',
|
||||||
|
models: ['gpt-5.5'],
|
||||||
|
builtin: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'deepseek',
|
value: 'deepseek',
|
||||||
label: 'DeepSeek',
|
label: 'DeepSeek',
|
||||||
base_url: 'https://api.deepseek.com/v1',
|
base_url: 'https://api.deepseek.com/v1',
|
||||||
models: ['deepseek-chat', 'deepseek-reasoner'],
|
models: ['deepseek-chat', 'deepseek-reasoner'],
|
||||||
|
builtin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'openrouter',
|
value: 'openrouter',
|
||||||
label: 'OpenRouter',
|
label: 'OpenRouter',
|
||||||
base_url: 'https://openrouter.ai/api/v1',
|
base_url: 'https://openrouter.ai/api/v1',
|
||||||
models: ['openrouter/auto'],
|
models: ['openrouter/auto'],
|
||||||
|
builtin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'lmstudio',
|
value: 'lmstudio',
|
||||||
label: 'LM Studio',
|
label: 'LM Studio',
|
||||||
base_url: 'http://127.0.0.1:1234/v1',
|
base_url: 'http://127.0.0.1:1234/v1',
|
||||||
models: [],
|
models: [],
|
||||||
|
builtin: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'xai-oauth',
|
value: 'xai-oauth',
|
||||||
label: 'xAI Grok OAuth (SuperGrok Subscription)',
|
label: 'xAI Grok OAuth (SuperGrok Subscription)',
|
||||||
base_url: 'https://api.x.ai/v1',
|
base_url: 'https://api.x.ai/v1',
|
||||||
models: ['grok-4.3', 'grok-4.20-0309-reasoning'],
|
models: ['grok-4.3', 'grok-4.20-0309-reasoning'],
|
||||||
|
builtin: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
@@ -277,6 +289,7 @@ describe('models controller — model visibility', () => {
|
|||||||
expect(ctx.body.allProviders).toEqual(expect.arrayContaining([
|
expect(ctx.body.allProviders).toEqual(expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: 'deepseek',
|
provider: 'deepseek',
|
||||||
|
builtin: true,
|
||||||
base_url_env: 'DEEPSEEK_BASE_URL',
|
base_url_env: 'DEEPSEEK_BASE_URL',
|
||||||
}),
|
}),
|
||||||
expect.not.objectContaining({
|
expect.not.objectContaining({
|
||||||
@@ -286,6 +299,31 @@ describe('models controller — model visibility', () => {
|
|||||||
]))
|
]))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('marks custom-prefixed providers as builtin when their provider key matches a preset', async () => {
|
||||||
|
mockReadConfigYamlForProfile.mockResolvedValue({
|
||||||
|
model: { default: 'gpt-5.5', provider: 'custom:fun-codex' },
|
||||||
|
custom_providers: [
|
||||||
|
{
|
||||||
|
name: 'fun-codex',
|
||||||
|
base_url: 'https://proxy.example.com/v1',
|
||||||
|
model: 'gpt-5.5',
|
||||||
|
api_key: 'sk-test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const ctx = makeCtx()
|
||||||
|
ctx.query = { profile: 'default' }
|
||||||
|
await ctrl.getAvailable(ctx)
|
||||||
|
|
||||||
|
expect(ctx.body.groups).toEqual(expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
provider: 'custom:fun-codex',
|
||||||
|
builtin: true,
|
||||||
|
}),
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
|
||||||
it('returns LM Studio configured default model when env credentials exist and catalog is empty', async () => {
|
it('returns LM Studio configured default model when env credentials exist and catalog is empty', async () => {
|
||||||
mockReadFile.mockResolvedValue('LM_API_KEY=local\nLM_BASE_URL=http://127.0.0.1:1234/v1\n')
|
mockReadFile.mockResolvedValue('LM_API_KEY=local\nLM_BASE_URL=http://127.0.0.1:1234/v1\n')
|
||||||
mockReadConfigYaml.mockResolvedValue({ model: { default: 'eee', provider: 'lmstudio' } })
|
mockReadConfigYaml.mockResolvedValue({ model: { default: 'eee', provider: 'lmstudio' } })
|
||||||
|
|||||||
Reference in New Issue
Block a user