From edbbcf5384b5ed183979fa1ccadf1f1e15cb66b5 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Mon, 5 Oct 2026 16:41:42 +0800 Subject: [PATCH] revert(provider-inference): remove API Route provider This reverts commit c097190cb7f69185bfb740ef1ce6b5da395fea8f. --- .../src/providers/cloud/api-route/index.ts | 53 ---- .../provider-inference/src/providers/index.ts | 280 +++++++++--------- .../src/providers/registry.test.ts | 92 +++--- .../stage-ui/src/libs/providers/attributes.ts | 1 - .../src/libs/providers/metadata.test.ts | 11 - 5 files changed, 184 insertions(+), 253 deletions(-) delete mode 100644 packages/provider-inference/src/providers/cloud/api-route/index.ts diff --git a/packages/provider-inference/src/providers/cloud/api-route/index.ts b/packages/provider-inference/src/providers/cloud/api-route/index.ts deleted file mode 100644 index c0bf6f0249..0000000000 --- a/packages/provider-inference/src/providers/cloud/api-route/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createOpenAI } from '@xsai-ext/providers/create' -import { z } from 'zod' - -import { ProviderValidationCheck } from '../../../types' -import { createOpenAICompatibleValidators } from '../../../validators' -import { defineProvider } from '../../registry' - -const apiRouteConfigSchema = z.object({ - apiKey: z - .string('API Key'), - baseUrl: z - .string('Base URL') - .optional() - .default('https://global.api-route.com/v1'), -}) - -type ApiRouteConfig = z.input - -export const providerApiRoute = defineProvider({ - id: 'api-route', - name: 'API Route', - nameLocalize: () => 'API Route', - description: 'api-route.com', - descriptionLocalize: () => 'api-route.com', - tasks: ['chat'], - icon: 'i-lobe-icons:openai', - - createProviderConfig: ({ t }) => apiRouteConfigSchema.extend({ - apiKey: apiRouteConfigSchema.shape.apiKey.meta({ - labelLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.api-key.label'), - descriptionLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.api-key.description'), - placeholderLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.api-key.placeholder'), - type: 'password', - }), - baseUrl: apiRouteConfigSchema.shape.baseUrl.meta({ - labelLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.base-url.label'), - descriptionLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.base-url.description'), - placeholderLocalized: t('settings.pages.providers.catalog.edit.config.common.fields.field.base-url.placeholder'), - }), - }), - createProvider(config) { - return createOpenAI(config.apiKey, config.baseUrl) - }, - - validationRequiredWhen(config) { - return !!config.apiKey?.trim() - }, - validators: { - ...createOpenAICompatibleValidators({ - checks: [ProviderValidationCheck.Connectivity, ProviderValidationCheck.ModelList, ProviderValidationCheck.ChatCompletions], - }), - }, -}) diff --git a/packages/provider-inference/src/providers/index.ts b/packages/provider-inference/src/providers/index.ts index ec06506c22..f7991fae90 100644 --- a/packages/provider-inference/src/providers/index.ts +++ b/packages/provider-inference/src/providers/index.ts @@ -1,150 +1,148 @@ -import type { ProviderDefinition } from '../types' - -import { provider302AI } from './cloud/302-ai' +import type { ProviderDefinition } from '../types' + +import { provider302AI } from './cloud/302-ai' import { providerAIHubMix } from './cloud/aihubmix' import { providerAmazonBedrock } from './cloud/amazon-bedrock' import { providerAnthropic } from './cloud/anthropic' -import { providerApiRoute } from './cloud/api-route' import { providerAtlasCloud } from './cloud/atlascloud' -import { providerAzureAIFoundry } from './cloud/azure-ai-foundry' -import { providerAzureOpenAI } from './cloud/azure-openai' -import { providerBytePlus } from './cloud/byteplus' -import { providerBytePlusCodingPlan } from './cloud/byteplus-coding-plan' -import { providerCerebrasAI } from './cloud/cerebras-ai' -import { providerCloudflareWorkersAI } from './cloud/cloudflare-workers-ai' -import { providerCometAPI, providerCometAPISpeech, providerCometAPITranscription } from './cloud/comet-api' -import { providerDeepSeek } from './cloud/deepseek' -import { providerElevenLabs } from './cloud/elevenlabs' -import { providerFeatherlessAI } from './cloud/featherless-ai' -import { providerFireworksAI } from './cloud/fireworks-ai' -import { providerGoogleGeminiAudioSpeech } from './cloud/google-gemini-audio-speech' -import { providerGoogleGenerativeAI } from './cloud/google-generative-ai' -import { providerGroq } from './cloud/groq' -import { providerMimo } from './cloud/mimo' -import { providerMimoAudioSpeech, providerMimoAudioTranscription } from './cloud/mimo-audio' -import { providerMinimax, providerMinimaxGlobal } from './cloud/minimax' -import { providerMinimaxSpeech } from './cloud/minimax-speech' -import { providerMistralAI } from './cloud/mistral-ai' -import { providerModelScope } from './cloud/modelscope' -import { providerMoonshotAI } from './cloud/moonshot-ai' -import { providerN1N } from './cloud/n1n' -import { providerNovitaAI } from './cloud/novita-ai' -import { providerOpenAI } from './cloud/openai' -import { - providerOpenAIAudioSpeech, - providerOpenAIAudioTranscription, - providerOpenAICompatibleAudioSpeech, - providerOpenAICompatibleAudioTranscription, -} from './cloud/openai-audio' -import { providerOpenAICompatible } from './cloud/openai-compatible' -import { providerOpenPaths } from './cloud/openpaths' -import { providerOpenRouterAI } from './cloud/openrouter-ai' -import { providerOpenRouterAudioSpeech } from './cloud/openrouter-audio-speech' -import { providerPerplexityAI } from './cloud/perplexity-ai' -import { providerTogetherAI } from './cloud/together-ai' -import { - providerAlibabaCloudModelStudio, - providerDeepgramTts, - providerMicrosoftSpeech, - providerVolcengineSpeech, -} from './cloud/unspeech' -import { providerVolcengineCodingPlan } from './cloud/volcengine-coding-plan' -import { providerXAI } from './cloud/xai' -import { providerZai } from './cloud/zai' -import { providerBrowserWebSpeechApi } from './local/browser-web-speech-api' -import { providerIndexTtsVllm } from './local/index-tts-vllm' -import { providerLmStudio } from './local/lm-studio' -import { providerOllama } from './local/ollama' -import { providerPlayer2Speech } from './local/player2-speech' -import { providerSpeechNoop } from './local/speech-noop' -import { providerAivisSpeech, providerVoicevox } from './local/voicevox' - -/** - * Erases configuration types at the registry boundary. - * - * A registry selects definitions by a runtime id. It cannot know the selected - * configuration type. Individual provider exports preserve their exact type. - */ -type ProviderDefinitionRegistration = Pick & { id: TId } - -type ErasedProviderDefinitions = { - [K in keyof TDefinitions]: TDefinitions[K] extends ProviderDefinitionRegistration - ? ProviderDefinition, TId> - : never -} - -function eraseProviderDefinitions( - ...definitions: TDefinitions -): ErasedProviderDefinitions { - return definitions as unknown as ErasedProviderDefinitions -} - -/** - * Definitions that can load without Vue, Pinia, persistence, or Electron. - * - * This list includes Browser-only definitions. Hosts must call - * `isAvailableBy` before they offer one to a user in the current runtime. - */ -export const portableProviderDefinitions = eraseProviderDefinitions( +import { providerAzureAIFoundry } from './cloud/azure-ai-foundry' +import { providerAzureOpenAI } from './cloud/azure-openai' +import { providerBytePlus } from './cloud/byteplus' +import { providerBytePlusCodingPlan } from './cloud/byteplus-coding-plan' +import { providerCerebrasAI } from './cloud/cerebras-ai' +import { providerCloudflareWorkersAI } from './cloud/cloudflare-workers-ai' +import { providerCometAPI, providerCometAPISpeech, providerCometAPITranscription } from './cloud/comet-api' +import { providerDeepSeek } from './cloud/deepseek' +import { providerElevenLabs } from './cloud/elevenlabs' +import { providerFeatherlessAI } from './cloud/featherless-ai' +import { providerFireworksAI } from './cloud/fireworks-ai' +import { providerGoogleGeminiAudioSpeech } from './cloud/google-gemini-audio-speech' +import { providerGoogleGenerativeAI } from './cloud/google-generative-ai' +import { providerGroq } from './cloud/groq' +import { providerMimo } from './cloud/mimo' +import { providerMimoAudioSpeech, providerMimoAudioTranscription } from './cloud/mimo-audio' +import { providerMinimax, providerMinimaxGlobal } from './cloud/minimax' +import { providerMinimaxSpeech } from './cloud/minimax-speech' +import { providerMistralAI } from './cloud/mistral-ai' +import { providerModelScope } from './cloud/modelscope' +import { providerMoonshotAI } from './cloud/moonshot-ai' +import { providerN1N } from './cloud/n1n' +import { providerNovitaAI } from './cloud/novita-ai' +import { providerOpenAI } from './cloud/openai' +import { + providerOpenAIAudioSpeech, + providerOpenAIAudioTranscription, + providerOpenAICompatibleAudioSpeech, + providerOpenAICompatibleAudioTranscription, +} from './cloud/openai-audio' +import { providerOpenAICompatible } from './cloud/openai-compatible' +import { providerOpenPaths } from './cloud/openpaths' +import { providerOpenRouterAI } from './cloud/openrouter-ai' +import { providerOpenRouterAudioSpeech } from './cloud/openrouter-audio-speech' +import { providerPerplexityAI } from './cloud/perplexity-ai' +import { providerTogetherAI } from './cloud/together-ai' +import { + providerAlibabaCloudModelStudio, + providerDeepgramTts, + providerMicrosoftSpeech, + providerVolcengineSpeech, +} from './cloud/unspeech' +import { providerVolcengineCodingPlan } from './cloud/volcengine-coding-plan' +import { providerXAI } from './cloud/xai' +import { providerZai } from './cloud/zai' +import { providerBrowserWebSpeechApi } from './local/browser-web-speech-api' +import { providerIndexTtsVllm } from './local/index-tts-vllm' +import { providerLmStudio } from './local/lm-studio' +import { providerOllama } from './local/ollama' +import { providerPlayer2Speech } from './local/player2-speech' +import { providerSpeechNoop } from './local/speech-noop' +import { providerAivisSpeech, providerVoicevox } from './local/voicevox' + +/** + * Erases configuration types at the registry boundary. + * + * A registry selects definitions by a runtime id. It cannot know the selected + * configuration type. Individual provider exports preserve their exact type. + */ +type ProviderDefinitionRegistration = Pick & { id: TId } + +type ErasedProviderDefinitions = { + [K in keyof TDefinitions]: TDefinitions[K] extends ProviderDefinitionRegistration + ? ProviderDefinition, TId> + : never +} + +function eraseProviderDefinitions( + ...definitions: TDefinitions +): ErasedProviderDefinitions { + return definitions as unknown as ErasedProviderDefinitions +} + +/** + * Definitions that can load without Vue, Pinia, persistence, or Electron. + * + * This list includes Browser-only definitions. Hosts must call + * `isAvailableBy` before they offer one to a user in the current runtime. + */ +export const portableProviderDefinitions = eraseProviderDefinitions( provider302AI, providerAIHubMix, providerAmazonBedrock, providerAnthropic, - providerApiRoute, providerAtlasCloud, - providerAzureAIFoundry, - providerAzureOpenAI, - providerBytePlus, - providerBytePlusCodingPlan, - providerCerebrasAI, - providerCloudflareWorkersAI, - providerCometAPI, - providerCometAPISpeech, - providerCometAPITranscription, - providerDeepSeek, - providerElevenLabs, - providerFeatherlessAI, - providerFireworksAI, - providerGoogleGeminiAudioSpeech, - providerGoogleGenerativeAI, - providerGroq, - providerMinimax, - providerMinimaxGlobal, - providerMinimaxSpeech, - providerMimo, - providerMimoAudioSpeech, - providerMimoAudioTranscription, - providerMistralAI, - providerModelScope, - providerMoonshotAI, - providerN1N, - providerNovitaAI, - providerOpenAI, - providerOpenAIAudioSpeech, - providerOpenAIAudioTranscription, - providerOpenAICompatibleAudioSpeech, - providerOpenAICompatibleAudioTranscription, - providerOpenAICompatible, - providerOpenPaths, - providerOpenRouterAI, - providerOpenRouterAudioSpeech, - providerPerplexityAI, - providerTogetherAI, - providerAlibabaCloudModelStudio, - providerDeepgramTts, - providerMicrosoftSpeech, - providerVolcengineSpeech, - providerVolcengineCodingPlan, - providerXAI, - providerZai, - providerBrowserWebSpeechApi, - providerIndexTtsVllm, - providerLmStudio, - providerOllama, - providerPlayer2Speech, - providerSpeechNoop, - providerAivisSpeech, - providerVoicevox, -) - -export { createProviderRegistry, defineProvider } from './registry' + providerAzureAIFoundry, + providerAzureOpenAI, + providerBytePlus, + providerBytePlusCodingPlan, + providerCerebrasAI, + providerCloudflareWorkersAI, + providerCometAPI, + providerCometAPISpeech, + providerCometAPITranscription, + providerDeepSeek, + providerElevenLabs, + providerFeatherlessAI, + providerFireworksAI, + providerGoogleGeminiAudioSpeech, + providerGoogleGenerativeAI, + providerGroq, + providerMinimax, + providerMinimaxGlobal, + providerMinimaxSpeech, + providerMimo, + providerMimoAudioSpeech, + providerMimoAudioTranscription, + providerMistralAI, + providerModelScope, + providerMoonshotAI, + providerN1N, + providerNovitaAI, + providerOpenAI, + providerOpenAIAudioSpeech, + providerOpenAIAudioTranscription, + providerOpenAICompatibleAudioSpeech, + providerOpenAICompatibleAudioTranscription, + providerOpenAICompatible, + providerOpenPaths, + providerOpenRouterAI, + providerOpenRouterAudioSpeech, + providerPerplexityAI, + providerTogetherAI, + providerAlibabaCloudModelStudio, + providerDeepgramTts, + providerMicrosoftSpeech, + providerVolcengineSpeech, + providerVolcengineCodingPlan, + providerXAI, + providerZai, + providerBrowserWebSpeechApi, + providerIndexTtsVllm, + providerLmStudio, + providerOllama, + providerPlayer2Speech, + providerSpeechNoop, + providerAivisSpeech, + providerVoicevox, +) + +export { createProviderRegistry, defineProvider } from './registry' diff --git a/packages/provider-inference/src/providers/registry.test.ts b/packages/provider-inference/src/providers/registry.test.ts index 83caf6727b..7f041605d1 100644 --- a/packages/provider-inference/src/providers/registry.test.ts +++ b/packages/provider-inference/src/providers/registry.test.ts @@ -1,50 +1,48 @@ -import type { PortableProviderId } from '../index' - -import { describe, expect, expectTypeOf, it } from 'vitest' - -import { createProviderRegistry, getDefinedProvider, listProviders, portableProviderDefinitions } from '../index' - -describe('portable provider registry', () => { +import type { PortableProviderId } from '../index' + +import { describe, expect, expectTypeOf, it } from 'vitest' + +import { createProviderRegistry, getDefinedProvider, listProviders, portableProviderDefinitions } from '../index' + +describe('portable provider registry', () => { it('exports the portable provider id union', () => { - expectTypeOf<'api-route'>().toExtend() expectTypeOf<'openai'>().toExtend() - expectTypeOf().not.toExtend() - - expect(getDefinedProvider('api-route')).toBeDefined() + expectTypeOf().not.toExtend() + expect(getDefinedProvider('openai')).toBeDefined() - }) - - it('registers each portable definition by id', () => { - for (const definition of portableProviderDefinitions) - expect(getDefinedProvider(definition.id)).toBe(definition) - }) - - it('lists definitions in deterministic display order', () => { - expect(listProviders()).toEqual(listProviders()) - }) - - it('places definitions without an order after all ordered definitions', () => { - const [template] = portableProviderDefinitions - const registry = createProviderRegistry([ - { ...template, id: 'unordered-z', name: 'Zeta', order: undefined }, - { ...template, id: 'high-order', name: 'High order', order: 100_000 }, - { ...template, id: 'unordered-a', name: 'Alpha', order: undefined }, - ]) - - expect(registry.list().map(provider => provider.id)).toEqual([ - 'high-order', - 'unordered-a', - 'unordered-z', - ]) - }) - - it('does not include providers that require application runtime adapters', () => { - const providerIds = new Set(listProviders().map(provider => provider.id)) - - expect(providerIds).not.toContain('official-provider') - expect(providerIds).not.toContain('apple-speech-transcription') - expect(providerIds).not.toContain('kokoro-local') - expect(providerIds).not.toContain('browser-local-audio-speech') - expect(providerIds).not.toContain('aliyun-nls-transcription') - }) -}) + }) + + it('registers each portable definition by id', () => { + for (const definition of portableProviderDefinitions) + expect(getDefinedProvider(definition.id)).toBe(definition) + }) + + it('lists definitions in deterministic display order', () => { + expect(listProviders()).toEqual(listProviders()) + }) + + it('places definitions without an order after all ordered definitions', () => { + const [template] = portableProviderDefinitions + const registry = createProviderRegistry([ + { ...template, id: 'unordered-z', name: 'Zeta', order: undefined }, + { ...template, id: 'high-order', name: 'High order', order: 100_000 }, + { ...template, id: 'unordered-a', name: 'Alpha', order: undefined }, + ]) + + expect(registry.list().map(provider => provider.id)).toEqual([ + 'high-order', + 'unordered-a', + 'unordered-z', + ]) + }) + + it('does not include providers that require application runtime adapters', () => { + const providerIds = new Set(listProviders().map(provider => provider.id)) + + expect(providerIds).not.toContain('official-provider') + expect(providerIds).not.toContain('apple-speech-transcription') + expect(providerIds).not.toContain('kokoro-local') + expect(providerIds).not.toContain('browser-local-audio-speech') + expect(providerIds).not.toContain('aliyun-nls-transcription') + }) +}) diff --git a/packages/stage-ui/src/libs/providers/attributes.ts b/packages/stage-ui/src/libs/providers/attributes.ts index 6e0470c6e6..c1e1daeb45 100644 --- a/packages/stage-ui/src/libs/providers/attributes.ts +++ b/packages/stage-ui/src/libs/providers/attributes.ts @@ -44,7 +44,6 @@ const providerAttributesById = { 'aliyun-nls-transcription': paidCloud, 'amazon-bedrock': paidCloud, 'anthropic': paidCloud, - 'api-route': paidCloud, 'app-local-audio-speech': freeLocal, 'app-local-audio-transcription': freeLocal, 'apple-vision': freeLocal, diff --git a/packages/stage-ui/src/libs/providers/metadata.test.ts b/packages/stage-ui/src/libs/providers/metadata.test.ts index 31d0dc0295..fff61a6543 100644 --- a/packages/stage-ui/src/libs/providers/metadata.test.ts +++ b/packages/stage-ui/src/libs/providers/metadata.test.ts @@ -72,15 +72,4 @@ describe('provider metadata selector', () => { expect(definition.id).toBe('test-provider') expect(definition.tasks).toEqual(['chat']) }) - - // https://github.com/moeru-ai/airi/pull/2526#discussion_r3994122656 - it('keeps API Route in the paid and cloud catalogue filters', async () => { - const metadata = await selectProviderMetadata({ ...definition, id: 'api-route' }, t) - - const paidProviders = [metadata].filter(provider => provider.pricing === 'paid') - const cloudProviders = [metadata].filter(provider => provider.deployment === 'cloud') - - expect(paidProviders).toEqual([metadata]) - expect(cloudProviders).toEqual([metadata]) - }) })