feat(vercel-provider): add OrqAiImageModel with /images/generations +…#129
Merged
thedevtoni merged 1 commit intoMay 15, 2026
Merged
Conversation
a38e929 to
ac5a10f
Compare
…ross Orq upstreams
Replace direct use of `OpenAICompatibleImageModel` with a dedicated
`OrqAiImageModel` that targets the Orq AI proxy correctly across all
upstream providers (OpenAI, fal, Google Imagen, Leonardo, etc).
Problems fixed
--------------
1. `OpenAICompatibleImageModel`'s `/images/generations` branch hardcodes
`response_format: "b64_json"` in the request body. Newer OpenAI image
models (gpt-image-1 and later) reject this parameter with
`Unknown parameter: 'response_format'`, and the Orq proxy forwards
that error verbatim. Image generation through Orq was broken for any
gpt-image-1+ model.
2. The upstream response schema only accepts `data[].b64_json` as a
required string. Several Orq-routed upstreams (fal, Leonardo, dall-e
default) return the image under `data[].url` instead, either as a data
URI or as an http URL. These responses failed schema validation with
`AI_TypeValidationError: data[0].b64_json: Invalid input: expected
string, received undefined`. This affected both the generation and the
edit branches because they share one schema.
3. The upstream `/images/edits` branch forwards a `mask` field if present
in the call options. Orq's `CreateImageEditRequestBody` schema (see
orq-node) does not define `mask`, so requests carrying it are rejected
server-side. The new model drops `mask` with a warning instead.
Note: image *editing* was technically reachable on `main` via the
inherited upstream class and worked for the common gpt-image-1 path
(because the edit branch does not send `response_format` and gpt-image-1
always returns `b64_json`). It was broken for dall-e-2 with default
`response_format: url` and for any call that included a `mask`. This PR
makes editing robust across all Orq-routed models, not just gpt-image-1.
Changes
-------
- New `OrqAiImageModel` implementing `ImageModelV3` (specificationVersion
`"v3"`, `maxImagesPerCall = 10`), wired up via `provider.imageModel(id)`
in `createOrqAiProvider`.
- `/images/generations` branch drops `response_format` so each underlying
upstream uses its own default — gpt-image-1 returns base64 inline,
dall-e-* defaults to a URL, fal/Leonardo return data URIs. User-supplied
extras (quality, style, background, etc.) pass through via
`providerOptions.orq`.
- Response schema accepts every shape documented in orq-node's
`CreateImageData` (`{ b64_json?, url?, revised_prompt? }`) plus an
optional `usage` envelope (`input_tokens`, `output_tokens`,
`total_tokens`).
- `normalizeImageEntry` handles three response shapes uniformly:
* `b64_json` field -> returned as-is
* `url` data URI -> strips the prefix
* `url` http(s) URL -> fetches and base64-encodes
Always returns base64 strings, matching the `ImageModelV3` contract.
- `/images/edits` branch (multipart form-data) activates when
`files != null && files.length > 0`. Each file is converted to a
`File` with a `.png` / `.jpg` / `.webp` filename so Orq's middleware
doesn't reject the part as `application/octet-stream`. The `mask`
parameter is dropped with a warning because Orq's edit schema does not
carry a mask field.
- `defaultOrqAiErrorStructure` is an inline copy of the upstream OpenAI
error envelope. `@ai-sdk/openai-compatible` exports the type but not
the value, so we replicate the schema. Orq's proxy returns OpenAI-shaped
error envelopes, so this matches 1:1.
- `createOrqAiProvider` now throws if `apiKey` is empty, and the URL
builder no longer wraps the path in a redundant `new URL(...)`.
- Adds `zod` as a peer dependency, matching the upstream
`@ai-sdk/openai-compatible` pattern (`^3.25.76 || ^4.1.8`).
Spec compliance verified against:
- `@ai-sdk/provider`'s `ImageModelV3` type (return shape: `images`,
`warnings`, `response { timestamp, modelId, headers }`, optional `usage`).
- Upstream `OpenAICompatibleImageModel@2.0.35` in `vercel/ai` for body /
schema / warning conventions.
- `orq-ai/orq-node`'s `CreateImageRequestBody`,
`CreateImageEditRequestBody`, `CreateImageData`, and `CreateImageUsage`
for body + response shapes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ac5a10f to
e6f2772
Compare
thedevtoni
approved these changes
May 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
… /images/edits support
Replace direct use of
OpenAICompatibleImageModelfor image generation with a dedicatedOrqAiImageModelthat targets the Orq AI proxy correctly across all upstream providers (OpenAI, fal, Google Imagen, Leonardo, etc).Problems fixed
OpenAICompatibleImageModelhardcodesresponse_format: "b64_json"in the request body. Newer OpenAI image models (gpt-image-1 and later) reject this parameter withUnknown parameter: 'response_format', and the Orq proxy forwards that error verbatim. Image generation through Orq was broken for any gpt-image-1+ model.The upstream model's response schema only accepts
data[].b64_json. Several Orq-routed upstreams (fal, Leonardo, dall-e-2 default) return the image underdata[].urlinstead — either as a data URI or as an http URL. These responses failed schema validation withAI_TypeValidationError: data[0].b64_json: Invalid input: expected string, received undefined.The upstream model did not support image editing via Orq. Calling
generateImage({ prompt: { images: [...] } })had no effect and Orq's/images/editsendpoint was unreachable through this provider.Changes
New
OrqAiImageModelimplementingImageModelV3(specificationVersion"v3",maxImagesPerCall = 10), wired up viaprovider.imageModel(id)increateOrqAiProvider.Request: drops
response_formatso each underlying upstream uses its own default — gpt-image-1 returns base64 inline, dall-e-* defaults to a URL, fal/Leonardo return data URIs. User-supplied extras (quality, style, background, etc.) pass through viaproviderOptions.orq.Response schema accepts every shape documented in orq-node's
CreateImageData({ b64_json?, url?, revised_prompt? }) and an optionalusageenvelope (input_tokens,output_tokens,total_tokens).normalizeImageEntryhandles three response shapes uniformly:b64_jsonfield -> returned as-isurldata URI -> strips the prefixurlhttp(s) URL -> fetches and base64-encodes Always returns base64 strings, matching theImageModelV3contract.New
/images/editsbranch (multipart form-data) activates whenfiles != null && files.length > 0. Each file is converted to aFilewith a.png/.jpg/.webpfilename so Orq's middleware doesn't reject the part asapplication/octet-stream. Themaskparameter is dropped with a warning because Orq's edit schema does not carry a mask field.Inline copy of
defaultOpenAICompatibleErrorStructurebecause@ai-sdk/openai-compatibleexports the type but not the value. Orq's proxy returns OpenAI-shaped error envelopes, so the schema matches 1:1.createOrqAiProvidernow throws ifapiKeyis empty, and the URL builder no longer wraps the path in a redundantnew URL(...).Spec compliance verified against:
@ai-sdk/provider'sImageModelV3type (return shape:images,warnings,response { timestamp, modelId, headers }, optionalusage).OpenAICompatibleImageModelinvercel/aifor body / schema / warning conventions.orq-ai/orq-node'sCreateImageRequestBody,CreateImageEditRequestBody,CreateImageData, andCreateImageUsagefor body + response shapes.