llmProviders.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import { StorageEnum } from '../base/enums';
  2. import { createStorage } from '../base/base';
  3. import type { BaseStorage } from '../base/types';
  4. import { type AgentNameEnum, llmProviderModelNames, llmProviderParameters, ProviderTypeEnum } from './types';
  5. // Interface for a single provider configuration
  6. export interface ProviderConfig {
  7. name?: string; // Display name in the options
  8. type?: ProviderTypeEnum; // Help to decide which LangChain ChatModel package to use
  9. apiKey: string; // Must be provided, but may be empty for local models
  10. baseUrl?: string; // Optional base URL if provided // For Azure: Endpoint
  11. modelNames?: string[]; // Chosen model names (NOT used for Azure OpenAI)
  12. createdAt?: number; // Timestamp in milliseconds when the provider was created
  13. // Azure Specific Fields:
  14. azureDeploymentNames?: string[]; // Azure deployment names array
  15. azureApiVersion?: string;
  16. }
  17. // Interface for storing multiple LLM provider configurations
  18. // The key is the provider id, which is the same as the provider type for built-in providers, but is custom for custom providers
  19. export interface LLMKeyRecord {
  20. providers: Record<string, ProviderConfig>;
  21. }
  22. export type LLMProviderStorage = BaseStorage<LLMKeyRecord> & {
  23. setProvider: (providerId: string, config: ProviderConfig) => Promise<void>;
  24. getProvider: (providerId: string) => Promise<ProviderConfig | undefined>;
  25. removeProvider: (providerId: string) => Promise<void>;
  26. hasProvider: (providerId: string) => Promise<boolean>;
  27. getAllProviders: () => Promise<Record<string, ProviderConfig>>;
  28. };
  29. // Storage for LLM provider configurations
  30. // use "llm-api-keys" as the key for the storage, for backward compatibility
  31. const storage = createStorage<LLMKeyRecord>(
  32. 'llm-api-keys',
  33. { providers: {} },
  34. {
  35. storageEnum: StorageEnum.Local,
  36. liveUpdate: true,
  37. },
  38. );
  39. // Helper function to determine provider type from provider name
  40. // Make sure to update this function if you add a new provider type
  41. export function getProviderTypeByProviderId(providerId: string): ProviderTypeEnum {
  42. switch (providerId) {
  43. case ProviderTypeEnum.OpenAI:
  44. case ProviderTypeEnum.Anthropic:
  45. case ProviderTypeEnum.DeepSeek:
  46. case ProviderTypeEnum.Gemini:
  47. case ProviderTypeEnum.Grok:
  48. case ProviderTypeEnum.Ollama:
  49. case ProviderTypeEnum.AzureOpenAI:
  50. case ProviderTypeEnum.OpenRouter:
  51. return providerId;
  52. default:
  53. return ProviderTypeEnum.CustomOpenAI;
  54. }
  55. }
  56. // Helper function to get display name from provider id
  57. // Make sure to update this function if you add a new provider type
  58. export function getDefaultDisplayNameFromProviderId(providerId: string): string {
  59. switch (providerId) {
  60. case ProviderTypeEnum.OpenAI:
  61. return 'OpenAI';
  62. case ProviderTypeEnum.Anthropic:
  63. return 'Anthropic';
  64. case ProviderTypeEnum.DeepSeek:
  65. return 'DeepSeek';
  66. case ProviderTypeEnum.Gemini:
  67. return 'Gemini';
  68. case ProviderTypeEnum.Grok:
  69. return 'Grok';
  70. case ProviderTypeEnum.Ollama:
  71. return 'Ollama';
  72. case ProviderTypeEnum.AzureOpenAI:
  73. return 'Azure OpenAI';
  74. case ProviderTypeEnum.OpenRouter:
  75. return 'OpenRouter';
  76. default:
  77. return providerId; // Use the provider id as display name for custom providers by default
  78. }
  79. }
  80. // Get default configuration for built-in providers
  81. export function getDefaultProviderConfig(providerId: string): ProviderConfig {
  82. switch (providerId) {
  83. case ProviderTypeEnum.OpenAI:
  84. case ProviderTypeEnum.Anthropic:
  85. case ProviderTypeEnum.DeepSeek:
  86. case ProviderTypeEnum.Gemini:
  87. case ProviderTypeEnum.Grok:
  88. case ProviderTypeEnum.OpenRouter: // OpenRouter uses modelNames
  89. return {
  90. apiKey: '',
  91. name: getDefaultDisplayNameFromProviderId(providerId),
  92. type: providerId,
  93. baseUrl: providerId === ProviderTypeEnum.OpenRouter ? 'https://openrouter.ai/api/v1' : undefined,
  94. modelNames: [...(llmProviderModelNames[providerId] || [])],
  95. createdAt: Date.now(),
  96. };
  97. case ProviderTypeEnum.Ollama:
  98. return {
  99. apiKey: 'ollama', // Set default API key for Ollama
  100. name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.Ollama),
  101. type: ProviderTypeEnum.Ollama,
  102. modelNames: [], // Ollama uses modelNames (user adds them)
  103. baseUrl: 'http://localhost:11434',
  104. createdAt: Date.now(),
  105. };
  106. case ProviderTypeEnum.AzureOpenAI:
  107. return {
  108. apiKey: '', // User needs to provide API Key
  109. name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.AzureOpenAI),
  110. type: ProviderTypeEnum.AzureOpenAI,
  111. baseUrl: '', // User needs to provide Azure endpoint
  112. // modelNames: [], // Not used for Azure configuration
  113. azureDeploymentNames: [], // Azure deployment names
  114. azureApiVersion: '2024-02-15-preview', // Provide a common default API version
  115. createdAt: Date.now(),
  116. };
  117. default: // Handles CustomOpenAI
  118. return {
  119. apiKey: '',
  120. name: getDefaultDisplayNameFromProviderId(providerId),
  121. type: ProviderTypeEnum.CustomOpenAI,
  122. baseUrl: '',
  123. modelNames: [], // Custom providers use modelNames
  124. createdAt: Date.now(),
  125. };
  126. }
  127. }
  128. export function getDefaultAgentModelParams(providerId: string, agentName: AgentNameEnum): Record<string, number> {
  129. const newParameters = llmProviderParameters[providerId as keyof typeof llmProviderParameters]?.[agentName] || {
  130. temperature: 0.1,
  131. topP: 0.1,
  132. };
  133. return newParameters;
  134. }
  135. // Helper function to ensure backward compatibility for provider configs
  136. function ensureBackwardCompatibility(providerId: string, config: ProviderConfig): ProviderConfig {
  137. // Log input config
  138. // console.log(`[ensureBackwardCompatibility] Input for ${providerId}:`, JSON.stringify(config));
  139. const updatedConfig = { ...config };
  140. // Ensure name exists
  141. if (!updatedConfig.name) {
  142. updatedConfig.name = getDefaultDisplayNameFromProviderId(providerId);
  143. }
  144. // Ensure type exists
  145. if (!updatedConfig.type) {
  146. updatedConfig.type = getProviderTypeByProviderId(providerId);
  147. }
  148. // Handle Azure specifics
  149. if (updatedConfig.type === ProviderTypeEnum.AzureOpenAI) {
  150. // Ensure Azure fields exist, provide defaults if missing
  151. if (updatedConfig.azureApiVersion === undefined) {
  152. // console.log(`[ensureBackwardCompatibility] Adding default azureApiVersion for ${providerId}`);
  153. updatedConfig.azureApiVersion = '2024-02-15-preview';
  154. }
  155. // Initialize azureDeploymentNames array if it doesn't exist yet
  156. if (!updatedConfig.azureDeploymentNames) {
  157. updatedConfig.azureDeploymentNames = [];
  158. }
  159. // CRITICAL: Delete modelNames if it exists for Azure type to clean up old configs
  160. if (Object.prototype.hasOwnProperty.call(updatedConfig, 'modelNames')) {
  161. // console.log(`[ensureBackwardCompatibility] Deleting modelNames for Azure config ${providerId}`);
  162. delete updatedConfig.modelNames;
  163. }
  164. } else {
  165. // Ensure modelNames exists ONLY for non-Azure types
  166. if (!updatedConfig.modelNames) {
  167. // console.log(`[ensureBackwardCompatibility] Adding default modelNames for non-Azure ${providerId}`);
  168. updatedConfig.modelNames = llmProviderModelNames[providerId as keyof typeof llmProviderModelNames] || [];
  169. }
  170. }
  171. // Ensure createdAt exists
  172. if (!updatedConfig.createdAt) {
  173. updatedConfig.createdAt = new Date('03/04/2025').getTime();
  174. }
  175. // Log output config
  176. // console.log(`[ensureBackwardCompatibility] Output for ${providerId}:`, JSON.stringify(updatedConfig));
  177. return updatedConfig;
  178. }
  179. export const llmProviderStore: LLMProviderStorage = {
  180. ...storage,
  181. async setProvider(providerId: string, config: ProviderConfig) {
  182. if (!providerId) {
  183. throw new Error('Provider id cannot be empty');
  184. }
  185. if (config.apiKey === undefined) {
  186. throw new Error('API key must be provided (can be empty for local models)');
  187. }
  188. const providerType = config.type || getProviderTypeByProviderId(providerId);
  189. if (providerType === ProviderTypeEnum.AzureOpenAI) {
  190. if (!config.baseUrl?.trim()) {
  191. throw new Error('Azure Endpoint (baseUrl) is required');
  192. }
  193. if (!config.azureDeploymentNames || config.azureDeploymentNames.length === 0) {
  194. throw new Error('At least one Azure Deployment Name is required');
  195. }
  196. if (!config.azureApiVersion?.trim()) {
  197. throw new Error('Azure API Version is required');
  198. }
  199. if (!config.apiKey?.trim()) {
  200. throw new Error('API Key is required for Azure OpenAI');
  201. }
  202. } else if (providerType !== ProviderTypeEnum.CustomOpenAI && providerType !== ProviderTypeEnum.Ollama) {
  203. if (!config.apiKey?.trim()) {
  204. throw new Error(`API Key is required for ${getDefaultDisplayNameFromProviderId(providerId)}`);
  205. }
  206. }
  207. if (providerType !== ProviderTypeEnum.AzureOpenAI) {
  208. if (!config.modelNames || config.modelNames.length === 0) {
  209. console.warn(`Provider ${providerId} of type ${providerType} is being saved without model names.`);
  210. }
  211. }
  212. const completeConfig: ProviderConfig = {
  213. apiKey: config.apiKey || '',
  214. baseUrl: config.baseUrl,
  215. name: config.name || getDefaultDisplayNameFromProviderId(providerId),
  216. type: providerType,
  217. createdAt: config.createdAt || Date.now(),
  218. ...(providerType === ProviderTypeEnum.AzureOpenAI
  219. ? {
  220. azureDeploymentNames: config.azureDeploymentNames || [],
  221. azureApiVersion: config.azureApiVersion,
  222. }
  223. : {
  224. modelNames: config.modelNames || [],
  225. }),
  226. };
  227. console.log(`[llmProviderStore.setProvider] Saving config for ${providerId}:`, JSON.stringify(completeConfig));
  228. const current = (await storage.get()) || { providers: {} };
  229. await storage.set({
  230. providers: {
  231. ...current.providers,
  232. [providerId]: completeConfig,
  233. },
  234. });
  235. },
  236. async getProvider(providerId: string) {
  237. const data = (await storage.get()) || { providers: {} };
  238. const config = data.providers[providerId];
  239. return config ? ensureBackwardCompatibility(providerId, config) : undefined;
  240. },
  241. async removeProvider(providerId: string) {
  242. const current = (await storage.get()) || { providers: {} };
  243. const newProviders = { ...current.providers };
  244. delete newProviders[providerId];
  245. await storage.set({ providers: newProviders });
  246. },
  247. async hasProvider(providerId: string) {
  248. const data = (await storage.get()) || { providers: {} };
  249. return providerId in data.providers;
  250. },
  251. async getAllProviders() {
  252. const data = await storage.get();
  253. const providers = { ...data.providers };
  254. // Add backward compatibility for all providers
  255. for (const [providerId, config] of Object.entries(providers)) {
  256. providers[providerId] = ensureBackwardCompatibility(providerId, config);
  257. }
  258. return providers;
  259. },
  260. };