refactor: extract provider logic into modular registry
Each AI provider now has its own module implementing a ProviderModule interface with createGateway, getAuthHeaders, and fetchModels methods. This replaces the monolithic switch statements in ai-provider.ts and models.post.ts with a clean registry pattern.
This commit is contained in:
+32
-159
@@ -1,26 +1,9 @@
|
||||
import { createCerebras, type CerebrasProvider } from "@ai-sdk/cerebras";
|
||||
import { createGoogleGenerativeAI, type GoogleGenerativeAIProvider } from "@ai-sdk/google";
|
||||
import { createOpenRouter, type OpenRouterProvider } from "@openrouter/ai-sdk-provider";
|
||||
import { createOllama, type OllamaProvider } from "ai-sdk-ollama";
|
||||
import { createOpenAICompatible, type OpenAICompatibleProvider } from '@ai-sdk/openai-compatible';
|
||||
import { createCohere, type CohereProvider } from '@ai-sdk/cohere';
|
||||
import { createMistral, type MistralProvider } from '@ai-sdk/mistral';
|
||||
import { createLongcat, type LongcatProvider } from 'longcat-ai-sdk-provider';
|
||||
import { type StreamTextTransform } from "ai";
|
||||
import { providerBaseUrls, Providers } from "~/types/model";
|
||||
import type { Provider as ProviderDrizzle, Model as ModelDrizzle } from '~/composables/useModels';
|
||||
import { type Result, Err, Ok } from "~~/types/result";
|
||||
// import { createLongcatTransformer } from "./longcat";
|
||||
import { getProvider, getProviderBaseUrl } from './providers';
|
||||
|
||||
export type ModelGateway =
|
||||
OpenRouterProvider
|
||||
| OllamaProvider
|
||||
| CerebrasProvider
|
||||
| GoogleGenerativeAIProvider
|
||||
| OpenAICompatibleProvider
|
||||
| CohereProvider
|
||||
| MistralProvider
|
||||
| LongcatProvider;
|
||||
export type ModelGateway = any;
|
||||
|
||||
export interface Gateway {
|
||||
gateway: ModelGateway;
|
||||
@@ -43,162 +26,52 @@ export enum GatewayFetchError {
|
||||
}
|
||||
|
||||
export async function getProviderDetails(provider: ProviderDrizzle, providerApiKey?: string, model?: ModelDrizzle): Promise<Result<Provider, GatewayFetchError>> {
|
||||
let gateway: Gateway = {} as Gateway;
|
||||
const providerModule = getProvider(provider.type);
|
||||
|
||||
if (!providerModule) {
|
||||
return Err(GatewayFetchError.NoProviderBaseUrl);
|
||||
}
|
||||
|
||||
let baseURL = undefined;
|
||||
let modelsEndpoint = undefined;
|
||||
let headers: Record<string, string> = {};
|
||||
|
||||
if (provider.config.apiProxyUrl && provider.config.apiProxyUrl.trim() !== '') {
|
||||
baseURL = provider.config.apiProxyUrl;
|
||||
} else {
|
||||
baseURL = providerBaseUrls[provider.type as typeof Providers[number]];
|
||||
baseURL = providerModule.baseUrl;
|
||||
}
|
||||
baseURL = baseURL.replace(/\/$/, '');
|
||||
|
||||
switch (provider.type as typeof Providers[number]) {
|
||||
case 'openrouter': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
const gatewayResult = providerModule.createGateway({
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
model,
|
||||
});
|
||||
|
||||
gateway.gateway = createOpenRouter({
|
||||
apiKey: providerApiKey,
|
||||
headers: {
|
||||
'HTTP-Referer': 'https://localhost:3000',
|
||||
'X-Title': 'Veridian',
|
||||
},
|
||||
});
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = `/models`;
|
||||
} break;
|
||||
case 'closedrouter': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
if (!gatewayResult.ok) {
|
||||
return gatewayResult;
|
||||
}
|
||||
|
||||
gateway.gateway = createOpenAICompatible({
|
||||
name: 'ClosedRouter',
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
includeUsage: true,
|
||||
});
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = `/models`;
|
||||
} break;
|
||||
case 'ollama': {
|
||||
if (baseURL === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderBaseUrl);
|
||||
}
|
||||
|
||||
if (providerApiKey !== undefined) {
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
}
|
||||
modelsEndpoint = `/api/tags`;
|
||||
|
||||
if (model !== undefined) {
|
||||
const innerGateway = createOllama({
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
})
|
||||
|
||||
gateway.gateway = ((modelId: string) => innerGateway(modelId, { think: model.capabilities.includes('reasoning') })) as OllamaProvider;
|
||||
}
|
||||
} break;
|
||||
case 'vllm': {
|
||||
gateway.gateway = createOpenAICompatible({
|
||||
name: 'vLLM',
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
includeUsage: true,
|
||||
});
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = '/models';
|
||||
} break;
|
||||
case 'cerebras': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
|
||||
gateway.gateway = createCerebras({
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
})
|
||||
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = `/models`;
|
||||
} break;
|
||||
case 'google': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
|
||||
gateway.gateway = createGoogleGenerativeAI({
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
});
|
||||
headers['x-goog-api-key'] = `${providerApiKey}`
|
||||
modelsEndpoint = `/models`;
|
||||
} break;
|
||||
case 'longcat': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
|
||||
gateway.gateway = createLongcat({
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
});
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = null;
|
||||
// streamTransformer = createLongcatTransformer() as StreamTextTransform<{}>;
|
||||
} break;
|
||||
case 'cohere': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
|
||||
gateway.gateway = createCohere({
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
});
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = `/models`;
|
||||
} break;
|
||||
case 'inception': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
|
||||
gateway.gateway = createOpenAICompatible({
|
||||
name: 'Inception',
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
includeUsage: true,
|
||||
});
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = null;
|
||||
}
|
||||
case 'mistral': {
|
||||
if (providerApiKey === undefined) {
|
||||
return Err(GatewayFetchError.NoProviderApiKey);
|
||||
}
|
||||
|
||||
gateway.gateway = createMistral({
|
||||
apiKey: providerApiKey,
|
||||
baseURL,
|
||||
});
|
||||
headers['Authorization'] = `Bearer ${providerApiKey}`
|
||||
modelsEndpoint = '/models';
|
||||
const rawHeaders = providerModule.getAuthHeaders(providerApiKey);
|
||||
const headers: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(rawHeaders)) {
|
||||
if (value !== undefined) {
|
||||
headers[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok({
|
||||
gateway,
|
||||
gateway: gatewayResult.data,
|
||||
endpoint: {
|
||||
baseURL,
|
||||
modelsEndpoint,
|
||||
modelsEndpoint: providerModule.modelsEndpoint,
|
||||
headers,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function isRerankingProvider(gateway: any): gateway is { reranking: (modelId: string) => any } {
|
||||
return (
|
||||
gateway !== null &&
|
||||
'reranking' in gateway &&
|
||||
typeof (gateway as any).reranking === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user