llmProviders.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { StorageEnum } from '../base/enums';
  2. import { createStorage } from '../base/base';
  3. import type { BaseStorage } from '../base/types';
  4. import { llmProviderModelNames, 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
  11. modelNames?: string[]; // Chosen model names, if not provided use hardcoded names from llmProviderModelNames
  12. createdAt?: number; // Timestamp in milliseconds when the provider was created
  13. }
  14. // Interface for storing multiple LLM provider configurations
  15. // The key is the provider id, which is the same as the provider type for built-in providers, but is custom for custom providers
  16. export interface LLMKeyRecord {
  17. providers: Record<string, ProviderConfig>;
  18. }
  19. export type LLMProviderStorage = BaseStorage<LLMKeyRecord> & {
  20. setProvider: (providerId: string, config: ProviderConfig) => Promise<void>;
  21. getProvider: (providerId: string) => Promise<ProviderConfig | undefined>;
  22. removeProvider: (providerId: string) => Promise<void>;
  23. hasProvider: (providerId: string) => Promise<boolean>;
  24. getAllProviders: () => Promise<Record<string, ProviderConfig>>;
  25. };
  26. // Storage for LLM provider configurations
  27. // use "llm-api-keys" as the key for the storage, for backward compatibility
  28. const storage = createStorage<LLMKeyRecord>(
  29. 'llm-api-keys',
  30. { providers: {} },
  31. {
  32. storageEnum: StorageEnum.Local,
  33. liveUpdate: true,
  34. },
  35. );
  36. // Helper function to determine provider type from provider name
  37. export function getProviderTypeByProviderId(providerId: string): ProviderTypeEnum {
  38. switch (providerId) {
  39. case ProviderTypeEnum.OpenAI:
  40. case ProviderTypeEnum.Anthropic:
  41. case ProviderTypeEnum.Gemini:
  42. case ProviderTypeEnum.Ollama:
  43. return providerId;
  44. default:
  45. return ProviderTypeEnum.CustomOpenAI;
  46. }
  47. }
  48. // Helper function to get display name from provider id
  49. export function getDefaultDisplayNameFromProviderId(providerId: string): string {
  50. switch (providerId) {
  51. case ProviderTypeEnum.OpenAI:
  52. return 'OpenAI';
  53. case ProviderTypeEnum.Anthropic:
  54. return 'Anthropic';
  55. case ProviderTypeEnum.Gemini:
  56. return 'Gemini';
  57. case ProviderTypeEnum.Ollama:
  58. return 'Ollama';
  59. default:
  60. return providerId; // Use the provider id as display name for custom providers by default
  61. }
  62. }
  63. // Get default configuration for built-in providers
  64. export function getDefaultProviderConfig(providerId: string): ProviderConfig {
  65. switch (providerId) {
  66. case ProviderTypeEnum.OpenAI:
  67. return {
  68. apiKey: '',
  69. name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.OpenAI),
  70. type: ProviderTypeEnum.OpenAI,
  71. modelNames: [...(llmProviderModelNames[ProviderTypeEnum.OpenAI] || [])],
  72. createdAt: Date.now(),
  73. };
  74. case ProviderTypeEnum.Anthropic:
  75. return {
  76. apiKey: '',
  77. name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.Anthropic),
  78. type: ProviderTypeEnum.Anthropic,
  79. modelNames: [...(llmProviderModelNames[ProviderTypeEnum.Anthropic] || [])],
  80. createdAt: Date.now(),
  81. };
  82. case ProviderTypeEnum.Gemini:
  83. return {
  84. apiKey: '',
  85. name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.Gemini),
  86. type: ProviderTypeEnum.Gemini,
  87. modelNames: [...(llmProviderModelNames[ProviderTypeEnum.Gemini] || [])],
  88. createdAt: Date.now(),
  89. };
  90. case ProviderTypeEnum.Ollama:
  91. return {
  92. apiKey: 'ollama', // Set default API key for Ollama
  93. name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.Ollama),
  94. type: ProviderTypeEnum.Ollama,
  95. modelNames: [],
  96. baseUrl: 'http://localhost:11434',
  97. createdAt: Date.now(),
  98. };
  99. default:
  100. return {
  101. apiKey: '',
  102. name: getDefaultDisplayNameFromProviderId(providerId),
  103. type: ProviderTypeEnum.CustomOpenAI,
  104. baseUrl: '',
  105. modelNames: [],
  106. createdAt: Date.now(),
  107. };
  108. }
  109. }
  110. // Helper function to ensure backward compatibility for provider configs
  111. function ensureBackwardCompatibility(providerId: string, config: ProviderConfig): ProviderConfig {
  112. const updatedConfig = { ...config };
  113. if (!updatedConfig.name) {
  114. updatedConfig.name = getDefaultDisplayNameFromProviderId(providerId);
  115. }
  116. if (!updatedConfig.type) {
  117. updatedConfig.type = getProviderTypeByProviderId(providerId);
  118. }
  119. if (!updatedConfig.modelNames) {
  120. updatedConfig.modelNames = llmProviderModelNames[providerId as keyof typeof llmProviderModelNames] || [];
  121. }
  122. if (!updatedConfig.createdAt) {
  123. // if createdAt is not set, set it to "03/04/2025" for backward compatibility
  124. updatedConfig.createdAt = new Date('03/04/2025').getTime();
  125. }
  126. return updatedConfig;
  127. }
  128. export const llmProviderStore: LLMProviderStorage = {
  129. ...storage,
  130. async setProvider(providerId: string, config: ProviderConfig) {
  131. if (!providerId) {
  132. throw new Error('Provider id cannot be empty');
  133. }
  134. if (config.apiKey === undefined) {
  135. throw new Error('API key must be provided (can be empty for local models)');
  136. }
  137. if (!config.modelNames) {
  138. throw new Error('Model names must be provided');
  139. }
  140. // Ensure backward compatibility by filling in missing fields
  141. const completeConfig: ProviderConfig = {
  142. ...config,
  143. name: config.name || getDefaultDisplayNameFromProviderId(providerId),
  144. type: config.type || getProviderTypeByProviderId(providerId),
  145. modelNames: config.modelNames,
  146. createdAt: config.createdAt || Date.now(),
  147. };
  148. const current = (await storage.get()) || { providers: {} };
  149. await storage.set({
  150. providers: {
  151. ...current.providers,
  152. [providerId]: completeConfig,
  153. },
  154. });
  155. },
  156. async getProvider(providerId: string) {
  157. const data = (await storage.get()) || { providers: {} };
  158. const config = data.providers[providerId];
  159. return config ? ensureBackwardCompatibility(providerId, config) : undefined;
  160. },
  161. async removeProvider(providerId: string) {
  162. const current = (await storage.get()) || { providers: {} };
  163. const newProviders = { ...current.providers };
  164. delete newProviders[providerId];
  165. await storage.set({ providers: newProviders });
  166. },
  167. async hasProvider(providerId: string) {
  168. const data = (await storage.get()) || { providers: {} };
  169. return providerId in data.providers;
  170. },
  171. async getAllProviders() {
  172. const data = await storage.get();
  173. const providers = { ...data.providers };
  174. // Add backward compatibility for all providers
  175. for (const [providerId, config] of Object.entries(providers)) {
  176. providers[providerId] = ensureBackwardCompatibility(providerId, config);
  177. }
  178. return providers;
  179. },
  180. };