llm-connect/contracts/functional/images.md
tegwick e1ca67e509
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 7s
Resolve the OpenRouter key from OpenBao before the environment
auto tries platform/suppliers/openrouter/llm-connect, then the live
activity-core workload lane, then OPENROUTER_API_KEY. A denied token
falls back to the environment. LLM_CONNECT_KEY_SOURCE selects env or
openbao alone.

Assistant: grok
Assistant-Session: 01a11ab6-1534-7d63-8606-662b92a80ed6
2026-10-08 19:35:00 +02:00

3.9 KiB

Text-to-image rendering

llm_connect.images renders one square image from a prompt. It does not choose a model or a budget. The caller passes both. Credentials stay in the provider client and never appear on a receipt.

Request

  • Prompt: non-empty, at most 16 000 characters.
  • Size: WIDTHxHEIGHT, square, edge from 1024 through 4096. Default 1024x1024.
  • Format: png or jpeg. A file suffix .png, .jpg, or .jpeg must match the format.
  • An existing output file is left untouched unless force is set.

Receipt

Each successful render returns image bytes and a receipt:

  • provider, model
  • prompt_digest — SHA-256 of the UTF-8 prompt. The prompt text is not stored.
  • cost_usd, cost_eur, cost_source (provider, quote, or unpriced)
  • width, height, media_type, output_format, byte_length
  • output_path when a file was written
  • within_cap — false when the provider cost exceeded a cap that was checked before the call. The file is still written, because the provider already charged it, and the batch stops.

Spend gate

ImageSpendPolicy takes max_cost_usd, max_batch_cost_usd, and quote_usd. A cap requires a quote. The quote is reserved before the provider call. The provider-reported usage.cost replaces it on the receipt when present. A failed HTTP call releases the reservation. Parallel /images requests share one gate for the life of the server process.

Provider

OpenRouter only, via POST {api_base}/images (default https://openrouter.ai/api/v1/images). The request sends n=1, aspect_ratio=1:1, an explicit pixel size, and output_format. The key comes from the explicit argument, OpenBao (platform/suppliers/openrouter/llm-connect, then the live workload lane platform/workloads/activity-core/llm-connect/llm-connect-provider-secrets), OPENROUTER_API_KEY, or apikey-openrouter.txt. Set LLM_CONNECT_KEY_SOURCE=env to skip OpenBao, or openbao to use it alone. Only b64_json is accepted. A URL in the response is not fetched.

There is no default image model on image render. image render-hall renders one named entry, the seat about to be published, using the 2026-10-08 decision tree in docs/image-model-selection.md. It does not walk the corpus. A hall portrait that does not ask for vector output, editing, readable text, or a brand system uses google/gemini-nano-banana-2.1 with quote 0.04 USD and a per-call cap of 0.08 USD.

image plan without --entry only inventories missing portraits. That inventory is not a render queue. Existing seats are historic when a later publisher drips them, and carry a historic remark on the post. Seats written after that practice starts wait a short cooldown, then publish; the portrait is rendered at that moment if the file is still absent. The cooldown scheduler is not part of this library.

CLI

llm-connect image render \
  --prompt "..." \
  --model "<image-model>" \
  --output visuals/name.jpg \
  --max-cost-usd 0.15 \
  --max-batch-cost-usd 15 \
  --quote-usd 0.08

llm-connect image render --batch renders.jsonl --model "<image-model>" \
  --max-cost-usd 0.15 --max-batch-cost-usd 15 --quote-usd 0.08

A batch file is JSONL. Each object has prompt and output_path. The receipt JSON goes to stdout. Image bytes stay in the output file. Exit 2 means a cap stopped the batch after any already-paid files were written.

HTTP

POST /images exists when the server is started with --image-model, --image-max-cost-usd, and --image-quote-usd. Without those flags the path is not found. The body is prompt, optional size, and optional output_format. The response is receipt plus image_base64. Caps are the server's, not the caller's. /execute is unchanged.

Usage rows are appended only when LLM_CONNECT_USAGE_LEDGER is set (server) or unless --no-ledger is passed (CLI). The row's tags carry kind=image and the prompt digest.