Table of Contents

Providers and options

TTMSMCPCloudImageAI presents five image services — OpenAI, Google Gemini, Black Forest Labs, Stability and Reve — behind a single surface, and that surface is deliberately small: a Service, an APIKey, an optional Model, and a CustomOptions string for everything a provider supports that the component does not publish. The trade is that the first three are portable and the last two are not: a model name or an option that is correct for one service is meaningless to the next. This guide covers how selecting a service rebuilds the implementation underneath, which defaults apply when you name no model, how to shape CustomOptions for each provider, why some services answer in several round trips, and how to see the traffic while you are getting the options right.

Choosing a service

Change Service when you want a different provider — a different price point, a model only one vendor offers, or a fallback when one is unavailable. Assignment is not just a flag: it destroys the provider implementation that was in place and creates the one for the new service, so everything provider-shaped should be set after it.

procedure TForm1.SelectService(AService: TTMSMCPCloudImageAIService);
begin
  { Assigning Service tears down the previous provider implementation and
    creates the one for the new service. Do it before anything else. }
  ImageAI.Service := AService;

  { The key belongs to the service, so it has to change with it. One key
    property holds the key of whichever service is currently selected. }
  case AService of
    isOpenAI:    ImageAI.APIKey := Config.ReadString('imageai', 'openai_key', '');
    isGemini:    ImageAI.APIKey := Config.ReadString('imageai', 'gemini_key', '');
    isBFL:       ImageAI.APIKey := Config.ReadString('imageai', 'bfl_key', '');
    isStability: ImageAI.APIKey := Config.ReadString('imageai', 'stability_key', '');
    isReve:      ImageAI.APIKey := Config.ReadString('imageai', 'reve_key', '');
  end;

  { Model and CustomOptions are service-specific too. Clearing them falls
    back to the service default rather than sending the previous service's
    model name to the new endpoint. }
  ImageAI.Model := '';
  ImageAI.CustomOptions := '';
end;

APIKey is a single property holding the key of whichever service is currently selected — unlike Cloud AI, which keeps one key per service in an APIKeys object. So a service switch that does not carry a key switch with it authenticates the new provider with the old provider's key and fails at the service, not in your code.

Value Service Endpoint family
isOpenAI OpenAI api.openai.com
isGemini Google Gemini generativelanguage.googleapis.com
isBFL Black Forest Labs api.bfl.ai
isStability Stability api.stability.ai
isReve Reve api.reve.com

The default is isOpenAI, applied when the component is created.

Naming a model

Model is an optional free-form string sent to the selected service. Leave it empty and each provider substitutes its own default, which is the right choice until you have a reason to pin one:

procedure TForm1.ApplyPreferredModel;
begin
  { Model is a free-form string sent to the selected service. Leaving it
    empty is supported: each provider substitutes its own default. }
  case ImageAI.Service of
    isOpenAI:
      ImageAI.Model := 'gpt-image-1';      { default: dall-e-2 }
    isGemini:
      ImageAI.Model := 'imagen-4.0-generate-001';
                                           { default: gemini-2.5-flash-image }
    isBFL:
      ImageAI.Model := 'flux-pro-1.1';     { default: flux-2-flex }
    isStability:
      ImageAI.Model := 'sd3.5-large';      { default: sd3.5-flash }
    isReve:
      ImageAI.Model := '';                 { default: reve-create@20250915 }
  end;
end;

procedure TForm1.GenerateWithFallback(const APrompt: string);
begin
  { A model name the service does not recognise comes back through
    OnRequestError, not as a compile-time or assignment failure - so keep
    the fallback path visible while you are trying model names. }
  ImageAI.OnRequestError := ImageAIRequestError;
  ImageAI.Execute(APrompt);
end;
Service Default when Model is empty
OpenAI dall-e-2
Gemini gemini-2.5-flash-image
Black Forest Labs flux-2-flex
Stability sd3.5-flash
Reve reve-create@20250915 for a generation, reve-remix@20250915 for a reference-image request

Two model names change more than the model. On Gemini, a name containing imagen switches the component to the Imagen prediction endpoint and its different response shape; on Stability, a name containing sd3 selects the SD3 generation route. Both are handled for you — the point is that the model string is not inert, so a typo can change the endpoint rather than being rejected.

A model the service does not recognise comes back through OnRequestError with the provider's own message. There is no local validation, which makes the error handler the place you will actually try model names.

Service-specific options

CustomOptions is the escape hatch for everything a provider supports that the component does not publish: image size, aspect ratio, quality, output format, seed, and so on. It is a JSON fragment — key-value pairs only, without the surrounding braces:

procedure TForm1.ApplyImageOptions;
begin
  { CustomOptions is the inner body of a JSON object: pairs only, no outer
    braces, no trailing comma. The component wraps it for the service. }
  case ImageAI.Service of
    isOpenAI:
      ImageAI.CustomOptions := '"size": "1536x1024", "quality": "low"';
    isGemini:
      { Gemini options land inside the image configuration block. }
      ImageAI.CustomOptions := '"aspectRatio": "16:9"';
    isBFL:
      ImageAI.CustomOptions := '"width": 1024, "height": 768';
    isStability:
      ImageAI.CustomOptions := '"aspect_ratio": "3:2", "output_format": "png"';
    isReve:
      ImageAI.CustomOptions := '"aspect_ratio": "3:2"';
  end;
end;

procedure TForm1.ClearImageOptions;
begin
  { An option that is valid for one service is usually rejected by the
    next, so clear the string whenever Service changes. }
  ImageAI.CustomOptions := '';
end;

The component wraps the fragment in whatever structure the provider expects. For OpenAI and Black Forest Labs it is merged into the request body; for Gemini it goes inside the image configuration, or inside the Imagen parameters when an Imagen model is selected; for Stability each pair is parsed out and added as a separate form field. That is why the same option string is not portable — and why a malformed fragment (an outer brace, a trailing comma) can surface as a parsing failure rather than as a rejected option.

Clear CustomOptions when Service changes. Options are the most service-specific thing on the component, and carrying one across is a request the new provider will refuse.

Requests that answer in several round trips

Not every service replies with the image. Black Forest Labs accepts the job and returns a reference to poll; Stability does the same for a background replacement, waiting about ten seconds between attempts. The component follows the poll for you: it issues the follow-up requests, waits where the service asks it to, and fires OnImageGenerated exactly once when the job completes.

Three things follow. A polling generation takes noticeably longer than a direct one, so a progress indicator is worth wiring. The inherited request events fire for every poll, so they count HTTP requests rather than images. And the polling runs on a background task that is cancelled when the component is destroyed — so a form closed mid-generation does not leave a request running, but it also does not deliver the image.

Watching the traffic

While you are working out which options a provider accepts, turn the transport log on. Logging and LogFileName are inherited from TTMSMCPCloudBase, and OnRequestLog mirrors every line into the application:

procedure TForm1.EnableImageAILogging;
begin
  { Inherited from the cloud transport base. Logging writes the request and
    response detail of every call - including each poll - to the file. }
  ImageAI.LogFileName := TPath.Combine(FOutputFolder, 'imageai.log');
  ImageAI.Logging := True;

  ImageAI.OnRequestLog := ImageAIRequestLog;
end;

procedure TForm1.ImageAIRequestLog(Sender: TObject;
  const ARequestResult: TTMSMCPCloudBaseRequestResult; AMessage: string);
begin
  memoLog.Lines.Add(AMessage);
end;

procedure TForm1.DisableImageAILogging;
begin
  { A logged response body contains the generated image as base64, so the
    file grows quickly. Turn logging off once the options are settled. }
  ImageAI.Logging := False;
end;

The log contains the request body the component built from Model and CustomOptions, which is the quickest way to see whether an option landed where you expected. It also contains the response body — including the generated image as base64 — so the file grows fast. Turn Logging back off once the options are settled.

Combining service, model, options, and logging

Configuring a provider end to end is a sequence, not a set of independent assignments: the service comes first because it rebuilds the implementation, then the key that belongs to it, then the model and options that only make sense for it, and only then the request.

procedure TForm1.GenerateWideShot(const APrompt: string);
begin
  { 1. Service first: assigning it rebuilds the provider implementation and
       discards whatever the previous one had set up. }
  ImageAI.Service := isStability;
  ImageAI.APIKey := Config.ReadString('imageai', 'stability_key', '');

  { 2. Model, or empty for the provider default. }
  ImageAI.Model := 'sd3.5-large';

  { 3. Service-specific options: JSON pairs without the outer braces. These
       are not portable, so they are set after the service is known. }
  ImageAI.CustomOptions := '"aspect_ratio": "16:9", "output_format": "png"';

  { 4. Logging while the options are still being worked out. }
  ImageAI.LogFileName := TPath.Combine(FOutputFolder, 'imageai.log');
  ImageAI.Logging := True;
  ImageAI.OnRequestLog := ImageAIRequestLog;

  { 5. Both result paths wired before the call. Stability polls for its
       result, so several HTTP requests may run before one of these fires. }
  ImageAI.OnImageGenerated := ImageAIImageGenerated;
  ImageAI.OnRequestError := ImageAIRequestError;

  ImageAI.Images.Clear;
  ImageAI.Execute(APrompt);
end;

Both result events are wired before the call rather than inside it — on a polling service several HTTP requests run before either fires, and a handler assigned after Execute may already be too late.

Common mistakes

  • Changing Service without changing APIKey. One key property serves whichever service is selected, so the new provider is authenticated with the old key and rejects the request.
  • Setting Model or CustomOptions before Service. Assigning Service rebuilds the provider implementation. Set the service first.
  • Carrying CustomOptions across a service switch. Options are the least portable part of the component. Clear the string when the service changes.
  • Wrapping CustomOptions in braces. It is the inner body of a JSON object — pairs only, no outer braces and no trailing comma.
  • Expecting a bad model name to fail locally. It is sent as given and refused by the service, through OnRequestError.
  • Treating a second OnRequestStarted as a second image. On a polling service the request events fire for every poll.
  • Leaving Logging on in a shipped build. Every logged response carries a full base64 image.

See also