fix: update image models, handle upstream errors in HTTP 200
Three issues found in activity logs: 1. Image model IDs stale — FLUX and Seedream removed from OpenRouter. Updated to current models: Gemini 2.5/3.1 Flash Image, Gemini 3 Pro Image, GPT-5 Image/Mini. Default changed to gemini-2.5-flash-image. 2. openrouter/free router wraps upstream 429 errors in HTTP 200 body with error object. Both text and image response parsers now detect this pattern and surface a proper RuntimeException. 3. Image request used modalities: ["image"] but Gemini needs ["image", "text"]. Updated to include both. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,52 +23,44 @@ class PRAutoBlogger_Settings_Fields_Extended {
|
||||
public static function get_image_models(): array {
|
||||
return [
|
||||
[
|
||||
'id' => 'black-forest-labs/flux-2-pro',
|
||||
'name' => 'FLUX.2 Pro',
|
||||
'id' => 'google/gemini-2.5-flash-image',
|
||||
'name' => 'Gemini 2.5 Flash Image (Nano Banana)',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.005,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'Good quality, low cost. Recommended.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'google/gemini-3.1-flash-image-preview',
|
||||
'name' => 'Gemini 3.1 Flash Image (Nano Banana 2)',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.008,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'Latest Google. Better quality.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'google/gemini-3-pro-image-preview',
|
||||
'name' => 'Gemini 3 Pro Image (Nano Banana Pro)',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.03,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'High quality, fast. Recommended.', 'prautoblogger' ),
|
||||
'description' => __( 'Highest quality Google. Premium.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'black-forest-labs/flux-1.1-pro',
|
||||
'name' => 'FLUX 1.1 Pro',
|
||||
'id' => 'openai/gpt-5-image-mini',
|
||||
'name' => 'GPT-5 Image Mini',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.04,
|
||||
'cost_per_image' => 0.02,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'Previous gen Pro. Slightly pricier.', 'prautoblogger' ),
|
||||
'description' => __( 'OpenAI budget image model.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'black-forest-labs/flux-1.1-pro-ultra',
|
||||
'name' => 'FLUX 1.1 Pro Ultra',
|
||||
'id' => 'openai/gpt-5-image',
|
||||
'name' => 'GPT-5 Image',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.06,
|
||||
'cost_per_image' => 0.08,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'Highest quality FLUX. Premium.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'bytedance/seedream-3.0',
|
||||
'name' => 'Seedream 3.0',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.003,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'Budget. Good quality, very low cost.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'google/gemini-2.5-flash-preview:image',
|
||||
'name' => 'Gemini 2.5 Flash Image',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.01,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'Google multimodal. Text-in-image.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'openai/gpt-image-1',
|
||||
'name' => 'GPT Image 1',
|
||||
'provider' => 'openrouter',
|
||||
'cost_per_image' => 0.04,
|
||||
'capabilities' => [ 'image_generation' ],
|
||||
'description' => __( 'OpenAI. Photorealistic style.', 'prautoblogger' ),
|
||||
'description' => __( 'OpenAI premium. Photorealistic.', 'prautoblogger' ),
|
||||
],
|
||||
[
|
||||
'id' => 'flux-1-schnell',
|
||||
|
||||
@@ -23,16 +23,15 @@ class PRAutoBlogger_OpenRouter_Image_Pricing {
|
||||
* @var array<string, float>
|
||||
*/
|
||||
private const COST_PER_IMAGE = [
|
||||
'black-forest-labs/flux-2-pro' => 0.03,
|
||||
'black-forest-labs/flux-1.1-pro' => 0.04,
|
||||
'black-forest-labs/flux-1.1-pro-ultra' => 0.06,
|
||||
'bytedance/seedream-3.0' => 0.003,
|
||||
'google/gemini-2.5-flash-preview:image' => 0.01,
|
||||
'openai/gpt-image-1' => 0.04,
|
||||
'google/gemini-2.5-flash-image' => 0.005,
|
||||
'google/gemini-3.1-flash-image-preview' => 0.008,
|
||||
'google/gemini-3-pro-image-preview' => 0.03,
|
||||
'openai/gpt-5-image-mini' => 0.02,
|
||||
'openai/gpt-5-image' => 0.08,
|
||||
];
|
||||
|
||||
/** @var string Default model when none is configured. */
|
||||
public const DEFAULT_MODEL = 'black-forest-labs/flux-2-pro';
|
||||
public const DEFAULT_MODEL = 'google/gemini-2.5-flash-image';
|
||||
|
||||
/**
|
||||
* Resolve the model identifier from a hint, falling back to site option,
|
||||
|
||||
@@ -233,10 +233,12 @@ class PRAutoBlogger_OpenRouter_Image_Provider implements PRAutoBlogger_Image_Pro
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $prompt,
|
||||
'content' => 'Generate an image: ' . $prompt,
|
||||
],
|
||||
],
|
||||
'modalities' => [ 'image' ],
|
||||
// Use ["image", "text"] — some providers (Gemini) require both
|
||||
// modalities even when we only want the image output.
|
||||
'modalities' => [ 'image', 'text' ],
|
||||
'image_config' => [
|
||||
'aspect_ratio' => $aspect,
|
||||
],
|
||||
|
||||
@@ -59,6 +59,16 @@ class PRAutoBlogger_OpenRouter_Image_Support {
|
||||
);
|
||||
}
|
||||
|
||||
// OpenRouter routers may wrap upstream errors in an HTTP 200 body.
|
||||
if ( isset( $decoded['error']['message'] ) ) {
|
||||
throw new \RuntimeException(
|
||||
sprintf(
|
||||
esc_html__( 'OpenRouter upstream image error: %s', 'prautoblogger' ),
|
||||
esc_html( $decoded['error']['message'] )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$images = $decoded['choices'][0]['message']['images'] ?? [];
|
||||
if ( empty( $images ) ) {
|
||||
throw new \RuntimeException(
|
||||
|
||||
@@ -50,6 +50,21 @@ class PRAutoBlogger_OpenRouter_Response_Parser {
|
||||
* @throws \RuntimeException If response structure is invalid.
|
||||
*/
|
||||
public function parse_success( array $data, string $model ): array {
|
||||
// OpenRouter routers (e.g. openrouter/free) may wrap upstream provider
|
||||
// errors inside an HTTP 200 body with an `error` object. Surface them
|
||||
// as RuntimeExceptions so the retry loop or caller can handle them.
|
||||
if ( isset( $data['error']['message'] ) ) {
|
||||
$upstream_code = (int) ( $data['error']['code'] ?? 0 );
|
||||
throw new \RuntimeException(
|
||||
sprintf(
|
||||
/* translators: %1$d: upstream HTTP code, %2$s: error message */
|
||||
__( 'OpenRouter upstream error (code %1$d): %2$s', 'prautoblogger' ),
|
||||
$upstream_code,
|
||||
$data['error']['message']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Validate we have a choices array with at least one message.
|
||||
if ( ! isset( $data['choices'][0]['message'] ) ) {
|
||||
// Log the raw shape so we can diagnose what the model actually returned.
|
||||
|
||||
Reference in New Issue
Block a user