diff --git a/mcp_servers/image_gen_server.py b/mcp_servers/image_gen_server.py index 697522e3d..327da5c03 100644 --- a/mcp_servers/image_gen_server.py +++ b/mcp_servers/image_gen_server.py @@ -33,7 +33,7 @@ async def list_tools() -> list[Tool]: "prompt": {"type": "string", "description": "Image description prompt"}, "model": {"type": "string", "description": "Model name (auto-detects if omitted)"}, "size": {"type": "string", "description": "Image size (default 1024x1024)"}, - "quality": {"type": "string", "description": "Quality: low, medium, high, auto (default medium)"}, + "quality": {"type": "string", "description": "Quality: low, medium, high, auto (default configured quality, otherwise medium)"}, }, "required": ["prompt"], }, @@ -66,7 +66,7 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]: if not model_spec: model_spec = _settings.get("image_model", "") - if quality == "medium" and _settings.get("image_quality"): + if "quality" not in arguments and _settings.get("image_quality"): quality = _settings["image_quality"] # Auto-detect best available image model diff --git a/src/ai_interaction.py b/src/ai_interaction.py index dd772eff1..90294774a 100644 --- a/src/ai_interaction.py +++ b/src/ai_interaction.py @@ -1207,7 +1207,7 @@ async def do_generate_image(content: str, session_id: Optional[str] = None, owne Line 1: prompt describing the image Line 2: model name (optional, default auto-detects: prefers gpt-image-1.5 > gpt-image-1) Line 3: size (optional, defaults to 1024x1024) - Line 4: quality (optional, defaults to medium — options: low, medium, high, auto) + Line 4: quality (optional, defaults to configured quality or medium — low, medium, high, auto) """ import base64 import httpx @@ -1225,13 +1225,13 @@ async def do_generate_image(content: str, session_id: Optional[str] = None, owne prompt = str(args.get('prompt') or '').strip() model_spec = str(args.get('model') or '').strip() size = str(args.get('size') or '1024x1024') - quality = str(args.get('quality') or 'medium') + quality = str(args.get('quality') or '') else: lines = content.strip().split("\n") prompt = lines[0].strip() if lines else "" model_spec = lines[1].strip() if len(lines) > 1 and lines[1].strip() else "" size = lines[2].strip() if len(lines) > 2 and lines[2].strip() else "1024x1024" - quality = lines[3].strip() if len(lines) > 3 and lines[3].strip() else "medium" + quality = lines[3].strip() if len(lines) > 3 and lines[3].strip() else "" if not prompt: return {"error": "Image prompt is required (line 1)"} @@ -1249,8 +1249,8 @@ async def do_generate_image(content: str, session_id: Optional[str] = None, owne # Use admin-configured model/quality if not specified by the tool call if not model_spec: model_spec = _settings.get("image_model", "") - if quality == "medium" and _settings.get("image_quality"): - quality = _settings["image_quality"] + if not quality: + quality = _settings.get("image_quality") or "medium" # Auto-detect best available image model if still not set if not model_spec: @@ -1482,7 +1482,7 @@ async def do_edit_image( session_id: Optional[str] = None, owner: Optional[str] = None, size: str = "auto", - quality: str = "medium", + quality: Optional[str] = None, progress_callback: Optional[Callable[[Dict[str, Any]], Awaitable[None]]] = None, incognito: bool = False, ) -> Dict: @@ -1509,8 +1509,8 @@ async def do_edit_image( if not model_spec: model_spec = _settings.get("image_model", "") - if quality == "medium" and _settings.get("image_quality"): - quality = _settings["image_quality"] + if quality is None: + quality = _settings.get("image_quality") or "medium" if not model_spec: return {"error": "No image model selected for image editing"} diff --git a/tests/test_image_quality_defaults.py b/tests/test_image_quality_defaults.py new file mode 100644 index 000000000..090ab4166 --- /dev/null +++ b/tests/test_image_quality_defaults.py @@ -0,0 +1,74 @@ +"""Explicit quality choices must take precedence over the configured default.""" +import asyncio +import json + +import pytest +from PIL import Image + + +@pytest.mark.parametrize("caller", ["json", "lines", "mcp", "edit"]) +@pytest.mark.parametrize("requested,configured,expected", [ + ("medium", "high", "medium"), + ("medium", "low", "medium"), + ("low", "high", "low"), + ("high", "low", "high"), + ("auto", "high", "auto"), + (None, "high", "high"), + (None, None, "medium"), +]) +def test_image_quality_precedence(monkeypatch, tmp_path, caller, requested, configured, expected): + from src import ai_interaction, settings + from mcp_servers.image_gen_server import call_tool + + config = {"image_gen_enabled": True, "image_model": "gpt-image-1"} + if configured is not None: + config["image_quality"] = configured + monkeypatch.setattr(settings, "load_settings", lambda: config) + monkeypatch.setattr(ai_interaction, "_resolve_model", lambda *args, **kwargs: + ("http://fixture.invalid/v1/chat/completions", "gpt-image-1", {})) + captured = [] + + class Response: + status_code = 418 + text = "Fixture captured request" + + def json(self): + return {"error": {"message": self.text}} + + class Client: + def __init__(self, *args, **kwargs): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + pass + + async def post(self, url, **kwargs): + captured.append(kwargs.get("json") or kwargs.get("data")) + return Response() + + monkeypatch.setattr("httpx.AsyncClient", Client) + args = {"prompt": "Fixture image", "model": "gpt-image-1"} + if requested is not None: + args["quality"] = requested + if caller == "json": + operation = ai_interaction.do_generate_image(json.dumps(args), incognito=True) + elif caller == "lines": + content = "Fixture image\ngpt-image-1\n1024x1024" + if requested is not None: + content += "\n" + requested + operation = ai_interaction.do_generate_image(content, incognito=True) + elif caller == "mcp": + operation = call_tool("generate_image", args) + else: + source = tmp_path / "fixture.png" + Image.new("RGB", (2, 2), "white").save(source) + kwargs = {} if requested is None else {"quality": requested} + operation = ai_interaction.do_edit_image("Fixture image", str(source), size="1024x1024", + incognito=True, **kwargs) + result = asyncio.run(operation) + assert "418" in str(result), result + assert len(captured) == 1 + assert captured[0]["quality"] == expected