import { BaseToolHandler, ToolExecutionContext, GenericToolSpec, HandlerContext, ToolArguments, HandlerError } from './base-handler.js';
import { ClientFactory } from './client-factory.js';
/**
* Abstract base class for handlers that need a client with factory-based creation.
* This provides a simpler alternative to SimpleClientHandler when you need
* access to ClientFactory for more complex client creation scenarios.
*
* Use this when:
* - Your client needs ClientFactory for creation (e.g., requires config/credentials)
* - You need a simple single-client handler pattern
*
* Use SimpleClientHandler when:
* - Your client has a simple constructor with no dependencies
* - You want the most minimal boilerplate
*/
export abstract class AbstractClientHandler<TToolName extends string, TClient> extends BaseToolHandler<TToolName> {
protected client: TClient | null = null;
protected clientFactory: ClientFactory;
constructor(context: HandlerContext, subLoggerName: string) {
super(context, subLoggerName);
this.clientFactory = new ClientFactory(context, this.logger);
}
/**
* Create the client for this handler.
* Returns null if the client cannot be created (e.g., missing credentials).
*/
protected abstract createClient(): TClient | null;
/**
* Get the context key used to expose the client in the execution context.
*/
protected abstract getClientContextKey(): string;
/**
* Get a descriptive name for the client (used in logging and error messages).
*/
protected abstract getClientDisplayName(): string;
/**
* Get the error message when the client is not available.
* Override this for custom error messages.
*/
protected getClientRequiredError(): string {
return `${this.getClientDisplayName()} client not initialized`;
}
protected async onInitialize(): Promise<void> {
this.client = this.createClient();
if (this.client) {
this.logger.debug(`${this.getClientDisplayName()} client initialized`);
}
}
protected async onDispose(): Promise<void> {
const client = this.client;
this.client = null;
await this.teardownClient(client);
this.logger.debug(`${this.getClientDisplayName()} client disposed`);
}
protected async createExecutionContext(toolName: string): Promise<ToolExecutionContext> {
if (!this.client) {
throw new HandlerError(
this.getClientRequiredError(),
toolName,
'CLIENT_NOT_INITIALIZED',
{ clientDisplayName: this.getClientDisplayName() },
);
}
return {
handlerContext: this.context,
logger: this.logger,
[this.getClientContextKey()]: this.client,
};
}
/**
* Abstract method to get tool configuration.
* Each concrete handler implements this with their specific config.
*/
protected abstract getToolConfig(): Record<TToolName, GenericToolSpec<ToolArguments, unknown>>;
/**
* Abstract method to get tool name set for O(1) lookup.
* Each concrete handler implements this with their specific tool set.
*/
protected abstract getToolNameSet(): Set<TToolName>;
}
export interface ConfiguredClientHandlerOptions<TToolName extends string, TClient> {
createClient: (clientFactory: ClientFactory) => TClient | null;
clientContextKey: string;
clientDisplayName: string;
clientRequiredError: string;
toolNameSet: Set<TToolName>;
toolConfig: Record<TToolName, GenericToolSpec<ToolArguments, unknown>>;
}
/**
* Config-driven client handler that removes repetitive overrides in concrete handlers.
*/
export class ConfiguredClientHandler<TToolName extends string, TClient>
extends AbstractClientHandler<TToolName, TClient> {
private readonly options: ConfiguredClientHandlerOptions<TToolName, TClient>;
constructor(
context: HandlerContext,
subLoggerName: string,
options: ConfiguredClientHandlerOptions<TToolName, TClient>,
) {
super(context, subLoggerName);
this.options = options;
}
protected createClient(): TClient | null {
return this.options.createClient(this.clientFactory);
}
protected getClientContextKey(): string {
return this.options.clientContextKey;
}
protected getClientDisplayName(): string {
return this.options.clientDisplayName;
}
protected getClientRequiredError(): string {
return this.options.clientRequiredError;
}
protected getToolConfig(): Record<TToolName, GenericToolSpec<ToolArguments, unknown>> {
return this.options.toolConfig;
}
protected getToolNameSet(): Set<TToolName> {
return this.options.toolNameSet;
}
}