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 supportsDynamicModels boolean 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 an ImageError with code PROVIDER_NOT_FOUND.
  • getAllProviders(): Aggregates metadata from all registered adapters into an array of ImageProvider objects.
  • getStaticModels(providerId: string): Returns cached static models or loads them lazily from the adapter's getModels() method.
  • getModels(providerId: string, connectionConfig?): Unified entry point that prefers dynamic fetching (via getModelsAsync()) when supportsDynamicModels is 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 when getAdapter() 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:

  1. Create the adapter class implementing IImageProviderAdapter in a new file (e.g., packages/core/src/services/image/adapters/mynewservice.ts). The class must provide:

    • getProvider() returning ImageProvider metadata
    • getModels() returning static ImageModel[]
    • Optional getModelsAsync(config) if dynamic fetching is supported
    • supportsDynamicModels boolean flag
  2. Register the adapter in packages/core/src/services/image/adapters/registry.ts by importing the class and adding it to initializeAdapters():

import { MyNewServiceImageAdapter } from './mynewservice';

protected initializeAdapters(): void {
  // ... existing adapters ...
  const myNewAdapter = new MyNewServiceImageAdapter();
  this.adapters.set('mynewservice', myNewAdapter);
  this.preloadStaticModels();
}
  1. 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-optimizer provides 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 shared AbstractAdapterRegistry (managing caching, discovery, and model retrieval logic).
  • Adapters implement IImageProviderAdapter to expose provider metadata, static model lists, and optional dynamic model fetching via getModelsAsync().
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →