Skip to content

fix(ai): send OpenRouter image edits as input references - #60067

Merged
rekram1-node merged 1 commit into
devfrom
openrouter-image-edits
Oct 29, 2026
Merged

rekram1-node merged 1 commit into
devfrom
openrouter-image-edits

Conversation

@rekram1-node

Copy link
Copy Markdown
Contributor

Problem

Image edits through OpenRouter never worked. OpenRouter image models use the OpenAI images protocol, and when a request has input images that protocol posts multipart to /images/edits. OpenRouter has no such endpoint, so every edit came back as HTTP 404 for every model.

Just changing the path isn't enough either. OpenRouter's image API (POST /api/v1/images, alias /images/generations) is JSON only and reads input images from input_references: [{ type: "image_url", image_url: { url } }]. If you send OpenAI's images field instead, it silently ignores it and you get an unrelated text-to-image redraw.

Change

I added an imageInput option to OpenAIImages.model, next to the sizing hook from #59927. It defaults to "edits", so OpenAI, Azure, Vercel and the other hosts behave exactly as before. OpenRouter passes "references":

 OpenRouter image request
-  images? -> multipart POST /images/edits        (404)
+  always  -> JSON POST /images/generations
+  images  -> input_references: [{ type: "image_url", image_url: { url } }]
+             bytes as data URLs, http(s) URLs as is
+  mask    -> UnsupportedOperation (OpenRouter has no mask parameter)

A request without images sends the same body as before. Response parsing didn't need changes, since OpenRouter returns OpenAI-style data[].b64_json.

This is stacked on #59927 (image-sizing-fixes) because both touch the same lines in openai-images.ts and openrouter.ts. Once that merges this retargets to dev with only its own commit.

Out of scope

  • Image.stream through OpenRouter. Its stream events carry media_type but no output_format, and the protocol currently requires output_format on partial and completed events, so streaming there fails to decode. That's a separate fix.
  • Per-model reference limits. GET /api/v1/images/models lists input_references {min, max} per model (some take 0). I'm leaving the count check to OpenRouter.
  • Vercel AI Gateway edits already work through /v1/images/edits, so I left them alone.

Verification

  • Live: POST /api/v1/images/edits returns 404. /api/v1/images/models lists input_references for nearly every model and no mask parameter anywhere.
  • Recorded openrouter-images/edits-an-image-through-input-references with openai/gpt-image-1-mini (about $0.002). The black circle fixture came back as the same circle in green, so it's a real edit.
  • New unit tests in test/image.test.ts: no images keeps the plain body, bytes and URLs become input_references on /images/generations as JSON, and a mask fails with UnsupportedOperation before any request. These and the recorded test fail without the fix.
  • packages/ai: bun typecheck, bun run typecheck:tsc, bun test (1979 pass). Root: bun run check.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none" stroke="currentColor" stroke-width="2">
  <style>
    .dot { animation: go 2.4s ease-in-out infinite; }
    .out { animation: fill 2.4s ease-in-out infinite; }
    @keyframes go { 0% { transform: translateX(0); opacity: 0; } 15% { opacity: 1; } 55% { transform: translateX(72px); opacity: 1; } 70%, 100% { transform: translateX(72px); opacity: 0; } }
    @keyframes fill { 0%, 50% { fill-opacity: 0.15; } 70%, 90% { fill-opacity: 0.9; } 100% { fill-opacity: 0.15; } }
    @media (prefers-reduced-motion: reduce) { .dot, .out { animation: none; } .dot { opacity: 0; } .out { fill-opacity: 0.9; } }
  </style>
  <rect x="24" y="12" width="48" height="48" rx="6" opacity="0.6"/>
  <circle cx="48" cy="36" r="12" fill="currentColor" fill-opacity="0.35" stroke="none"/>
  <line x1="84" y1="36" x2="156" y2="36" stroke-dasharray="3 5" opacity="0.4"/>
  <path d="M150 30 L157 36 L150 42" opacity="0.4"/>
  <circle class="dot" cx="84" cy="36" r="4" fill="currentColor" stroke="none"/>
  <rect x="168" y="12" width="48" height="48" rx="6"/>
  <circle class="out" cx="192" cy="36" r="12" fill="currentColor" fill-opacity="0.9"/>
</svg>

Base automatically changed from image-sizing-fixes to dev October 29, 2026 02:21
@rekram1-node
rekram1-node force-pushed the openrouter-image-edits branch from e8ba722 to b1d7692 Compare October 29, 2026 02:21
@rekram1-node
rekram1-node enabled auto-merge (squash) October 29, 2026 02:21
@rekram1-node
rekram1-node merged commit 27641b3 into dev Oct 29, 2026
20 checks passed
@rekram1-node
rekram1-node deleted the openrouter-image-edits branch October 29, 2026 02:27
@amrkmn amrkmn mentioned this pull request Oct 29, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant