How the Image Model Adapter Registry Manages Multiple Image Generation Services in Prompt-Optimizer
The image model adapter registry in the linshenkx/prompt-optimizer repository provides a unified abstraction layer that discovers, registers, and orchestrates multiple image generation services—including Gemini, OpenAI, SiliconFlow, and Ollama—behind a consistent TypeScript interface with centralized error handling and support for both static and dynamic model discovery.
The image model adapter registry serves as the central nervous system for the prompt-optimizer project's image generation capabilities. Located in the packages/core/src/services/image/adapters/ directory, this architecture allows the application to treat heterogeneous services—from cloud providers like OpenAI and Gemini to local deployments like Ollama—as interchangeable components through a common IImageProviderAdapter interface.
Core Architecture of the Image Model Adapter Registry
The registry implementation separates concerns between image-specific logic and shared adapter management functionality. This design enables consistent behavior across both image and LLM adapter registries while allowing domain-specific error handling.
ImageAdapterRegistry and AbstractAdapterRegistry
The concrete implementation resides in packages/core/src/services/image/adapters/registry.ts, where the ImageAdapterRegistry class extends AbstractAdapterRegistry<IImageProviderAdapter> from packages/core/src/services/adapters/abstract-registry.ts. This inheritance provides shared functionality for caching, provider discovery, and model metadata management while the concrete implementation handles image-specific errors through the ImageError class defined in packages/core/src/services/image/errors.ts.
The abstract base manages an internal Map<string, IImageProviderAdapter> that stores all registered adapters using lowercase provider keys for case-insensitive lookups.
Provider-Specific Adapters
Each supported service implements the IImageProviderAdapter interface. For example, packages/core/src/services/image/adapters/gemini.ts contains the GeminiImageAdapter class, while packages/core/src/services/image/adapters/openai.ts implements OpenAIImageAdapter. These adapters encapsulate provider-specific logic for:
- Exposing provider metadata via
getProvider() - Returning static model lists via
getModels() - Optionally supporting dynamic model fetching via
getModelsAsync(config) - Declaring
supportsDynamicModelsboolean flags
Registry Initialization and Adapter Registration
When instantiated, the registry automatically discovers and registers all available adapters through the initializeAdapters() method. This process occurs in the constructor of the abstract base class, which invokes the concrete implementation:
// packages/core/src/services/adapters/abstract-registry.ts
constructor() {
this.initializeAdapters(); // Automatically invoked
}
The concrete ImageAdapterRegistry implements this method to instantiate each adapter and store it in an internal Map using lowercase provider keys:
// packages/core/src/services/image/adapters/registry.ts
protected initializeAdapters(): void {
const geminiAdapter = new GeminiImageAdapter();
const openaiAdapter = new OpenAIImageAdapter();
const ollamaAdapter = new OllamaImageAdapter();
// ... additional adapters for Seedream, SiliconFlow, OpenRouter, DashScope, ModelScope
this.adapters.set('gemini', geminiAdapter);
this.adapters.set('openai', openaiAdapter);
this.adapters.set('ollama', ollamaAdapter);
// Pre-load static models for fast look-ups
this.preloadStaticModels();
}
Unified API for Provider and Model Discovery
The image model adapter registry exposes a consistent API for querying providers and their models, abstracting whether the underlying service supports dynamic model listing or only static definitions.
Key Registry Methods
The following methods in packages/core/src/services/adapters/abstract-registry.ts provide the primary interface:
getAdapter(providerId: string): Retrieves the concrete adapter instance or throws anImageErrorwith codePROVIDER_NOT_FOUND.getAllProviders(): Aggregates metadata from all registered adapters into an array ofImageProviderobjects.getStaticModels(providerId: string): Returns cached static models or loads them lazily from the adapter'sgetModels()method.getModels(providerId: string, connectionConfig?): Unified entry point that prefers dynamic fetching (viagetModelsAsync()) whensupportsDynamicModelsis true and configuration is provided; otherwise falls back to static models.validateProviderModel(providerId, modelId): Confirms that a specific model ID exists within the specified provider's catalog.
Static vs Dynamic Model Retrieval
Static models are hard-coded arrays defined within each adapter (e.g., the list of available Gemini models in gemini.ts). The registry caches these during initialization via preloadStaticModels() for fast synchronous access.
Dynamic models require authenticated API calls. When getModels() is invoked with a connectionConfig object containing API keys and endpoints, the registry checks supportsDynamicModels() and calls adapter.getModelsAsync(connectionConfig) if available. Failures are caught and logged, with automatic graceful fallback to the cached static list.
// Example: Fetching dynamic models with fallback
const connectionConfig = {
apiKey: process.env.OPENAI_API_KEY,
endpoint: 'https://api.openai.com/v1'
};
try {
const models = await imageRegistry.getModels('openai', connectionConfig);
console.log('Dynamic models:', models.map(m => m.id));
} catch (error) {
// Registry automatically falls back to static models on failure
const staticModels = imageRegistry.getStaticModels('openai');
console.log('Static fallback:', staticModels.map(m => m.id));
}
Error Handling and Validation
The registry centralizes error handling through the ImageError class defined in packages/core/src/services/image/errors.ts. When operations fail, the registry generates specific error codes:
PROVIDER_NOT_FOUND: Returned whengetAdapter()is called with an unregistered provider ID.DYNAMIC_MODELS_NOT_SUPPORTED: Returned when dynamic model fetching is requested for a provider that only supports static lists.
The concrete registry implements createUnknownProviderError() to wrap these codes in ImageError instances:
// packages/core/src/services/image/adapters/registry.ts
protected createUnknownProviderError(providerId: string): Error {
return new ImageError(
IMAGE_ERROR_CODES.PROVIDER_NOT_FOUND,
undefined,
{ providerId }
);
}
Extending the Registry with New Providers
Adding support for a new image generation service requires no modifications to the core registry logic. The architecture supports extension through three steps:
-
Create the adapter class implementing
IImageProviderAdapterin a new file (e.g.,packages/core/src/services/image/adapters/mynewservice.ts). The class must provide:getProvider()returningImageProvidermetadatagetModels()returning staticImageModel[]- Optional
getModelsAsync(config)if dynamic fetching is supported supportsDynamicModelsboolean flag
-
Register the adapter in
packages/core/src/services/image/adapters/registry.tsby importing the class and adding it toinitializeAdapters():
import { MyNewServiceImageAdapter } from './mynewservice';
protected initializeAdapters(): void {
// ... existing adapters ...
const myNewAdapter = new MyNewServiceImageAdapter();
this.adapters.set('mynewservice', myNewAdapter);
this.preloadStaticModels();
}
- Export the adapter (if needed) and ensure it follows the naming convention. The registry automatically handles the rest, including static model caching and error mapping.
Summary
- The image model adapter registry in
linshenkx/prompt-optimizerprovides a unified abstraction over heterogeneous image generation services including Gemini, OpenAI, Ollama, SiliconFlow, Seedream, OpenRouter, DashScope, and ModelScope. - The architecture separates concerns between the concrete
ImageAdapterRegistry(handling image-specific errors) and the sharedAbstractAdapterRegistry(managing caching, discovery, and model retrieval logic). - Adapters implement
IImageProviderAdapterto expose provider metadata, static model lists, and optional dynamic model fetching viagetModelsAsync(). - The registry automatically pre-loads static models during initialization for performance and gracefully falls back from dynamic to static model lists when API calls fail or are unsupported.
- Adding new providers requires only implementing the adapter interface and registering the instance in
initializeAdapters(), with zero changes needed to the core registry logic.
Frequently Asked Questions
What is the difference between static and dynamic model lists in the image model adapter registry?
Static model lists are hard-coded arrays defined within each adapter (such as GeminiImageAdapter or OpenAIImageAdapter) that describe the models a service offers without requiring API authentication. Dynamic model lists are fetched at runtime via the adapter's optional getModelsAsync() method, which requires a connectionConfig object containing API keys and endpoints. The registry prefers dynamic lists when available and configured, but automatically falls back to static lists if the dynamic request fails or is unsupported.
How does the registry handle unsupported or unknown image providers?
When a caller requests an adapter for an unregistered provider ID via getAdapter(), the registry invokes createUnknownProviderError() to generate an ImageError with the code PROVIDER_NOT_FOUND. This error includes the requested provider ID in its metadata, allowing applications to display meaningful feedback to users. Similarly, attempting to fetch dynamic models from a provider that only supports static lists returns an error with code DYNAMIC_MODELS_NOT_SUPPORTED.
Can I add support for a custom image generation API without modifying the core registry code?
Yes, extending the registry to support a new image generation service requires no changes to the abstract or concrete registry classes. You only need to create a new adapter class implementing the IImageProviderAdapter interface in a file such as packages/core/src/services/image/adapters/custom.ts, then import and register that adapter in the initializeAdapters() method of packages/core/src/services/image/adapters/registry.ts. The registry automatically handles caching, error mapping, and API unification for the new provider.
What performance optimizations does the registry implement for model lookups?
The registry optimizes model lookups by pre-loading static model lists during initialization via the preloadStaticModels() method, which caches all static models in memory to avoid repeated disk access or computation. For dynamic model fetching, the registry implements a fallback mechanism that returns cached static models immediately if the asynchronous API call fails, ensuring that the application remains responsive even when external services are unavailable. All provider lookups use a Map<string, IImageProviderAdapter> with lowercase keys for O(1) case-insensitive access.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →