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:
Zoe
2026-04-27 12:04:49 -05:00
parent 6ca4de4cab
commit 1d40812f2a
21 changed files with 1221 additions and 798 deletions
+50
View File
@@ -0,0 +1,50 @@
import { createAnthropic } from '@ai-sdk/anthropic';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData } from './helpers';
export default {
id: 'anthropic',
baseUrl: 'https://api.anthropic.com/v1',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createAnthropic({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey
? { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }
: {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: {
'x-api-key': `${apiKey}`,
'anthropic-version': '2023-06-01',
},
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.data.map((model: any) => ({
...getModelData(model.id, 'anthropic', modelsDevData),
id: model.id,
name: model.display_name ?? model.id,
}));
},
} satisfies ProviderModule;
+44
View File
@@ -0,0 +1,44 @@
import { createCerebras } from '@ai-sdk/cerebras';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData } from './helpers';
export default {
id: 'cerebras',
baseUrl: 'https://api.cerebras.ai/v1',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createCerebras({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.data.map((model: any) => ({
...getModelData(model.id, 'cerebras', modelsDevData),
id: model.id,
}));
},
} satisfies ProviderModule;
+63
View File
@@ -0,0 +1,63 @@
import { createOpenAI } from '@ai-sdk/openai';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
export default {
id: 'closedrouter',
baseUrl: 'https://router.queef.in/v1',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createOpenAI({ name: 'ClosedRouter', apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.data.map((model: any) => {
const capabilities = new Array<string>();
if (model.capabilities) {
if (model.capabilities.reasoning) {
capabilities.push('reasoning');
}
if (model.capabilities.tool_call) {
capabilities.push('tools');
}
}
return {
id: model.id,
name: model.name,
attributes: {
inputModalities: model.modalities?.input || ['text'],
outputModalities: model.modalities?.output || ['text'],
capabilities: capabilities,
contextWindow: model.context_window,
supported_parameters: [],
},
};
});
},
} satisfies ProviderModule;
+80
View File
@@ -0,0 +1,80 @@
import { createCohere } from '@ai-sdk/cohere';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData, mergeSets } from './helpers';
export default {
id: 'cohere',
baseUrl: 'https://api.cohere.ai/v2',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createCohere({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
const models = [];
for (const model of data.models) {
const inputModalities = new Set<string>();
const capabilities = new Set<string>();
for (const feature of model.features || []) {
switch (feature) {
case 'tools':
capabilities.add('tools');
break;
case 'vision':
inputModalities.add('image');
break;
case 'reasoning':
capabilities.add('reasoning');
break;
}
}
const modelData = getModelData(model.name, 'cohere', modelsDevData);
models.push({
...modelData,
id: model.name,
attributes: {
...modelData.attributes,
inputModalities: mergeSets(
inputModalities,
new Set(modelData.attributes?.inputModalities || [])
),
capabilities: mergeSets(
capabilities,
new Set(modelData.attributes?.capabilities || [])
),
contextWindow: model.context_length || modelData.attributes?.contextWindow,
}
});
}
return models;
},
} satisfies ProviderModule;
+45
View File
@@ -0,0 +1,45 @@
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData } from './helpers';
export default {
id: 'google',
baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createGoogleGenerativeAI({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { 'x-goog-api-key': apiKey } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { 'x-goog-api-key': `${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.models.map((model: any) => ({
...getModelData(model.name.replace('models/', ''), 'google', modelsDevData),
id: model.name.replace('models/', ''),
name: model.displayName,
}));
},
} satisfies ProviderModule;
+123
View File
@@ -0,0 +1,123 @@
import { SupportedModalities } from '~/types/model';
export function mergeSets(setA: Set<string>, setB: Set<string>): string[] {
return Array.from(new Set([...setA, ...setB]));
}
export const getModelData = (modelId: string, providerId: string, modelsDevData: any) => {
const modelData = modelsDevData[providerId]?.models[modelId];
if (modelData === undefined) return {
cost: {},
attributes: {
inputModalities: ['text'],
outputModalities: ['text'],
capabilities: [],
}
};
const capabilities = new Array<string>();
if (modelData.reasoning) {
capabilities.push('reasoning');
}
if (modelData.tool_call) {
capabilities.push('tools');
}
let inputModalities = modelData.modalities.input.filter((m: string) => (SupportedModalities as Readonly<string[]>).includes(m));
let outputModalities = modelData.modalities.output.filter((m: string) => (SupportedModalities as Readonly<string[]>).includes(m));
let supportedParameters = [];
if (modelData.temperature) {
supportedParameters.push('temperature');
}
let contextWindow = modelData.limit?.context || null;
let cost: Record<string, string> = {};
for (const key in modelData.cost) {
switch (key) {
case 'input': {
cost.prompt = formatMoney(modelData.cost[key].toString());
} break;
case 'output': {
cost.completion = formatMoney(modelData.cost[key].toString());
} break;
}
}
let releasedAt = (new Date(modelData.release_date)).getTime();
return {
name: modelData.name,
attributes: {
inputModalities,
outputModalities,
capabilities: capabilities || [],
contextWindow,
supported_parameters: modelData.supportedParameters,
},
cost,
releasedAt,
}
}
export const lshDecimal = (number: string, shift: number) => {
if (number[0] === '-') {
return undefined;
}
if (number.length === 0) {
return '0.00';
}
let value = '';
let [integerPart, fractionalPart] = number.split('.');
if (!fractionalPart) {
fractionalPart = '';
}
if (shift <= fractionalPart.length) {
integerPart += fractionalPart.substring(0, shift);
fractionalPart = fractionalPart.substring(shift);
} else if (shift > fractionalPart.length) {
integerPart += fractionalPart;
for (let i = 0; i < shift - fractionalPart.length; i++) {
integerPart += '0';
}
fractionalPart = '';
}
integerPart = integerPart!.replace(/^0+/, '');
if (integerPart.length === 0) {
integerPart = '0';
}
value += integerPart;
if (fractionalPart.length > 0) {
if (fractionalPart.length < 2) {
fractionalPart += '0';
}
value += '.' + fractionalPart;
} else {
value += '.00';
}
return value;
}
export const formatMoney = (value: string) => {
const [integerPart, fractionalPart] = value.split('.');
if (fractionalPart === undefined) {
return `${integerPart}.00`;
}
if (fractionalPart.length === 1) {
return `${integerPart}.${fractionalPart}0`;
}
return `${integerPart}.${fractionalPart}`;
}
+42
View File
@@ -0,0 +1,42 @@
import { createOpenAI } from '@ai-sdk/openai';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
export default {
id: 'inception',
baseUrl: 'https://api.inceptionlabs.ai/v1',
modelsEndpoint: null,
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createOpenAI({ name: 'Inception', apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels() {
// inception doesn't have a model list endpoint so we hardcode them
return [
{
id: "mercury-2",
name: "Mercury 2",
attributes: {
inputModalities: ['text'],
outputModalities: ['text'],
capabilities: ['tools', 'reasoning'],
contextWindow: 128_000,
}
},
];
},
} satisfies ProviderModule;
+89
View File
@@ -0,0 +1,89 @@
import openrouter from './openrouter';
import ollama from './ollama';
import vllm from './vllm';
import cerebras from './cerebras';
import google from './google';
import longcat from './longcat';
import cohere from './cohere';
import inception from './inception';
import mistral from './mistral';
import closedrouter from './closedrouter';
import nvidia from './nvidia';
import xiaomi from './xiaomi';
import openai from './openai';
import anthropic from './anthropic';
import type { Result } from '~~/types/result';
import type { Gateway, GatewayFetchError } from '~~/server/utils/ai-provider';
import type { Model as ModelDrizzle } from '~/composables/useModels';
export interface NormalizedModel {
id: string;
name?: string;
cost?: Record<string, string | undefined>;
attributes: {
inputModalities: string[];
outputModalities: string[];
capabilities: string[];
contextWindow?: number | null;
supported_parameters?: string[];
};
releasedAt?: number;
}
export interface ProviderModule {
id: string;
baseUrl: string;
modelsEndpoint: string | null;
createGateway(config: {
apiKey?: string;
baseURL: string;
model?: ModelDrizzle;
}): Result<Gateway, GatewayFetchError>;
getAuthHeaders(apiKey?: string): Record<string, string | undefined>;
fetchModels(config: {
baseURL: string;
apiKey?: string;
modelsDevData: any;
}): Promise<NormalizedModel[]>;
}
const providers = [
openrouter,
ollama,
vllm,
cerebras,
google,
longcat,
cohere,
inception,
mistral,
closedrouter,
nvidia,
xiaomi,
openai,
anthropic,
];
type ProviderId = typeof providers[number]['id'];
type Provider = typeof providers[number];
const registry = new Map<ProviderId, Provider>(providers.map((p) => [p.id, p]));
export function getProvider<T extends ProviderId>(id: T): Provider | undefined {
return registry.get(id);
}
export function getAllProviders() {
return Array.from(registry.values());
}
export function getProviderIds() {
return Array.from(registry.keys());
}
export function getProviderBaseUrl<T extends ProviderId>(id: T): string {
return registry.get(id)?.baseUrl ?? '';
}
+72
View File
@@ -0,0 +1,72 @@
import { createLongcat } from 'longcat-ai-sdk-provider';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
export default {
id: 'longcat',
baseUrl: 'https://api.longcat.chat/openai/v1',
modelsEndpoint: null,
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createLongcat({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels() {
// longcat doesn't have a model list endpoint so we hardcode them
return [
{
id: "LongCat-Flash-Chat",
name: "LongCat Flash Chat",
attributes: {
inputModalities: ['text'],
outputModalities: ['text'],
capabilities: ['tools'],
contextWindow: 256_000,
}
},
{
id: "LongCat-Flash-Thinking-2601",
name: "LongCat Flash Thinking (2601)",
attributes: {
inputModalities: ['text'],
outputModalities: ['text'],
capabilities: ['reasoning', 'tools'],
contextWindow: 256_000,
}
},
{
id: "LongCat-Flash-Omni-2603",
name: "LongCat Flash Omni",
attributes: {
inputModalities: ['text', 'image', 'audio', 'video'],
outputModalities: ['text', 'audio'],
capabilities: ['tools'],
contextWindow: 256_000,
}
},
{
id: "LongCat-Flash-Lite",
name: "LongCat Flash Lite",
attributes: {
inputModalities: ['text'],
outputModalities: ['text'],
capabilities: ['tools'],
contextWindow: 320_000,
}
}
];
},
} satisfies ProviderModule;
+84
View File
@@ -0,0 +1,84 @@
import { createMistral } from '@ai-sdk/mistral';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData, mergeSets } from './helpers';
export default {
id: 'mistral',
baseUrl: 'https://api.mistral.ai/v1',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createMistral({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
const models = [];
for (const model of data.data) {
const inputModalities = new Set<string>();
const capabilities = new Set<string>();
for (const capability of Object.keys(model.capabilities)) {
if (model.capabilities[capability] === false) continue;
switch (capability) {
case 'function_calling':
capabilities.add('tools');
break;
case 'completion_chat':
inputModalities.add('text');
capabilities.add('completion');
break;
case 'vision':
inputModalities.add('image');
break;
}
}
const modelData = getModelData(model.id, 'mistral', modelsDevData);
models.push({
...modelData,
id: model.id,
name: modelData.name || model.name || model.id,
attributes: {
...modelData.attributes,
inputModalities: mergeSets(
inputModalities,
new Set(modelData.attributes?.inputModalities || [])
),
capabilities: mergeSets(
capabilities,
new Set(modelData.attributes?.capabilities || [])
),
contextWindow: model.max_context_length || modelData.attributes?.contextWindow,
},
});
}
return models;
},
} satisfies ProviderModule;
+44
View File
@@ -0,0 +1,44 @@
import { createOpenAI } from '@ai-sdk/openai';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData } from './helpers';
export default {
id: 'nvidia',
baseUrl: 'https://integrate.api.nvidia.com/v1',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createOpenAI({ name: 'Nvidia', apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.data.map((model: any) => ({
...getModelData(model.id, 'nvidia', modelsDevData),
id: model.id,
}));
},
} satisfies ProviderModule;
+131
View File
@@ -0,0 +1,131 @@
import { createOllama } from 'ai-sdk-ollama';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData, mergeSets } from './helpers';
export default {
id: 'ollama',
baseUrl: '',
modelsEndpoint: '/api/tags',
createGateway({ apiKey, baseURL, model }) {
if (baseURL === undefined) {
return Err(GatewayFetchError.NoProviderBaseUrl);
}
if (model !== undefined) {
const innerGateway = createOllama({
apiKey,
baseURL,
});
return Ok({
gateway: ((modelId: string) => innerGateway(modelId, { think: model.capabilities.includes('reasoning') })) as any,
streamTransformer: undefined,
textTransformer: undefined,
});
}
return Ok({
gateway: createOllama({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0'
};
if (apiKey) {
headers['Authorization'] = `Bearer ${apiKey}`;
}
const res = await fetch(`${baseURL}/api/tags`, {
method: 'GET',
headers,
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
const models = new Map<string, any>();
const infoPromises = [];
for (const model of data.models) {
infoPromises.push(fetch(`${baseURL}/api/show`, {
method: 'POST',
headers,
body: JSON.stringify({ model: model.name }),
}).then((res) => res.json()).then((json) => {
const inputModalities = new Set<string>();
const capabilities = new Set<string>();
for (const capability of json.capabilities) {
switch (capability) {
case 'thinking':
capabilities.add('reasoning');
break;
case 'tools':
capabilities.add('tools');
break;
case 'completion':
inputModalities.add('text');
break;
case 'vision':
inputModalities.add('image');
break;
}
}
let contextWindow: number | undefined;
try {
for (const key of Object.keys(json.model_info)) {
if (key.endsWith('context_length')) {
contextWindow = json.model_info[key];
break;
}
}
} catch (e) {
console.error(e);
}
let normalizedId = model.name;
normalizedId = normalizedId.replace(/:cloud$/, '');
normalizedId = normalizedId.replace(/-cloud$/, '');
normalizedId = normalizedId.replace(/:latest$/, '');
const modelData = getModelData(normalizedId, 'ollama-cloud', modelsDevData);
models.set(model.name, {
...modelData,
id: model.name,
name: model.name,
attributes: {
...modelData.attributes,
inputModalities: mergeSets(
inputModalities,
new Set(modelData.attributes?.inputModalities || [])
),
capabilities: mergeSets(
capabilities,
new Set(modelData.attributes?.capabilities || [])
),
contextWindow,
}
});
}));
}
await Promise.all(infoPromises);
return Array.from(models.values());
},
} satisfies ProviderModule;
+47
View File
@@ -0,0 +1,47 @@
import { createOpenAI } from '@ai-sdk/openai';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData } from './helpers';
export default {
id: 'openai',
baseUrl: 'https://api.openai.com/v1',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createOpenAI({ apiKey, baseURL }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.data
.filter((model: any) => model.object === 'model')
.map((model: any) => ({
...getModelData(model.id, 'openai', modelsDevData),
id: model.id,
name: model.id,
}));
},
} satisfies ProviderModule;
+119
View File
@@ -0,0 +1,119 @@
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import { SupportedModalities } from '~/types/model';
import type { ProviderModule } from '.';
import { lshDecimal } from './helpers';
export default {
id: 'openrouter',
baseUrl: 'https://openrouter.ai/api/v1',
modelsEndpoint: '/models',
createGateway({ apiKey }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createOpenRouter({
apiKey,
headers: {
'HTTP-Referer': 'https://localhost:3000',
'X-Title': 'Veridian',
},
}),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
const models = [];
for (const model of data.data) {
const capabilities = new Set<string>();
for (const capability of model.supported_parameters) {
switch (capability) {
case 'reasoning':
capabilities.add('reasoning');
break;
case 'tools':
capabilities.add('tools');
break;
}
}
const pricing = {} as Record<string, string | undefined>;
for (const key of Object.keys(model.pricing)) {
switch (key) {
case 'prompt':
case 'completion':
case 'image':
case 'audio':
case 'discount':
pricing[key] = lshDecimal(model.pricing[key], 6);
break;
case 'request':
pricing['request'] = lshDecimal(model.pricing[key], 3);
break;
case 'image_tokens':
pricing['imageTokens'] = lshDecimal(model.pricing[key], 6);
break;
case 'image_output':
pricing['imageOutput'] = lshDecimal(model.pricing[key], 6);
break;
case 'audio_output':
pricing['audioOutput'] = lshDecimal(model.pricing[key], 6);
break;
case 'input_audio_cache':
pricing['inputAudioCache'] = lshDecimal(model.pricing[key], 6);
break;
case 'web_search':
pricing['webSearch'] = lshDecimal(model.pricing[key], 3);
break;
case 'internal_reasoning':
pricing['internalReasoning'] = lshDecimal(model.pricing[key], 6);
break;
case 'input_cache_read':
pricing['inputCacheRead'] = lshDecimal(model.pricing[key], 6);
break;
case 'input_cache_write':
pricing['inputCacheWrite'] = lshDecimal(model.pricing[key], 6);
break;
}
}
models.push({
id: model.id as string,
name: model.name as string,
cost: pricing,
attributes: {
inputModalities: model.architecture.input_modalities.filter((modality: string) => (SupportedModalities as Readonly<string[]>).includes(modality)),
outputModalities: model.architecture.output_modalities.filter((modality: string) => (SupportedModalities as Readonly<string[]>).includes(modality)),
capabilities: Array.from(capabilities),
contextWindow: model.context_length,
supported_parameters: model.supported_parameters,
},
releasedAt: model.created * 1000,
});
}
return models;
},
} satisfies ProviderModule;
+42
View File
@@ -0,0 +1,42 @@
import { Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
export default {
id: 'vllm',
baseUrl: '',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
return Ok({
gateway: createOpenAICompatible({ name: 'vLLM', apiKey, baseURL, includeUsage: true }),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.data.map((model: any) => ({
id: model.id,
attributes: {
inputModalities: ['text'],
outputModalities: ['text'],
contextWindow: model.max_model_len,
}
}));
},
} satisfies ProviderModule;
+50
View File
@@ -0,0 +1,50 @@
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
import { GatewayFetchError } from '~~/server/utils/ai-provider';
import { Err, Ok } from '~~/types/result';
import type { ProviderModule } from '.';
import { getModelData } from './helpers';
export default {
id: 'xiaomi',
baseUrl: 'https://api.xiaomimimo.com/v1',
modelsEndpoint: '/models',
createGateway({ apiKey, baseURL }) {
if (apiKey === undefined) {
return Err(GatewayFetchError.NoProviderApiKey);
}
return Ok({
gateway: createOpenAICompatible({
name: 'Xiaomi',
apiKey,
baseURL,
includeUsage: true,
}),
streamTransformer: undefined,
textTransformer: undefined,
});
},
getAuthHeaders(apiKey) {
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
},
async fetchModels({ baseURL, apiKey, modelsDevData }) {
const res = await fetch(`${baseURL}/models`, {
method: 'GET',
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await res.json();
if (!res.ok) {
throw createError({ statusCode: res.status, message: JSON.stringify(data) });
}
return data.data.map((model: any) => ({
...getModelData(model.id, 'xiaomi', modelsDevData),
id: model.id,
}));
},
} satisfies ProviderModule;