Skip to content

feat: support OpenRouter image generation endpoint - #5964

Open
Robinnnnn wants to merge 4 commits into
QuantumNous:mainfrom
Robinnnnn:openrouter-image-generation
Open

feat: support OpenRouter image generation endpoint#5964
Robinnnnn wants to merge 4 commits into
QuantumNous:mainfrom
Robinnnnn:openrouter-image-generation

Conversation

@Robinnnnn

@Robinnnnn Robinnnnn commented Jul 7, 2026

Copy link
Copy Markdown

📝 变更描述 / Description

OpenRouter 现已提供图像生成 API,端点为 POST {base}/v1/images官方文档),与 OpenAI 风格的 /v1/images/generations 路径不同。目前 OpenRouter 渠道复用 OpenAI adaptor,图像请求会被转发到 https://openrouter.ai/api/v1/images/generations,上游返回 404。

本 PR 在 openai.Adaptor.GetRequestURL 中为 OpenRouter 渠道的 RelayModeImagesGenerations 增加特判,改为请求 {base}/v1/images。请求体(model/prompt/n/size)和响应体(created/data[].b64_json/usage)均为 OpenAI Images 兼容格式,OpenRouter 会忽略未知字段,因此现有的 ImageRequest 转换和 OpenaiImageHandler(含 usage 计费解析)无需改动。

OpenRouter now offers an image generation API at POST {base}/v1/images (docs) — a flat path, unlike OpenAI's /v1/images/generations. Since the OpenRouter channel reuses the OpenAI adaptor, image requests currently go to https://openrouter.ai/api/v1/images/generations and get a 404 upstream. This PR special-cases RelayModeImagesGenerations for OpenRouter channels in GetRequestURL. Request and response shapes are OpenAI-Images-compatible (OpenRouter silently ignores unknown fields, responses carry created / data[].b64_json / usage), so the existing ConvertImageRequest passthrough and OpenaiImageHandler billing path work unchanged.

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • 无 / None

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 Issues 与 PRs,确认不是重复提交。
  • Bug fix 说明: 不适用(新功能)。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试与端到端手动验证(见下)。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

1. 路径验证 / Endpoint verification — OpenRouter 上游确认扁平路径存在,/generations 不存在:

$ curl -s -o /dev/null -w "%{http_code}" -X POST https://openrouter.ai/api/v1/images -d '{...}'
401   # 需要鉴权 → 路径存在
$ curl -s -o /dev/null -w "%{http_code}" -X POST https://openrouter.ai/api/v1/images/generations -d '{}'
404   # 路径不存在

2. 单元测试 / Unit tests(新增 openrouter_image_test.go,覆盖图像 URL 特判 + chat 路径不受影响):

$ go build ./...   # ok
$ go test ./relay/channel/openai/...
ok  	github.com/QuantumNous/new-api/relay/channel/openai	0.367s

3. 端到端验证 / E2E — 本地启动 new-api(SQLite),配置 type=20 OpenRouter 渠道指向一个记录请求路径的 mock 上游,然后调用 new-api 的 /v1/images/generations

$ curl -s -X POST http://127.0.0.1:18791/v1/images/generations \
    -H "Authorization: Bearer sk-..." \
    -d '{"model":"google/gemini-2.5-flash-image","prompt":"a cute cat"}'
{"created":1751800000,"data":[{"b64_json":"aGVsbG8="}],"usage":{"completion_tokens":1290,"prompt_tokens":10,"total_tokens":1300}}

# mock 上游日志(确认命中扁平路径 + 渠道密钥透传):
MOCK UPSTREAM HIT: POST /v1/images Authorization="Bearer sk-or-test"

# new-api 计费日志(usage 正确入账):
google/gemini-2.5-flash-image  prompt_tokens=10  completion_tokens=1290  quota=48750

Summary by CodeRabbit

  • Bug Fixes
    • Fixed OpenRouter image-generation routing to use the correct /v1/images endpoint.
    • Updated OpenRouter image requests to merge additional image parameters from extra fields into the outbound JSON body, while preserving existing/known fields.
    • Ensured non-OpenRouter channels omit these extra/unknown image fields from the serialized payload.
  • Tests
    • Added unit tests covering OpenRouter URL rewriting, chat routing staying unchanged, and request-body transformation for both OpenRouter and non-OpenRouter channels.

OpenRouter's image generation API lives at POST {base}/v1/images
(https://openrouter.ai/docs/features/multimodal/image-generation-api),
not the OpenAI-style /v1/images/generations. Route image generation
requests on OpenRouter channels to that path so downstream
/v1/images/generations requests relay correctly. The existing OpenAI
image handler already parses OpenRouter's response shape
(created/data[].b64_json/usage).
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88d4449f-3ca4-46d5-9cf0-a20bb8776f68

📥 Commits

Reviewing files that changed from the base of the PR and between 017cf45 and 7684bf2.

📒 Files selected for processing (3)
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/openrouter_image.go
  • relay/channel/openai/openrouter_image_test.go

Walkthrough

This PR adds OpenRouter-specific image-generation URL routing and image-body merging. OpenRouter image requests now use /v1/images, and extra image parameters are merged into the serialized request body. Tests cover both the routing and payload behavior.

Changes

OpenRouter image handling

Layer / File(s) Summary
Image URL rewrite
relay/channel/openai/adaptor.go, relay/channel/openai/openrouter_image_test.go
GetRequestURL now routes OpenRouter image-generation requests to {ChannelBaseUrl}/v1/images, and tests verify that image requests rewrite while chat completions do not.
Image body extra merge
relay/channel/openai/openrouter_image.go, relay/channel/openai/adaptor.go, relay/channel/openai/openrouter_image_test.go
ConvertImageRequest now uses a JSON merge helper for OpenRouter image generation, preserving known fields and injecting extra request fields into the outbound body; tests verify OpenRouter merging and non-OpenRouter omission.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • QuantumNous/new-api#2036: Also adds RelayModeImagesGenerations handling in an adaptor, including image URL rewriting and custom image request conversion.
  • QuantumNous/new-api#2356: Touches dto.ImageRequest, which affects which fields are treated as known versus merged from request.Extra here.
  • QuantumNous/new-api#4646: Updates the dto.ImageRequest schema in a way that can change OpenRouter image-body merging behavior.

Suggested reviewers: creamlike1024, seefs001

Poem

A bunny hops through OpenRouter trails,
Images now skip the winding rails.
Extras hop in when the body is spun,
Chat stays steady, and routing is done.
🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding OpenRouter support for the image generation endpoint.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@relay/channel/openai/openrouter_image_test.go`:
- Around line 14-62: The new tests in TestGetRequestURLOpenRouterImageGeneration
and TestGetRequestURLOpenRouterChatUnchanged should use testify instead of raw
t.Fatalf. Update the openrouter_image_test.go test setup to import
github.com/stretchr/testify/require and use require.NoError for GetRequestURL
errors, then use assert.Equal for the returned URL comparisons. Keep the
existing test names and Adaptor/GetRequestURL usage, but replace manual fatal
checks with the appropriate require/assert helpers throughout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc97ac6c-a51f-4c6e-adbe-765e46fce95e

📥 Commits

Reviewing files that changed from the base of the PR and between 5cbb7b0 and 6915d61.

📒 Files selected for processing (2)
  • relay/channel/openai/adaptor.go
  • relay/channel/openai/openrouter_image_test.go

Comment thread relay/channel/openai/openrouter_image_test.go
@Robinnnnn

Copy link
Copy Markdown
Author

Addressed the review comment — tests now use require.NoError / assert.Equal from testify.

@Calcium-Ion

Copy link
Copy Markdown
Member

这里把 OpenRouter 图片生成请求改到 /v1/images 是对的,但请求体仍走通用 dto.ImageRequest 序列化。当前 ImageRequest.MarshalJSON 不会把 Extra 中捕获的未知字段合并回去,所以 OpenRouter 图片 API 支持的 resolutionaspect_ratioseedinput_referencesprovider.options 等参数会被丢弃,导致请求无法按用户指定配置生成图片。

建议补充 OpenRouter 图片 API 需要的显式字段,或增加 OpenRouter 专用 image DTO/转换路径,并加一个回归测试验证至少一个 OpenRouter 专属参数(如 aspect_ratioprovider.options)会被转发到上游。

参考:https://openrouter.ai/docs/guides/overview/multimodal/image-generation.md

@Robinnnnn

Copy link
Copy Markdown
Author

@Calcium-Ion @t0ng7u @seefs001 would appreciate a review!

The generic dto.ImageRequest serialization drops unknown fields
(aspect_ratio, resolution, seed, input_references, provider) that
OpenRouter's /v1/images endpoint accepts. Merge Extra back into the
outbound body for OpenRouter image generation requests only, leaving
all other channels' serialization unchanged.
@Robinnnnn

Copy link
Copy Markdown
Author

感谢审阅——您说得对,仅修复 URL 还不够。通用的 ImageRequest.MarshalJSON 会丢弃 Extra 中捕获的所有字段,导致 aspect_ratio / resolution / seed / input_references / provider 无法到达 OpenRouter。已在最新提交中修复。

实现方式: 我注意到 MarshalJSON 是有意不把 Extra 全局合并回去的(dto/openai_image.go 中被注释掉的那段代码),所以没有改动 DTO 本身。而是在 ConvertImageRequest 中加了一个仅针对该渠道的转换:当渠道为 OpenRouter 且 relay mode 为图片生成时,把请求展平为 map[string]json.RawMessage 并合并 Extra(键冲突时已知字段优先)。其他所有渠道以及 image edits 的 multipart 路径序列化行为完全不变。

测试: TestConvertImageRequestOpenRouterMergesExtra 验证 aspect_ratioseedprovider.options 能保留到出站请求体中;TestConvertImageRequestNonOpenRouterDropsExtra 保证非 OpenRouter 渠道仍然丢弃未知字段(保持现有行为不变)。

已用真实 API 调用完成端到端验证: 本地运行 new-api 实例并配置 OpenRouter 渠道,向 POST /v1/images/generations 发送带 aspect_ratio: "16:9"seed: 42 的请求。调试日志确认出站请求发往 https://openrouter.ai/api/v1/images,请求体为 {"aspect_ratio":"16:9","model":"google/gemini-2.5-flash-image","n":1,"prompt":"...","seed":42},返回的图片尺寸为 1344×768——正好是 16:9;而 aspect_ratio: "1:1" 的对照请求返回 1024×1024。provider 选项也以同样方式透传。

这种方式能通用覆盖 OpenRouter 图片生成文档 中的所有参数——OpenRouter 未来新增的参数也无需再改动即可透传。如果您更倾向于显式的类型化字段,我也很乐意调整。

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants