Skip to content

feat(openrouter): add image generation provider#67668

Merged
steipete merged 1 commit intoopenclaw:mainfrom
notamicrodose:feat/openrouter-image-gen
Apr 24, 2026
Merged

feat(openrouter): add image generation provider#67668
steipete merged 1 commit intoopenclaw:mainfrom
notamicrodose:feat/openrouter-image-gen

Conversation

@notamicrodose
Copy link
Copy Markdown
Contributor

@notamicrodose notamicrodose commented Apr 16, 2026

Summary

  • Adds image generation support to the OpenRouter extension via the OpenAI-compatible /chat/completions endpoint (modalities: ["image", "text"])
  • Implements a native image generation provider following existing provider patterns (OpenAI, Google, fal, minimax, vydra)
  • Supports generate + edit modes, multiple aspect ratios (1:1 through 21:9), and resolution hints (1K/2K/4K) via image_config
  • Handles OpenRouter's non-standard image response format (message.images[].image_url.url) with robust fallbacks for other response structures
  • Ships with Gemini image models as defaults; the provider is model-agnostic and supports any image-capable model routed through OpenRouter
  • Production-tested end-to-end on a live OpenClaw instance (running in docker, hosted on ClawBob)

Change Type

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Root Cause

  • OpenRouter does not expose image generation as a native provider in OpenClaw, despite supporting image outputs via its chat completions API
  • Its response format differs from other providers (images returned outside message.content), requiring custom parsing logic

User-visible / Behavior Changes

  • Users can now generate and edit images using OpenRouter-backed models through the image_generate tool
  • OpenRouter becomes a first-class image generation provider alongside existing native providers
  • No additional provider keys required if OpenRouter is already configured

Issues

Resolves: #55066, #50485, #55277, #55011

Repro + Verification

Environment

  • OS: Linux (Dockerized OpenClaw instance)
  • Runtime/container: OpenClaw (Coolify deployment)
  • Model/provider: openrouter/google/gemini-3-pro-image-preview
  • Integration/channel (if any): OpenRouter extension

Verified

  • Contract: plugin registers imageGenerationProviderIds and satisfies requireGenerateImage
  • Live: image-generation.runtime.live.test.ts passes for OpenRouter (generate + edit)
  • Manual: confirmed image_generate tool produces valid images with correct formats and configurations

Build Note

  • No new build issues introduced
  • Uses existing SDK HTTP helpers (postJsonRequest, assertOkOrThrowHttpError) consistent with other providers

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0037e8900

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/openrouter/image-generation-provider.ts Outdated
Comment thread extensions/openrouter/image-generation-provider.ts Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

Adds an image generation provider to the bundled OpenRouter extension, routing to Gemini image models via OpenRouter's OpenAI-compatible chat completions endpoint. The implementation closely follows the established provider pattern (assertOkOrThrowHttpError, release in finally, pinDns: false) and correctly wires into the plugin manifest, api.ts, test-api.ts, and live test infrastructure.

  • req.count not forwarded: The capabilities declare maxCount: 4 for both generate and edit, but no n parameter is sent to the API. Callers requesting multiple images will silently receive only one. Every other provider that declares maxCount > 1 forwards req.count. Either pass n: req.count ?? 1 to the request body, or reduce maxCount to 1 if OpenRouter's image path does not support n > 1.
  • req.timeoutMs ignored: The provider hardcodes 90_000 ms and discards req.timeoutMs; five other image providers forward it. Using req.timeoutMs ?? 90_000 would make behavior consistent.

Confidence Score: 4/5

Safe to merge after fixing the count/n mismatch; other findings are minor.

One clear P1 functional gap: maxCount: 4 is declared but req.count is never forwarded to the API, so multi-image requests silently return one image. The P2 findings (ignored timeoutMs, dead b64_json fallback) do not block correctness.

extensions/openrouter/image-generation-provider.ts — the generateImage request body is missing req.count forwarding.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/openrouter/image-generation-provider.ts
Line: 219-229

Comment:
**`req.count` not forwarded — `maxCount: 4` is unreachable**

`ImageGenerationRequest` carries a `count` field, and the capabilities declare `maxCount: 4`, but the request body never includes `n`. Every `count > 1` request silently returns a single image. Every other provider that declares `maxCount > 1` (fal, openai, vydra, minimax, comfy) passes `req.count` (or `n: req.count ?? 1`) to the downstream API.

```suggestion
        body: {
          model,
          messages: [{ role: "user", content: contentParts }],
          modalities: ["image", "text"],
          max_tokens: 4096,
          ...(req.count && req.count > 1 ? { n: req.count } : {}),
          ...(Object.keys(imageConfig).length > 0 ? { image_config: imageConfig } : {}),
        },
```

If OpenRouter's chat-completions image path does not support `n > 1`, set `maxCount: 1` (and remove `maxCount` from the edit capability too) so callers don't assume multiple images are supported.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/openrouter/image-generation-provider.ts
Line: 212-229

Comment:
**`req.timeoutMs` ignored — hardcoded to 90 s**

`ImageGenerationRequest.timeoutMs` is part of the public interface. Five other image-generation providers (fal, openai, vydra, minimax, comfy) forward it to `postJsonRequest`; this one hardcodes 90 000 ms and silently discards the caller value. Consider using `req.timeoutMs ?? 90_000` so callers can apply shorter timeouts (e.g. in tests or when orchestrating parallel requests).

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/openrouter/image-generation-provider.ts
Line: 105-128

Comment:
**`b64_json` fallback checks the wrong object**

Inside the `type === "image_url"` branch, `iu` is `p.image_url ?? p.imageUrl`. The code then checks `iu?.b64_json`, but `b64_json` is not a standard sub-field of an `image_url` object — it would normally appear directly on the part (`p.b64_json`) or in a `b64_json`-typed part. The check at line 119 is effectively unreachable, so the fallback offers no real coverage. If you want to handle raw `b64_json` parts, check `p.b64_json` directly (outside the `type === "image_url"` block).

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(openrouter): add image generation p..." | Re-trigger Greptile

Comment thread extensions/openrouter/image-generation-provider.ts
Comment thread extensions/openrouter/image-generation-provider.ts Outdated
Comment thread extensions/openrouter/image-generation-provider.ts Outdated
@notamicrodose notamicrodose force-pushed the feat/openrouter-image-gen branch from d0037e8 to 20ac963 Compare April 16, 2026 12:55
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20ac96343d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/openrouter/image-generation-provider.ts Outdated
@notamicrodose notamicrodose force-pushed the feat/openrouter-image-gen branch 3 times, most recently from 9116e34 to f408066 Compare April 16, 2026 13:11
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f408066738

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

model,
n: count,
messages: [{ role: "user", content: contentParts }],
modalities: ["image", "text"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use image-only modalities for image-only OpenRouter models

The request body always sends modalities: ["image", "text"], but OpenRouter’s image-generation contract distinguishes image+text models from image-only models. For image-only models (for example Flux/Sourceful variants), this payload can be rejected or produce unreliable behavior because text output is requested when the model only supports image output. Since this provider is intended to be model-agnostic, the modalities should be derived from the selected model’s output capabilities (or at least allow an image-only path) instead of being hardcoded.

Useful? React with 👍 / 👎.

@steipete
Copy link
Copy Markdown
Contributor

Codex review: still useful as the likely implementation path for #55066, but not mergeable as-is. The current diff fixed the earlier n, timeout, and raw b64_json concerns; it still needs a rebase onto current main, current CI cleanup, and a focused OpenRouter image-generation shard/check before we can merge.

@steipete steipete force-pushed the feat/openrouter-image-gen branch from f408066 to ff6f24e Compare April 24, 2026 00:29
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation size: L and removed size: M labels Apr 24, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff6f24efac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +259 to +261
resolveProviderHttpRequestConfig({
baseUrl: req.cfg?.models?.providers?.openrouter?.baseUrl,
defaultBaseUrl: OPENROUTER_BASE_URL,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass configured OpenRouter request overrides to image calls

This request setup only forwards baseUrl and default headers, but never passes req.cfg?.models?.providers?.openrouter?.request into resolveProviderHttpRequestConfig. As a result, image generation ignores configured transport/auth overrides (for example proxy/TLS/private-network/header settings), so environments that depend on provider-level request policy can have normal OpenRouter chat traffic succeed while image_generate fails. Include the sanitized request overrides in this config call to keep behavior consistent with provider request settings.

Useful? React with 👍 / 👎.

@steipete steipete merged commit 0f026ad into openclaw:main Apr 24, 2026
68 of 69 checks passed
@steipete
Copy link
Copy Markdown
Contributor

Landed via squash merge onto main.

  • Local gate: pnpm check:changed typecheck/lint/guards passed; parallel changed test sweep hit local SIGTERM 143 in broad Vitest runner, then OPENCLAW_TEST_PROJECTS_PARALLEL=1 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=180000 pnpm test --changed origin/main passed.
  • Targeted/live: OpenRouter provider tests passed; live openrouter:generate returned 1 image with google/gemini-3.1-flash-image-preview.
  • Source commit: ff6f24e
  • Merge commit: 0f026ad

Thanks @notamicrodose!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

image_generate: Add OpenRouter provider support for image generation models

2 participants