import { BaseProvider, getOpenAILikeModel } from '~/lib/modules/llm/base-provider'; import type { ModelInfo } from '~/lib/modules/llm/types'; import type { IProviderSetting } from '~/types/model'; import type { LanguageModelV1 } from 'ai'; import { logger } from '~/utils/logger'; interface OpenAIModelsResponse { data: Array<{ id: string }>; } export default class OpenAILikeProvider extends BaseProvider { name = 'OpenAILike'; getApiKeyLink = undefined; config = { baseUrlKey: 'OPENAI_LIKE_API_BASE_URL', apiTokenKey: 'OPENAI_LIKE_API_KEY', modelsKey: 'OPENAI_LIKE_API_MODELS', }; staticModels: ModelInfo[] = []; async getDynamicModels( apiKeys?: Record, settings?: IProviderSetting, serverEnv: Record = {}, ): Promise { const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({ apiKeys, providerSettings: settings, serverEnv, defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL', defaultApiTokenKey: 'OPENAI_LIKE_API_KEY', }); if (!baseUrl || !apiKey) { return []; } try { const response = await fetch(`${baseUrl}/models`, { headers: { Authorization: `Bearer ${apiKey}`, }, signal: this.createTimeoutSignal(), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const res = (await response.json()) as OpenAIModelsResponse; return res.data.map((model) => ({ name: model.id, label: model.id, provider: this.name, maxTokenAllowed: 8000, })); } catch (error) { logger.info(`${this.name}: Could not fetch /models endpoint, checking fallback env`, error); // Fallback to OPENAI_LIKE_API_MODELS if available // eslint-disable-next-line dot-notation const modelsEnv = serverEnv['OPENAI_LIKE_API_MODELS'] || settings?.OPENAI_LIKE_API_MODELS; if (modelsEnv) { logger.info(`${this.name}: Using OPENAI_LIKE_API_MODELS fallback`); return this._parseModelsFromEnv(modelsEnv); } return []; } } /** * Parse OPENAI_LIKE_API_MODELS environment variable * Format: path/to/model1:limit;path/to/model2:limit;path/to/model3:limit */ private _parseModelsFromEnv(modelsEnv: string): ModelInfo[] { if (!modelsEnv) { return []; } try { const models: ModelInfo[] = []; const modelEntries = modelsEnv.split(';'); for (const entry of modelEntries) { const trimmedEntry = entry.trim(); if (!trimmedEntry) { continue; } const [modelPath, limitStr] = trimmedEntry.split(':'); if (!modelPath) { continue; } const limit = limitStr ? parseInt(limitStr.trim(), 10) : 8000; const modelName = modelPath.trim(); // Generate a readable label from the model path const label = this._generateModelLabel(modelName); models.push({ name: modelName, label, provider: this.name, maxTokenAllowed: limit, }); } logger.info(`${this.name}: Parsed ${models.length} models from env`); return models; } catch (error) { logger.error(`${this.name}: Error parsing OPENAI_LIKE_API_MODELS:`, error); return []; } } /** * Generate a readable label from model path */ private _generateModelLabel(modelPath: string): string { // Extract the last part of the path and clean it up const parts = modelPath.split('/'); const lastPart = parts[parts.length - 1]; // Remove common prefixes and clean up the name let label = lastPart .replace(/^accounts\//, '') .replace(/^fireworks\/models\//, '') .replace(/^models\//, '') // Capitalize first letter of each word .replace(/\b\w/g, (l) => l.toUpperCase()) // Replace spaces with hyphens for a cleaner look .replace(/\s+/g, '-'); // Add provider suffix if not already present if (!label.includes('Fireworks') && !label.includes('OpenAI')) { label += ' (OpenAI Compatible)'; } return label; } getModelInstance(options: { model: string; serverEnv: Env; apiKeys?: Record; providerSettings?: Record; }): LanguageModelV1 { const { model, serverEnv, apiKeys, providerSettings } = options; const envRecord = this.convertEnvToRecord(serverEnv); const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({ apiKeys, providerSettings: providerSettings?.[this.name], serverEnv: envRecord, defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL', defaultApiTokenKey: 'OPENAI_LIKE_API_KEY', }); if (!baseUrl || !apiKey) { throw new Error(`Missing configuration for ${this.name} provider`); } return getOpenAILikeModel(baseUrl, apiKey, model); } }