From 2667a1e65843cc182679820bb7fbfe4726ad3ef3 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Wed, 6 May 2026 18:19:59 -0500 Subject: [PATCH 1/6] feat!: remove deprecated AIProvider methods and create*/init* aliases (AIC-2388) Now that all provider packages (#1337 OpenAI, #1338 LangChain, #1339 Vercel) have migrated to the Runner protocol, the deprecated compatibility surface on the umbrella `feat/next-ai-release` branch can be removed. BREAKING CHANGE: Removes the following deprecated APIs: AIProvider abstract base class: - `invokeModel()` and `invokeStructuredModel()` instance methods - `static create()` factory method - `protected logger` field on the constructor (provider subclasses now declare their own private `_logger` field) LDAIClient deprecated method aliases: - `config()` (use `completionConfig`) - `agent()` (use `agentConfig`) - `agents()` (use `agentConfigs`) - `createChat()` and `initChat()` (use `createModel`) Public types: - `StructuredResponse` (only consumed by the removed `invokeStructuredModel` method) Type narrowing on `AIProvider` and `RunnerFactory`: - `createModel(config: LDAICompletionConfig | LDAIJudgeConfig)` - `createAgent(config: LDAIAgentConfig)` - `createAgentGraph(graphDef: AgentGraphDefinition)` Previously these accepted the wider `LDAIConfigKind` union; the new signatures mirror what the provider subclasses already declared and surface mode mismatches at the type level. Also updates README examples to use `createModel`/`run()` and removes the stale `_LegacyProviderAdapter` references in `RunnerFactory` doc comments. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../server-ai-langchain/README.md | 18 +-- .../src/LangChainRunnerFactory.ts | 11 +- .../ai-providers/server-ai-openai/README.md | 18 +-- .../src/OpenAIRunnerFactory.ts | 15 +- .../ai-providers/server-ai-vercel/README.md | 18 +-- .../src/VercelRunnerFactory.ts | 7 +- packages/sdk/server-ai/README.md | 29 ++-- .../__tests__/LDAIClientImpl.test.ts | 2 +- .../server-ai/__tests__/RunnerFactory.test.ts | 49 +++---- .../examples/openai-observability/README.md | 2 +- packages/sdk/server-ai/src/LDAIClientImpl.ts | 60 -------- packages/sdk/server-ai/src/api/LDAIClient.ts | 50 ------- packages/sdk/server-ai/src/api/judge/index.ts | 2 +- packages/sdk/server-ai/src/api/judge/types.ts | 18 --- .../server-ai/src/api/providers/AIProvider.ts | 132 +++--------------- .../src/api/providers/RunnerFactory.ts | 23 ++- 16 files changed, 121 insertions(+), 333 deletions(-) diff --git a/packages/ai-providers/server-ai-langchain/README.md b/packages/ai-providers/server-ai-langchain/README.md index bfc35f7d27..f899682093 100644 --- a/packages/ai-providers/server-ai-langchain/README.md +++ b/packages/ai-providers/server-ai-langchain/README.md @@ -20,7 +20,7 @@ ## Quick Setup -This package provides LangChain integration for the LaunchDarkly AI SDK. The simplest way to use it is with the LaunchDarkly AI SDK's `initChat` method: +This package provides LangChain integration for the LaunchDarkly AI SDK. The simplest way to use it is with the LaunchDarkly AI SDK's `createModel` method: 1. Install the required packages: @@ -30,7 +30,7 @@ npm install @launchdarkly/server-sdk-ai @launchdarkly/server-sdk-ai-langchain -- yarn add @launchdarkly/server-sdk-ai @launchdarkly/server-sdk-ai-langchain ``` -2. Create a chat session and use it: +2. Create a managed model and run it: ```typescript import { init } from '@launchdarkly/node-server-sdk'; @@ -40,17 +40,17 @@ import { initAi } from '@launchdarkly/server-sdk-ai'; const ldClient = init(sdkKey); const aiClient = initAi(ldClient); -// Create a chat session -const defaultConfig = { - enabled: true, +// Create a managed model +const defaultConfig = { + enabled: true, model: { name: 'gpt-4' }, provider: { name: 'openai' } }; -const chat = await aiClient.initChat('my-chat-config', context, defaultConfig); +const model = await aiClient.createModel('my-chat-config', context, defaultConfig); -if (chat) { - const response = await chat.invoke('What is the capital of France?'); - console.log(response.message.content); +if (model) { + const result = await model.run('What is the capital of France?'); + console.log(result.content); } ``` diff --git a/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts b/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts index 482447c900..90cbd1d38a 100644 --- a/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts +++ b/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts @@ -18,8 +18,11 @@ let instrumentPromise: Promise | undefined; * Factory for creating LangChain runners (chat completion and agent). */ export class LangChainRunnerFactory extends AIProvider { + private _logger?: LDLogger; + constructor(logger?: LDLogger) { - super(logger); + super(); + this._logger = logger; // eslint-disable-next-line no-underscore-dangle LangChainRunnerFactory._ensureInstrumented(logger).catch(() => {}); } @@ -29,7 +32,7 @@ export class LangChainRunnerFactory extends AIProvider { */ async createModel(config: LDAICompletionConfig): Promise { const llm = await createLangChainModel(config); - return new LangChainModelRunner(llm, config, this.logger); + return new LangChainModelRunner(llm, config, this._logger); } /** @@ -51,7 +54,7 @@ export class LangChainRunnerFactory extends AIProvider { }; const llm = await createLangChainModel(configForModel); - const lcTools = buildStructuredTools(toolDefinitions, tools ?? {}, this.logger); + const lcTools = buildStructuredTools(toolDefinitions, tools ?? {}, this._logger); const instructions = config.instructions ?? ''; const agent = createAgent({ @@ -60,7 +63,7 @@ export class LangChainRunnerFactory extends AIProvider { systemPrompt: instructions || undefined, }); - return new LangChainAgentRunner(agent as any, this.logger); + return new LangChainAgentRunner(agent as any, this._logger); } /** diff --git a/packages/ai-providers/server-ai-openai/README.md b/packages/ai-providers/server-ai-openai/README.md index 6cc18807df..dbc01cd941 100644 --- a/packages/ai-providers/server-ai-openai/README.md +++ b/packages/ai-providers/server-ai-openai/README.md @@ -23,7 +23,7 @@ ## Quick Setup -This package provides OpenAI integration for the LaunchDarkly AI SDK. The simplest way to use it is with the LaunchDarkly AI SDK's `initChat` method: +This package provides OpenAI integration for the LaunchDarkly AI SDK. The simplest way to use it is with the LaunchDarkly AI SDK's `createModel` method: 1. Install the required packages: @@ -31,7 +31,7 @@ This package provides OpenAI integration for the LaunchDarkly AI SDK. The simple npm install @launchdarkly/server-sdk-ai @launchdarkly/server-sdk-ai-openai --save ``` -2. Create a chat session and use it: +2. Create a managed model and run it: ```typescript import { init } from '@launchdarkly/node-server-sdk'; @@ -41,17 +41,17 @@ import { initAi } from '@launchdarkly/server-sdk-ai'; const ldClient = init(sdkKey); const aiClient = initAi(ldClient); -// Create a chat session -const defaultConfig = { - enabled: true, +// Create a managed model +const defaultConfig = { + enabled: true, model: { name: 'gpt-4' }, provider: { name: 'openai' } }; -const chat = await aiClient.initChat('my-chat-config', context, defaultConfig); +const model = await aiClient.createModel('my-chat-config', context, defaultConfig); -if (chat) { - const response = await chat.invoke("What is the capital of France?"); - console.log(response.message.content); +if (model) { + const result = await model.run('What is the capital of France?'); + console.log(result.content); } ``` diff --git a/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts b/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts index 97eebf64bb..b9c2cb215b 100644 --- a/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts +++ b/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts @@ -17,9 +17,11 @@ let instrumentPromise: Promise | undefined; */ export class OpenAIRunnerFactory extends AIProvider { private _client: OpenAI; + private _logger?: LDLogger; constructor(logger?: LDLogger) { - super(logger); + super(); + this._logger = logger; this._client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // Fire-and-forget: OTel instrumentation is optional and must not block construction. // eslint-disable-next-line no-underscore-dangle @@ -30,7 +32,7 @@ export class OpenAIRunnerFactory extends AIProvider { * Create a model runner from a completion AI configuration. */ async createModel(config: LDAICompletionConfig): Promise { - return new OpenAIModelRunner(this._client, config, this.logger); + return new OpenAIModelRunner(this._client, config, this._logger); } /** @@ -67,7 +69,12 @@ export class OpenAIRunnerFactory extends AIProvider { const parameters = mapParameterKeys({ ...(config.model?.parameters ?? {}) }); delete parameters.tools; - const { agentTools, toolNameMap } = buildAgentTools(toolHelper, configTools, registry, this.logger); + const { agentTools, toolNameMap } = buildAgentTools( + toolHelper, + configTools, + registry, + this._logger, + ); const agent = new Agent({ name: 'ldai-agent', instructions: config.instructions || undefined, @@ -76,7 +83,7 @@ export class OpenAIRunnerFactory extends AIProvider { modelSettings: parameters, }); - return new OpenAIAgentRunner(agent, agentRun, toolNameMap, this.logger); + return new OpenAIAgentRunner(agent, agentRun, toolNameMap, this._logger); } /** diff --git a/packages/ai-providers/server-ai-vercel/README.md b/packages/ai-providers/server-ai-vercel/README.md index fb65d1832f..f320194983 100644 --- a/packages/ai-providers/server-ai-vercel/README.md +++ b/packages/ai-providers/server-ai-vercel/README.md @@ -20,7 +20,7 @@ ## Quick Setup -This package provides Vercel AI SDK integration for the LaunchDarkly AI SDK. The simplest way to use it is with the LaunchDarkly AI SDK's `initChat` method: +This package provides Vercel AI SDK integration for the LaunchDarkly AI SDK. The simplest way to use it is with the LaunchDarkly AI SDK's `createModel` method: 1. Install the required packages: @@ -30,7 +30,7 @@ npm install @launchdarkly/server-sdk-ai @launchdarkly/server-sdk-ai-vercel --sav yarn add @launchdarkly/server-sdk-ai @launchdarkly/server-sdk-ai-vercel ``` -2. Create a chat session and use it: +2. Create a managed model and run it: ```typescript import { init } from '@launchdarkly/node-server-sdk'; @@ -40,17 +40,17 @@ import { initAi } from '@launchdarkly/server-sdk-ai'; const ldClient = init(sdkKey); const aiClient = initAi(ldClient); -// Create a chat session -const defaultConfig = { - enabled: true, +// Create a managed model +const defaultConfig = { + enabled: true, model: { name: 'gpt-4' }, provider: { name: 'openai' } }; -const chat = await aiClient.initChat('my-chat-config', context, defaultConfig); +const model = await aiClient.createModel('my-chat-config', context, defaultConfig); -if (chat) { - const response = await chat.invoke('What is the capital of France?'); - console.log(response.message.content); +if (model) { + const result = await model.run('What is the capital of France?'); + console.log(result.content); } ``` diff --git a/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts b/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts index 0510900e3d..37a9bd8fe3 100644 --- a/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts +++ b/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts @@ -15,8 +15,11 @@ import { VercelModelRunner } from './VercelModelRunner'; * framework. */ export class VercelRunnerFactory extends AIProvider { + private _logger?: LDLogger; + constructor(logger?: LDLogger) { - super(logger); + super(); + this._logger = logger; } /** @@ -25,7 +28,7 @@ export class VercelRunnerFactory extends AIProvider { async createModel(config: LDAICompletionConfig): Promise { const model = await VercelRunnerFactory.createVercelModel(config); const parameters = VercelRunnerFactory.mapParameters(config.model?.parameters); - return new VercelModelRunner(model, config, parameters, this.logger); + return new VercelModelRunner(model, config, parameters, this._logger); } /** diff --git a/packages/sdk/server-ai/README.md b/packages/sdk/server-ai/README.md index 96e2faf4b0..081bfbc831 100644 --- a/packages/sdk/server-ai/README.md +++ b/packages/sdk/server-ai/README.md @@ -79,37 +79,34 @@ if (aiConfig.enabled) { } ``` -## TrackedChat for Conversational AI +## ManagedModel for Tracked Model Invocations -`TrackedChat` provides a high-level interface for conversational AI with automatic conversation management and metrics tracking: +`ManagedModel` provides a high-level interface for invoking AI models with automatic metrics tracking and judge evaluation: - Automatically configures models based on AI configuration -- Maintains conversation history across multiple interactions - Automatically tracks token usage, latency, and success rates +- Runs configured judges asynchronously and reports their results - Works with any supported AI provider (see [AI Providers](https://github.com/launchdarkly/js-core#ai-providers) for available packages) -### Using TrackedChat +### Using ManagedModel ```typescript // Use the same defaultConfig from the retrieval section above -const chat = await aiClient.createChat( +const model = await aiClient.createModel( 'customer-support-chat', context, defaultConfig, { customerName: 'John' } ); -if (chat) { - // Simple conversation flow - metrics are automatically tracked by invoke() - const response1 = await chat.invoke('I need help with my order'); - console.log(response1.message.content); - - const response2 = await chat.invoke("What's the status?"); - console.log(response2.message.content); - - // Access conversation history - const messages = chat.getMessages(); - console.log(`Conversation has ${messages.length} messages`); +if (model) { + // Metrics are automatically tracked by run() + const result = await model.run('I need help with my order'); + console.log(result.content); + + // Judge evaluations run asynchronously; await if you need their results + const evals = await result.evaluations; + console.log('Judge results:', evals); } ``` diff --git a/packages/sdk/server-ai/__tests__/LDAIClientImpl.test.ts b/packages/sdk/server-ai/__tests__/LDAIClientImpl.test.ts index 0e5ca6de0e..29a33aec1a 100644 --- a/packages/sdk/server-ai/__tests__/LDAIClientImpl.test.ts +++ b/packages/sdk/server-ai/__tests__/LDAIClientImpl.test.ts @@ -634,7 +634,7 @@ describe('createJudge method', () => { beforeEach(() => { mockProvider = { - invokeStructuredModel: jest.fn(), + run: jest.fn(), }; mockJudge = { diff --git a/packages/sdk/server-ai/__tests__/RunnerFactory.test.ts b/packages/sdk/server-ai/__tests__/RunnerFactory.test.ts index f76ca556e9..b7aa331fa7 100644 --- a/packages/sdk/server-ai/__tests__/RunnerFactory.test.ts +++ b/packages/sdk/server-ai/__tests__/RunnerFactory.test.ts @@ -1,4 +1,7 @@ -import { LDAIConfigKind } from '../src/api/config/types'; +import { + LDAIAgentConfig, + LDAICompletionConfig, +} from '../src/api/config/types'; import { AIProvider, ToolRegistry } from '../src/api/providers/AIProvider'; import { AgentGraphRunner, Runner } from '../src/api/providers/Runner'; import { RunnerFactory, SupportedAIProvider } from '../src/api/providers/RunnerFactory'; @@ -7,14 +10,23 @@ import { RunnerFactory, SupportedAIProvider } from '../src/api/providers/RunnerF // Helpers // --------------------------------------------------------------------------- -const makeConfig = (providerName: string): LDAIConfigKind => +const makeConfig = (providerName: string): LDAICompletionConfig => ({ key: 'test-config', enabled: true, provider: { name: providerName }, createTracker: () => ({}) as any, evaluator: {} as any, - }) as unknown as LDAIConfigKind; + }) as unknown as LDAICompletionConfig; + +const makeAgentConfig = (providerName: string): LDAIAgentConfig => + ({ + key: 'test-agent-config', + enabled: true, + provider: { name: providerName }, + createTracker: () => ({}) as any, + evaluator: {} as any, + }) as unknown as LDAIAgentConfig; const makeRunner = (): Runner => ({ run: jest.fn() }); const makeGraphRunner = (): AgentGraphRunner => ({ run: jest.fn() }); @@ -162,7 +174,7 @@ describe('RunnerFactory.createAgent', () => { jest.spyOn(RunnerFactory as any, '_getProviderFactory').mockResolvedValue(mockFactory); - const result = await RunnerFactory.createAgent(makeConfig('openai'), tools); + const result = await RunnerFactory.createAgent(makeAgentConfig('openai'), tools); expect(result).toBe(runner); expect(mockFactory.createAgent).toHaveBeenCalledWith( @@ -177,7 +189,11 @@ describe('RunnerFactory.createAgent', () => { jest.spyOn(RunnerFactory as any, '_getProviderFactory').mockResolvedValue(undefined); - const result = await RunnerFactory.createAgent(makeConfig('openai'), undefined, logger as any); + const result = await RunnerFactory.createAgent( + makeAgentConfig('openai'), + undefined, + logger as any, + ); expect(result).toBeUndefined(); expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('not supported')); @@ -243,7 +259,7 @@ describe('AIProvider default factory methods', () => { it('createAgent returns undefined by default', async () => { const provider = new ConcreteProvider(); - const result = await provider.createAgent(makeConfig('openai')); + const result = await provider.createAgent(makeAgentConfig('openai')); expect(result).toBeUndefined(); }); @@ -252,25 +268,4 @@ describe('AIProvider default factory methods', () => { const result = await provider.createAgentGraph({} as any); expect(result).toBeUndefined(); }); - - it('createModel warns when not overridden', async () => { - const warnSpy = jest.fn(); - const provider = new ConcreteProvider({ warn: warnSpy } as any); - await provider.createModel(makeConfig('openai')); - expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('createModel not implemented')); - }); - - it('createAgent warns when not overridden', async () => { - const warnSpy = jest.fn(); - const provider = new ConcreteProvider({ warn: warnSpy } as any); - await provider.createAgent(makeConfig('openai')); - expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('createAgent not implemented')); - }); - - it('createAgentGraph warns when not overridden', async () => { - const warnSpy = jest.fn(); - const provider = new ConcreteProvider({ warn: warnSpy } as any); - await provider.createAgentGraph({} as any); - expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('createAgentGraph not implemented')); - }); }); diff --git a/packages/sdk/server-ai/examples/openai-observability/README.md b/packages/sdk/server-ai/examples/openai-observability/README.md index 2e43d77f58..a958d45575 100644 --- a/packages/sdk/server-ai/examples/openai-observability/README.md +++ b/packages/sdk/server-ai/examples/openai-observability/README.md @@ -1,6 +1,6 @@ # Provider-Specific Observability Example (OpenAI) -This example shows how to use the LaunchDarkly observability plugin when calling an AI provider directly — without the higher-level `createChat` abstraction. It uses OpenAI as the provider, but the same pattern applies to any provider (Bedrock, Anthropic, Vercel AI SDK, etc.). +This example shows how to use the LaunchDarkly observability plugin when calling an AI provider directly — without the higher-level `createModel` abstraction. It uses OpenAI as the provider, but the same pattern applies to any provider (Bedrock, Anthropic, Vercel AI SDK, etc.). ## How it works diff --git a/packages/sdk/server-ai/src/LDAIClientImpl.ts b/packages/sdk/server-ai/src/LDAIClientImpl.ts index c655db2551..7192be0ad9 100644 --- a/packages/sdk/server-ai/src/LDAIClientImpl.ts +++ b/packages/sdk/server-ai/src/LDAIClientImpl.ts @@ -221,18 +221,6 @@ export class LDAIClientImpl implements LDAIClient { ); } - /** - * @deprecated Use `completionConfig` instead. This method will be removed in a future version. - */ - async config( - key: string, - context: LDContext, - defaultValue?: LDAICompletionConfigDefault, - variables?: Record, - ): Promise { - return this.completionConfig(key, context, defaultValue, variables); - } - private async _judgeConfig( key: string, context: LDContext, @@ -290,18 +278,6 @@ export class LDAIClientImpl implements LDAIClient { ); } - /** - * @deprecated Use `agentConfig` instead. This method will be removed in a future version. - */ - async agent( - key: string, - context: LDContext, - defaultValue?: LDAIAgentConfigDefault, - variables?: Record, - ): Promise { - return this.agentConfig(key, context, defaultValue, variables); - } - async agentConfigs( agentConfigs: T, context: LDContext, @@ -330,29 +306,6 @@ export class LDAIClientImpl implements LDAIClient { return agents; } - /** - * @deprecated Use `agentConfigs` instead. This method will be removed in a future version. - */ - async agents( - agentConfigs: T, - context: LDContext, - ): Promise> { - return this.agentConfigs(agentConfigs, context); - } - - /** - * @deprecated Use `createModel` instead. This method will be removed in a future version. - */ - async createChat( - key: string, - context: LDContext, - defaultValue?: LDAICompletionConfigDefault, - variables?: Record, - defaultAiProvider?: SupportedAIProvider, - ): Promise { - return this.createModel(key, context, defaultValue, variables, defaultAiProvider); - } - async createJudge( key: string, context: LDContext, @@ -483,19 +436,6 @@ export class LDAIClientImpl implements LDAIClient { return new ManagedAgent(config, runner, this._logger); } - /** - * @deprecated Use `createModel` instead. This method will be removed in a future version. - */ - async initChat( - key: string, - context: LDContext, - defaultValue?: LDAICompletionConfigDefault, - variables?: Record, - defaultAiProvider?: SupportedAIProvider, - ): Promise { - return this.createModel(key, context, defaultValue, variables, defaultAiProvider); - } - createTracker(token: string, context: LDContext): LDAIConfigTracker { return LDAIConfigTrackerImpl.fromResumptionToken(token, this._ldClient, context); } diff --git a/packages/sdk/server-ai/src/api/LDAIClient.ts b/packages/sdk/server-ai/src/api/LDAIClient.ts index 749a739320..2b4935246f 100644 --- a/packages/sdk/server-ai/src/api/LDAIClient.ts +++ b/packages/sdk/server-ai/src/api/LDAIClient.ts @@ -81,16 +81,6 @@ export interface LDAIClient { defaultAiProvider?: SupportedAIProvider, ): Promise; - /** - * @deprecated Use `completionConfig` instead. This method will be removed in a future version. - */ - config( - key: string, - context: LDContext, - defaultValue?: LDAICompletionConfigDefault, - variables?: Record, - ): Promise; - /** * Retrieves and processes a single AI Config agent based on the provided key, LaunchDarkly context, * and variables. This includes the model configuration and the customized instructions. @@ -133,16 +123,6 @@ export interface LDAIClient { defaultAiProvider?: SupportedAIProvider, ): Promise; - /** - * @deprecated Use `agentConfig` instead. This method will be removed in a future version. - */ - agent( - key: string, - context: LDContext, - defaultValue?: LDAIAgentConfigDefault, - variables?: Record, - ): Promise; - /** * Retrieves and processes a Judge AI Config based on the provided key, LaunchDarkly context, * and variables. This includes the model configuration and the customized messages for evaluation. @@ -228,14 +208,6 @@ export interface LDAIClient { context: LDContext, ): Promise>; - /** - * @deprecated Use `agentConfigs` instead. This method will be removed in a future version. - */ - agents( - agentConfigs: T, - context: LDContext, - ): Promise>; - /** * Creates and returns a new ManagedModel instance for LLM model interactions. * @@ -297,28 +269,6 @@ export interface LDAIClient { defaultAiProvider?: SupportedAIProvider, ): Promise; - /** - * @deprecated Use `createModel` instead. This method will be removed in a future version. - */ - createChat( - key: string, - context: LDContext, - defaultValue?: LDAICompletionConfigDefault, - variables?: Record, - defaultAiProvider?: SupportedAIProvider, - ): Promise; - - /** - * @deprecated Use `createModel` instead. This method will be removed in a future version. - */ - initChat( - key: string, - context: LDContext, - defaultValue?: LDAICompletionConfigDefault, - variables?: Record, - defaultAiProvider?: SupportedAIProvider, - ): Promise; - /** * Creates and returns a new Judge instance for AI evaluation. * diff --git a/packages/sdk/server-ai/src/api/judge/index.ts b/packages/sdk/server-ai/src/api/judge/index.ts index ca86630278..4fa3bbb60a 100644 --- a/packages/sdk/server-ai/src/api/judge/index.ts +++ b/packages/sdk/server-ai/src/api/judge/index.ts @@ -1,2 +1,2 @@ export { Judge } from './Judge'; -export type { LDJudgeResult, StructuredResponse } from './types'; +export type { LDJudgeResult } from './types'; diff --git a/packages/sdk/server-ai/src/api/judge/types.ts b/packages/sdk/server-ai/src/api/judge/types.ts index b9d8a05a46..381eb97d90 100644 --- a/packages/sdk/server-ai/src/api/judge/types.ts +++ b/packages/sdk/server-ai/src/api/judge/types.ts @@ -1,21 +1,3 @@ -import { LDAIMetrics } from '../metrics/LDAIMetrics'; - -/** - * Structured response from AI models. - */ -export interface StructuredResponse { - /** The structured data returned by the model */ - data: Record; - - /** The raw response from the model */ - rawResponse: string; - - /** - * Metrics information including success status and token usage. - */ - metrics: LDAIMetrics; -} - /** * Result from a judge evaluation containing score, reasoning, and metadata. */ diff --git a/packages/sdk/server-ai/src/api/providers/AIProvider.ts b/packages/sdk/server-ai/src/api/providers/AIProvider.ts index 62a1fd55e5..d8ae79f840 100644 --- a/packages/sdk/server-ai/src/api/providers/AIProvider.ts +++ b/packages/sdk/server-ai/src/api/providers/AIProvider.ts @@ -1,9 +1,5 @@ -import { LDLogger } from '@launchdarkly/js-server-sdk-common'; - -import { ChatResponse } from '../chat/types'; -import { LDAIConfigKind, LDMessage } from '../config/types'; +import { LDAIAgentConfig, LDAICompletionConfig, LDAIJudgeConfig } from '../config/types'; import { AgentGraphDefinition } from '../graph/AgentGraphDefinition'; -import { StructuredResponse } from '../judge/types'; import { AgentGraphRunner, Runner } from './Runner'; /** @@ -14,97 +10,31 @@ import { AgentGraphRunner, Runner } from './Runner'; export type ToolRegistry = Record unknown>; /** - * Abstract base class for AI providers that implement chat model functionality. - * This class provides the contract that all provider implementations must follow - * to integrate with LaunchDarkly's tracking and configuration capabilities. + * Abstract base class for AI providers. + * + * An `AIProvider` is a per-provider factory: it is instantiated once per + * provider package and is responsible for constructing focused runtime + * capability objects via {@link createModel}, {@link createAgent}, and + * {@link createAgentGraph}. * - * Following the AICHAT spec recommendation to use base classes with non-abstract methods - * for better extensibility and backwards compatibility. + * Provider packages subclass `AIProvider` and override the methods they + * support. The default implementations return `undefined`, mirroring Python's + * base-class behaviour, so providers only need to implement the modes they + * actually support. */ export abstract class AIProvider { - protected readonly logger?: LDLogger; - - constructor(logger?: LDLogger) { - this.logger = logger; - } - /** - * Invoke the chat model with an array of messages. - * - * Default implementation takes no action and returns a placeholder response. - * Provider implementations should override this method. - * - * @deprecated Use the `Runner` interface and its `run` method instead. - * @param messages Array of LDMessage objects representing the conversation - * @returns Promise that resolves to a ChatResponse containing the model's response - */ - async invokeModel(_messages: LDMessage[]): Promise { - this.logger?.warn('invokeModel not implemented by this provider'); - return { - message: { - role: 'assistant', - content: '', - }, - metrics: { - success: false, - usage: { - total: 0, - input: 0, - output: 0, - }, - }, - }; - } - - /** - * Invoke the chat model with structured output support. - * - * Default implementation takes no action and returns a placeholder response. - * Provider implementations should override this method. - * - * @deprecated Use the `Runner` interface and its `run` method with `outputType` instead. - * @param messages Array of LDMessage objects representing the conversation - * @param responseStructure Dictionary of output configurations keyed by output name - * @returns Promise that resolves to a structured response - */ - async invokeStructuredModel( - _messages: LDMessage[], - _responseStructure: Record, - ): Promise { - this.logger?.warn('invokeStructuredModel not implemented by this provider'); - return { - data: {}, - rawResponse: '', - metrics: { - success: false, - usage: { - total: 0, - input: 0, - output: 0, - }, - }, - }; - } - - // ============================================================================ - // Factory instance methods (Python AIProvider pattern) - // - // Provider packages override these to return a configured Runner for the - // relevant mode. The default implementations log a warning and return - // undefined, mirroring Python's base-class behaviour. - // ============================================================================ - /** * Create a Runner for a completion or judge AI Config. * * Override in provider subclasses to return a configured {@link Runner}. - * Default implementation logs a warning and returns `undefined`. + * Default implementation returns `undefined`. * * @param config The completion or judge AI configuration. * @returns Promise resolving to a {@link Runner}, or `undefined` if this * provider does not support model creation. */ - async createModel(_config: LDAIConfigKind): Promise { - this.logger?.warn('createModel not implemented by this provider'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async createModel(config: LDAICompletionConfig | LDAIJudgeConfig): Promise { return undefined; } @@ -112,15 +42,15 @@ export abstract class AIProvider { * Create a Runner for an agent AI Config. * * Override in provider subclasses to return a configured {@link Runner}. - * Default implementation logs a warning and returns `undefined`. + * Default implementation returns `undefined`. * * @param config The agent AI configuration. * @param tools Optional registry of callable tools. * @returns Promise resolving to a {@link Runner}, or `undefined` if this * provider does not support agent creation. */ - async createAgent(_config: LDAIConfigKind, _tools?: ToolRegistry): Promise { - this.logger?.warn('createAgent not implemented by this provider'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async createAgent(config: LDAIAgentConfig, tools?: ToolRegistry): Promise { return undefined; } @@ -128,7 +58,7 @@ export abstract class AIProvider { * Create an AgentGraphRunner for an agent graph definition. * * Override in provider subclasses to return a configured {@link AgentGraphRunner}. - * Default implementation logs a warning and returns `undefined`. + * Default implementation returns `undefined`. * * @param graphDef The agent graph definition. * @param tools Optional registry of callable tools. @@ -136,29 +66,11 @@ export abstract class AIProvider { * this provider does not support graph execution. */ async createAgentGraph( - _graphDef: AgentGraphDefinition, - _tools?: ToolRegistry, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + graphDef: AgentGraphDefinition, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + tools?: ToolRegistry, ): Promise { - this.logger?.warn('createAgentGraph not implemented by this provider'); return undefined; } - - // ============================================================================ - // Legacy static factory (retained for backward compatibility) - // ============================================================================ - - /** - * Static method that constructs an instance of the provider. - * Each provider implementation must provide their own static create method - * that accepts an AIConfig and returns a configured instance. - * - * @deprecated Use the `createModel` factory method instead. - * @param aiConfig The LaunchDarkly AI configuration - * @param logger Optional logger for the provider - * @returns Promise that resolves to a configured provider instance - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - static async create(aiConfig: LDAIConfigKind, logger?: LDLogger): Promise { - throw new Error('Provider implementations must override the static create method'); - } } diff --git a/packages/sdk/server-ai/src/api/providers/RunnerFactory.ts b/packages/sdk/server-ai/src/api/providers/RunnerFactory.ts index d02c79d445..0451118495 100644 --- a/packages/sdk/server-ai/src/api/providers/RunnerFactory.ts +++ b/packages/sdk/server-ai/src/api/providers/RunnerFactory.ts @@ -1,6 +1,10 @@ import { LDLogger } from '@launchdarkly/js-server-sdk-common'; -import { LDAIConfigKind } from '../config/types'; +import { + LDAIAgentConfig, + LDAICompletionConfig, + LDAIJudgeConfig, +} from '../config/types'; import { AgentGraphDefinition } from '../graph/AgentGraphDefinition'; import { AIProvider, ToolRegistry } from './AIProvider'; import { AgentGraphRunner, Runner } from './Runner'; @@ -29,21 +33,16 @@ export type SupportedAIProvider = (typeof SUPPORTED_AI_PROVIDERS)[number]; * via {@link _getProviderFactory}, and delegates creation to the factory * instance methods on {@link AIProvider}. * - * Provider packages implement {@link AIProvider} factory methods - * (`createModel`, `createAgent`, `createAgentGraph`). The legacy - * {@link AIProvider} abstract class is retained for backward compatibility, - * and the {@link _LegacyProviderAdapter} shim wraps packages that have not - * yet migrated to the new pattern. + * Provider packages subclass {@link AIProvider} and override its factory + * methods (`createModel`, `createAgent`, `createAgentGraph`). */ export class RunnerFactory { /** * Load and return the AIProvider factory for the given provider type. * * This is the single place in the codebase that knows provider package names. - * If the provider package exports the new `*RunnerFactory` class, it is - * instantiated directly. Otherwise a {@link _LegacyProviderAdapter} wrapping - * the old `static create()` class is returned to keep CI green during the - * transition. + * Each supported provider package exports a `*RunnerFactory` class that + * extends {@link AIProvider}; this method instantiates it directly. * * @param providerType One of the {@link SUPPORTED_AI_PROVIDERS} values. * @param logger Optional logger forwarded to the provider factory. @@ -163,7 +162,7 @@ export class RunnerFactory { * `undefined` if no suitable provider could be loaded. */ static async createModel( - config: LDAIConfigKind, + config: LDAICompletionConfig | LDAIJudgeConfig, logger?: LDLogger, defaultAiProvider?: SupportedAIProvider, ): Promise { @@ -200,7 +199,7 @@ export class RunnerFactory { * provider could be loaded. */ static async createAgent( - config: LDAIConfigKind, + config: LDAIAgentConfig, tools?: ToolRegistry, logger?: LDLogger, defaultAiProvider?: SupportedAIProvider, From 62eb4b2c88c55eeb55e50e75db159e753e9b23d5 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 7 May 2026 10:06:47 -0500 Subject: [PATCH 2/6] keep logger in the ai provider --- .../server-ai-langchain/src/LangChainRunnerFactory.ts | 5 +---- .../server-ai-openai/src/OpenAIRunnerFactory.ts | 4 +--- .../server-ai-vercel/src/VercelRunnerFactory.ts | 5 +---- packages/sdk/server-ai/src/api/providers/AIProvider.ts | 7 +++++++ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts b/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts index 90cbd1d38a..f00cfb0e14 100644 --- a/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts +++ b/packages/ai-providers/server-ai-langchain/src/LangChainRunnerFactory.ts @@ -18,11 +18,8 @@ let instrumentPromise: Promise | undefined; * Factory for creating LangChain runners (chat completion and agent). */ export class LangChainRunnerFactory extends AIProvider { - private _logger?: LDLogger; - constructor(logger?: LDLogger) { - super(); - this._logger = logger; + super(logger); // eslint-disable-next-line no-underscore-dangle LangChainRunnerFactory._ensureInstrumented(logger).catch(() => {}); } diff --git a/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts b/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts index b9c2cb215b..9f99f7d798 100644 --- a/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts +++ b/packages/ai-providers/server-ai-openai/src/OpenAIRunnerFactory.ts @@ -17,11 +17,9 @@ let instrumentPromise: Promise | undefined; */ export class OpenAIRunnerFactory extends AIProvider { private _client: OpenAI; - private _logger?: LDLogger; constructor(logger?: LDLogger) { - super(); - this._logger = logger; + super(logger); this._client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // Fire-and-forget: OTel instrumentation is optional and must not block construction. // eslint-disable-next-line no-underscore-dangle diff --git a/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts b/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts index 37a9bd8fe3..9869301314 100644 --- a/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts +++ b/packages/ai-providers/server-ai-vercel/src/VercelRunnerFactory.ts @@ -15,11 +15,8 @@ import { VercelModelRunner } from './VercelModelRunner'; * framework. */ export class VercelRunnerFactory extends AIProvider { - private _logger?: LDLogger; - constructor(logger?: LDLogger) { - super(); - this._logger = logger; + super(logger); } /** diff --git a/packages/sdk/server-ai/src/api/providers/AIProvider.ts b/packages/sdk/server-ai/src/api/providers/AIProvider.ts index d8ae79f840..a6f5cd9974 100644 --- a/packages/sdk/server-ai/src/api/providers/AIProvider.ts +++ b/packages/sdk/server-ai/src/api/providers/AIProvider.ts @@ -1,3 +1,5 @@ +import { LDLogger } from '@launchdarkly/js-server-sdk-common'; + import { LDAIAgentConfig, LDAICompletionConfig, LDAIJudgeConfig } from '../config/types'; import { AgentGraphDefinition } from '../graph/AgentGraphDefinition'; import { AgentGraphRunner, Runner } from './Runner'; @@ -23,6 +25,11 @@ export type ToolRegistry = Record unknown>; * actually support. */ export abstract class AIProvider { + protected _logger?: LDLogger; + + constructor(logger?: LDLogger) { + this._logger = logger; + } /** * Create a Runner for a completion or judge AI Config. * From d224c70ff49a4e2ecf2b634463777c9a11804ff7 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 7 May 2026 10:12:49 -0500 Subject: [PATCH 3/6] chore: silence lint for protected _logger naming convention The naming-convention rule disallows leading underscores on class properties; subclasses already reference `this._logger` so renaming would cascade. Adding a targeted disable comment is the smaller fix. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/sdk/server-ai/src/api/providers/AIProvider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk/server-ai/src/api/providers/AIProvider.ts b/packages/sdk/server-ai/src/api/providers/AIProvider.ts index a6f5cd9974..bba6603049 100644 --- a/packages/sdk/server-ai/src/api/providers/AIProvider.ts +++ b/packages/sdk/server-ai/src/api/providers/AIProvider.ts @@ -25,6 +25,7 @@ export type ToolRegistry = Record unknown>; * actually support. */ export abstract class AIProvider { + // eslint-disable-next-line @typescript-eslint/naming-convention protected _logger?: LDLogger; constructor(logger?: LDLogger) { From 5bcdfc0dbb7f289038b5fbe0b4a3594aee0dda16 Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 7 May 2026 10:12:59 -0500 Subject: [PATCH 4/6] revert: restore LDAIClient deprecated method aliases These aliases (`config`, `agent`, `agents`, `createChat`, `initChat`) exist independently of the AIProvider cleanup and should follow their own deprecation cycle. Restored verbatim from the pre-cleanup state. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/sdk/server-ai/src/LDAIClientImpl.ts | 60 ++++++++++++++++++++ packages/sdk/server-ai/src/api/LDAIClient.ts | 50 ++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/packages/sdk/server-ai/src/LDAIClientImpl.ts b/packages/sdk/server-ai/src/LDAIClientImpl.ts index 7192be0ad9..8ff4c59769 100644 --- a/packages/sdk/server-ai/src/LDAIClientImpl.ts +++ b/packages/sdk/server-ai/src/LDAIClientImpl.ts @@ -221,6 +221,18 @@ export class LDAIClientImpl implements LDAIClient { ); } + /** + * @deprecated Use `completionConfig` instead. This method will be removed in a future version. + */ + async config( + key: string, + context: LDContext, + defaultValue?: LDAICompletionConfigDefault, + variables?: Record, + ): Promise { + return this.completionConfig(key, context, defaultValue, variables); + } + private async _judgeConfig( key: string, context: LDContext, @@ -278,6 +290,18 @@ export class LDAIClientImpl implements LDAIClient { ); } + /** + * @deprecated Use `agentConfig` instead. This method will be removed in a future version. + */ + async agent( + key: string, + context: LDContext, + defaultValue?: LDAIAgentConfigDefault, + variables?: Record, + ): Promise { + return this.agentConfig(key, context, defaultValue, variables); + } + async agentConfigs( agentConfigs: T, context: LDContext, @@ -306,6 +330,16 @@ export class LDAIClientImpl implements LDAIClient { return agents; } + /** + * @deprecated Use `agentConfigs` instead. This method will be removed in a future version. + */ + async agents( + agentConfigs: T, + context: LDContext, + ): Promise> { + return this.agentConfigs(agentConfigs, context); + } + async createJudge( key: string, context: LDContext, @@ -436,6 +470,32 @@ export class LDAIClientImpl implements LDAIClient { return new ManagedAgent(config, runner, this._logger); } + /** + * @deprecated Use `createModel` instead. This method will be removed in a future version. + */ + async createChat( + key: string, + context: LDContext, + defaultValue?: LDAICompletionConfigDefault, + variables?: Record, + defaultAiProvider?: SupportedAIProvider, + ): Promise { + return this.createModel(key, context, defaultValue, variables, defaultAiProvider); + } + + /** + * @deprecated Use `createModel` instead. This method will be removed in a future version. + */ + async initChat( + key: string, + context: LDContext, + defaultValue?: LDAICompletionConfigDefault, + variables?: Record, + defaultAiProvider?: SupportedAIProvider, + ): Promise { + return this.createModel(key, context, defaultValue, variables, defaultAiProvider); + } + createTracker(token: string, context: LDContext): LDAIConfigTracker { return LDAIConfigTrackerImpl.fromResumptionToken(token, this._ldClient, context); } diff --git a/packages/sdk/server-ai/src/api/LDAIClient.ts b/packages/sdk/server-ai/src/api/LDAIClient.ts index 2b4935246f..749a739320 100644 --- a/packages/sdk/server-ai/src/api/LDAIClient.ts +++ b/packages/sdk/server-ai/src/api/LDAIClient.ts @@ -81,6 +81,16 @@ export interface LDAIClient { defaultAiProvider?: SupportedAIProvider, ): Promise; + /** + * @deprecated Use `completionConfig` instead. This method will be removed in a future version. + */ + config( + key: string, + context: LDContext, + defaultValue?: LDAICompletionConfigDefault, + variables?: Record, + ): Promise; + /** * Retrieves and processes a single AI Config agent based on the provided key, LaunchDarkly context, * and variables. This includes the model configuration and the customized instructions. @@ -123,6 +133,16 @@ export interface LDAIClient { defaultAiProvider?: SupportedAIProvider, ): Promise; + /** + * @deprecated Use `agentConfig` instead. This method will be removed in a future version. + */ + agent( + key: string, + context: LDContext, + defaultValue?: LDAIAgentConfigDefault, + variables?: Record, + ): Promise; + /** * Retrieves and processes a Judge AI Config based on the provided key, LaunchDarkly context, * and variables. This includes the model configuration and the customized messages for evaluation. @@ -208,6 +228,14 @@ export interface LDAIClient { context: LDContext, ): Promise>; + /** + * @deprecated Use `agentConfigs` instead. This method will be removed in a future version. + */ + agents( + agentConfigs: T, + context: LDContext, + ): Promise>; + /** * Creates and returns a new ManagedModel instance for LLM model interactions. * @@ -269,6 +297,28 @@ export interface LDAIClient { defaultAiProvider?: SupportedAIProvider, ): Promise; + /** + * @deprecated Use `createModel` instead. This method will be removed in a future version. + */ + createChat( + key: string, + context: LDContext, + defaultValue?: LDAICompletionConfigDefault, + variables?: Record, + defaultAiProvider?: SupportedAIProvider, + ): Promise; + + /** + * @deprecated Use `createModel` instead. This method will be removed in a future version. + */ + initChat( + key: string, + context: LDContext, + defaultValue?: LDAICompletionConfigDefault, + variables?: Record, + defaultAiProvider?: SupportedAIProvider, + ): Promise; + /** * Creates and returns a new Judge instance for AI evaluation. * From bf2390cbd9445afa9bc9ef80c50f486fc19065ab Mon Sep 17 00:00:00 2001 From: Jason Bailey Date: Thu, 7 May 2026 11:08:23 -0500 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: joker23 <2494686+joker23@users.noreply.github.com> --- packages/sdk/server-ai/src/api/providers/AIProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk/server-ai/src/api/providers/AIProvider.ts b/packages/sdk/server-ai/src/api/providers/AIProvider.ts index bba6603049..558dbffa45 100644 --- a/packages/sdk/server-ai/src/api/providers/AIProvider.ts +++ b/packages/sdk/server-ai/src/api/providers/AIProvider.ts @@ -42,7 +42,7 @@ export abstract class AIProvider { * provider does not support model creation. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - async createModel(config: LDAICompletionConfig | LDAIJudgeConfig): Promise { + async createModel(_config: LDAICompletionConfig | LDAIJudgeConfig): Promise { return undefined; } @@ -58,7 +58,7 @@ export abstract class AIProvider { * provider does not support agent creation. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - async createAgent(config: LDAIAgentConfig, tools?: ToolRegistry): Promise { + async createAgent(_config: LDAIAgentConfig, _tools?: ToolRegistry): Promise { return undefined; } From afde51107f62f8b6f14ae954f0821a7bb43f0b5b Mon Sep 17 00:00:00 2001 From: jsonbailey Date: Thu, 7 May 2026 11:13:56 -0500 Subject: [PATCH 6/6] additional codereview feedback --- packages/sdk/server-ai/src/api/providers/AIProvider.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/sdk/server-ai/src/api/providers/AIProvider.ts b/packages/sdk/server-ai/src/api/providers/AIProvider.ts index 558dbffa45..c795f36d35 100644 --- a/packages/sdk/server-ai/src/api/providers/AIProvider.ts +++ b/packages/sdk/server-ai/src/api/providers/AIProvider.ts @@ -41,7 +41,6 @@ export abstract class AIProvider { * @returns Promise resolving to a {@link Runner}, or `undefined` if this * provider does not support model creation. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars async createModel(_config: LDAICompletionConfig | LDAIJudgeConfig): Promise { return undefined; } @@ -57,7 +56,6 @@ export abstract class AIProvider { * @returns Promise resolving to a {@link Runner}, or `undefined` if this * provider does not support agent creation. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars async createAgent(_config: LDAIAgentConfig, _tools?: ToolRegistry): Promise { return undefined; } @@ -74,10 +72,8 @@ export abstract class AIProvider { * this provider does not support graph execution. */ async createAgentGraph( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - graphDef: AgentGraphDefinition, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - tools?: ToolRegistry, + _graphDef: AgentGraphDefinition, + _tools?: ToolRegistry, ): Promise { return undefined; }