diff --git a/nanobot/providers/image_generation.py b/nanobot/providers/image_generation.py index 1dcdc649a..d0aac7860 100644 --- a/nanobot/providers/image_generation.py +++ b/nanobot/providers/image_generation.py @@ -768,6 +768,13 @@ _OPENAI_ASPECT_RATIO_SIZES = { "3:4": "1024x1360", "4:3": "1360x1024", } +_OPENAI_GPT_IMAGE_ASPECT_RATIO_SIZES = { + "1:1": "1024x1024", + "16:9": "1536x1024", + "9:16": "1024x1536", + "3:4": "1024x1536", + "4:3": "1536x1024", +} class OpenAIImageGenerationClient(ImageGenerationProvider): @@ -825,7 +832,7 @@ class OpenAIImageGenerationClient(ImageGenerationProvider): body["response_format"] = "b64_json" body["n"] = 1 - size = _openai_size(aspect_ratio, image_size) + size = _openai_size(clean_model, aspect_ratio, image_size) if size: body["size"] = size @@ -976,14 +983,20 @@ class CodexImageGenerationClient(ImageGenerationProvider): def _openai_size( + model: str, aspect_ratio: str | None, image_size: str | None, ) -> str: """Resolve aspect ratio or image_size to an OpenAI Images API size string.""" if image_size and "x" in image_size.lower(): return image_size - if aspect_ratio and aspect_ratio in _OPENAI_ASPECT_RATIO_SIZES: - return _OPENAI_ASPECT_RATIO_SIZES[aspect_ratio] + sizes = ( + _OPENAI_GPT_IMAGE_ASPECT_RATIO_SIZES + if model.startswith("gpt-image") + else _OPENAI_ASPECT_RATIO_SIZES + ) + if aspect_ratio and aspect_ratio in sizes: + return sizes[aspect_ratio] return "1024x1024" diff --git a/nanobot/webui/settings_api.py b/nanobot/webui/settings_api.py index a5ab13c5a..6d43e22c8 100644 --- a/nanobot/webui/settings_api.py +++ b/nanobot/webui/settings_api.py @@ -73,12 +73,16 @@ def _mask_secret_hint(secret: str | None) -> str | None: def _provider_requires_api_key(spec: Any) -> bool: if spec.backend == "azure_openai": return True + if spec.is_oauth: + return False if spec.is_local or spec.is_direct: return False return True def _provider_configured_for_settings(spec: Any, provider_config: Any) -> bool: + if spec.is_oauth: + return True if _provider_requires_api_key(spec): return bool(provider_config.api_key) return bool( diff --git a/tests/channels/test_websocket_channel.py b/tests/channels/test_websocket_channel.py index cc011a244..74a780c80 100644 --- a/tests/channels/test_websocket_channel.py +++ b/tests/channels/test_websocket_channel.py @@ -1055,6 +1055,7 @@ async def test_settings_api_returns_safe_subset_and_updates_whitelist( } assert image_providers["openrouter"]["label"] == "OpenRouter" assert image_providers["openrouter"]["configured"] is False + assert image_providers["openai_codex"]["configured"] is True assert image_providers["gemini"]["label"] == "Gemini" assert body["runtime"]["config_path"] == str(config_path) workspace_path = body["runtime"]["workspace_path"].replace("\\", "/") diff --git a/tests/providers/test_image_generation.py b/tests/providers/test_image_generation.py index 119bbcf05..c8c05c853 100644 --- a/tests/providers/test_image_generation.py +++ b/tests/providers/test_image_generation.py @@ -624,6 +624,19 @@ async def test_openai_aspect_ratio_to_size() -> None: assert fake.calls[0]["json"]["size"] == "1024x1024" +@pytest.mark.asyncio +async def test_openai_gpt_image_uses_supported_landscape_size() -> None: + fake = FakeClient(FakeResponse({"data": [{"b64_json": RAW_B64}]})) + client = OpenAIImageGenerationClient( + api_key="sk-openai-test", + client=fake, # type: ignore[arg-type] + ) + + await client.generate(prompt="draw", model="gpt-image-1", aspect_ratio="16:9") + + assert fake.calls[0]["json"]["size"] == "1536x1024" + + @pytest.mark.asyncio async def test_openai_default_size_when_no_aspect_ratio() -> None: fake = FakeClient(FakeResponse({"data": [{"b64_json": RAW_B64}]}))