From a218303f52915c2bdfd8e02683718ba9d21c5f6b Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Fri, 7 Nov 2025 22:22:49 +0000 Subject: [PATCH 1/4] fix: Fixes dynamic loading of provider packages --- .../src/api/providers/AIProviderFactory.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts b/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts index 0d33eb69a..f233bda7a 100644 --- a/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts +++ b/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts @@ -131,10 +131,16 @@ export class AIProviderFactory { logger?: LDLogger, ): Promise { try { - // Try to dynamically import the provider - // This will work if the package is installed - // eslint-disable-next-line import/no-extraneous-dependencies, global-require, import/no-dynamic-require - const { [providerClassName]: ProviderClass } = require(packageName); + // Use dynamic import to load the provider module + // This uses ESM resolution which can find packages in the user's node_modules + // eslint-disable-next-line import/no-extraneous-dependencies + const module = await import(packageName); + const ProviderClass = module[providerClassName]; + + if (!ProviderClass) { + logger?.warn(`Provider class ${providerClassName} not found in package ${packageName}`); + return undefined; + } const provider = await ProviderClass.create(aiConfig, logger); logger?.debug( @@ -142,10 +148,18 @@ export class AIProviderFactory { ); return provider; } catch (error) { - // If the provider is not available or creation fails, return undefined - logger?.warn( - `Error creating AIProvider for: ${aiConfig.provider?.name} with package ${packageName}: ${error}`, - ); + // Provide helpful error message if module is not found + const err = error as Error & { code?: string }; + if (err.code === 'ERR_MODULE_NOT_FOUND' || err.message?.includes('Cannot find module')) { + logger?.warn( + `Error creating AIProvider for: ${aiConfig.provider?.name} with package ${packageName}: ${err.message}. ` + + `Please install the ${packageName} package with your preferred package manager.`, + ); + } else { + logger?.warn( + `Error creating AIProvider for: ${aiConfig.provider?.name} with package ${packageName}: ${error}`, + ); + } return undefined; } } From ba1545dc8d8eba196249e0c42d4f66902c8f726f Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Fri, 7 Nov 2025 23:20:26 +0000 Subject: [PATCH 2/4] fix: Remove invalid peer dependency --- packages/ai-providers/server-ai-langchain/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ai-providers/server-ai-langchain/package.json b/packages/ai-providers/server-ai-langchain/package.json index d7e30f979..8a9d8b560 100644 --- a/packages/ai-providers/server-ai-langchain/package.json +++ b/packages/ai-providers/server-ai-langchain/package.json @@ -48,7 +48,6 @@ }, "peerDependencies": { "@langchain/core": "^0.2.0 || ^0.3.0", - "@launchdarkly/server-sdk-ai": "^0.14.0", - "langchain": "^0.2.0 || ^0.3.0" + "@launchdarkly/server-sdk-ai": "^0.14.0" } } From 6063c13f3fb54895bef6cdfb80c753b2a27abe46 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Fri, 7 Nov 2025 23:33:43 +0000 Subject: [PATCH 3/4] revert previous commit --- packages/ai-providers/server-ai-langchain/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ai-providers/server-ai-langchain/package.json b/packages/ai-providers/server-ai-langchain/package.json index 8a9d8b560..d7e30f979 100644 --- a/packages/ai-providers/server-ai-langchain/package.json +++ b/packages/ai-providers/server-ai-langchain/package.json @@ -48,6 +48,7 @@ }, "peerDependencies": { "@langchain/core": "^0.2.0 || ^0.3.0", - "@launchdarkly/server-sdk-ai": "^0.14.0" + "@launchdarkly/server-sdk-ai": "^0.14.0", + "langchain": "^0.2.0 || ^0.3.0" } } From 730c0cb3e1c2a005b609a3bd308e5bbc8bc12486 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Sat, 8 Nov 2025 02:12:21 +0000 Subject: [PATCH 4/4] Hardcode the imports so that tree shaking doesn't remove needed packages --- .../src/api/providers/AIProviderFactory.ts | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts b/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts index f233bda7a..54d38c907 100644 --- a/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts +++ b/packages/sdk/server-ai/src/api/providers/AIProviderFactory.ts @@ -94,57 +94,64 @@ export class AIProviderFactory { aiConfig: LDAIConfigKind, logger?: LDLogger, ): Promise { + let getProviderClass: () => Promise; + switch (providerType) { case 'openai': - return this._createProvider( - '@launchdarkly/server-sdk-ai-openai', - 'OpenAIProvider', - aiConfig, - logger, - ); + // Lambda with hardcoded import - webpack can statically analyze this + getProviderClass = async () => { + // eslint-disable-next-line import/no-extraneous-dependencies + const module = await import('@launchdarkly/server-sdk-ai-openai'); + return module.OpenAIProvider as unknown as typeof AIProvider; + }; + break; case 'langchain': - return this._createProvider( - '@launchdarkly/server-sdk-ai-langchain', - 'LangChainProvider', - aiConfig, - logger, - ); + // Lambda with hardcoded import - webpack can statically analyze this + getProviderClass = async () => { + // eslint-disable-next-line import/no-extraneous-dependencies + const module = await import('@launchdarkly/server-sdk-ai-langchain'); + return module.LangChainProvider as unknown as typeof AIProvider; + }; + break; case 'vercel': - return this._createProvider( - '@launchdarkly/server-sdk-ai-vercel', - 'VercelProvider', - aiConfig, - logger, - ); + // Lambda with hardcoded import - webpack can statically analyze this + getProviderClass = async () => { + // eslint-disable-next-line import/no-extraneous-dependencies + const module = await import('@launchdarkly/server-sdk-ai-vercel'); + return module.VercelProvider as unknown as typeof AIProvider; + }; + break; default: return undefined; } + + return this._createProvider(getProviderClass, providerType, aiConfig, logger); } /** * Create a provider instance dynamically. + * @param getProviderClass Lambda function that imports the module and returns the provider class + * @param providerType Provider type name for error messages + * @param aiConfig The AI configuration + * @param logger Optional logger */ private static async _createProvider( - packageName: string, - providerClassName: string, + getProviderClass: () => Promise, + providerType: SupportedAIProvider, aiConfig: LDAIConfigKind, logger?: LDLogger, ): Promise { try { - // Use dynamic import to load the provider module - // This uses ESM resolution which can find packages in the user's node_modules - // eslint-disable-next-line import/no-extraneous-dependencies - const module = await import(packageName); - const ProviderClass = module[providerClassName]; + const ProviderClass = await getProviderClass(); if (!ProviderClass) { - logger?.warn(`Provider class ${providerClassName} not found in package ${packageName}`); + logger?.warn(`Provider class not found for provider ${providerType}`); return undefined; } const provider = await ProviderClass.create(aiConfig, logger); logger?.debug( - `Successfully created AIProvider for: ${aiConfig.provider?.name} with package ${packageName}`, + `Successfully created AIProvider for: ${aiConfig.provider?.name} with provider ${providerType}`, ); return provider; } catch (error) { @@ -152,12 +159,12 @@ export class AIProviderFactory { const err = error as Error & { code?: string }; if (err.code === 'ERR_MODULE_NOT_FOUND' || err.message?.includes('Cannot find module')) { logger?.warn( - `Error creating AIProvider for: ${aiConfig.provider?.name} with package ${packageName}: ${err.message}. ` + - `Please install the ${packageName} package with your preferred package manager.`, + `Error creating AIProvider for: ${aiConfig.provider?.name} with provider ${providerType}: ${err.message}. ` + + `Please install the required package with your preferred package manager.`, ); } else { logger?.warn( - `Error creating AIProvider for: ${aiConfig.provider?.name} with package ${packageName}: ${error}`, + `Error creating AIProvider for: ${aiConfig.provider?.name} with provider ${providerType}: ${error}`, ); } return undefined;