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
Servicewithout changingAPIKey. One key property serves whichever service is selected, so the new provider is authenticated with the old key and rejects the request. - Setting
ModelorCustomOptionsbeforeService. AssigningServicerebuilds the provider implementation. Set the service first. - Carrying
CustomOptionsacross a service switch. Options are the least portable part of the component. Clear the string when the service changes. - Wrapping
CustomOptionsin 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
OnRequestStartedas a second image. On a polling service the request events fire for every poll. - Leaving
Loggingon in a shipped build. Every logged response carries a full base64 image.
See also
- Generating images — the four calls and what they return
- Getting started — the first request end to end
- Cloud AI — per-service keys, models, and generation settings for text
TTMSMCPCloudImageAI,TTMSMCPCloudImageAIService,TTMSMCPCloudBase