feat: resume fixes, OpenRouter templates, and project context

Delegate expected output filenames to modules so resume works for rembg
and composite; normalize G'MIC multi-frame output; add OpenRouter style
reference support with tests. Add Crusaders, orange, and team gallery
pipelines plus SOUL/AGENTS context files.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-07-12 10:45:00 +02:00
parent 980c7f3b8b
commit ce431d7eec
22 changed files with 1616 additions and 31 deletions
+87
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import io
import shutil
from pathlib import Path
from unittest.mock import MagicMock, patch
@@ -73,6 +74,92 @@ class TestAIParameters:
assert params["model"] == "black-forest-labs/flux.2-klein-4b"
assert params["strength"] == 0.3
assert params["api_key_env"] == "OPENROUTER_API_KEY"
assert params["template_image"] is None
def test_openrouter_accepts_template_image(self, tmp_path: Path) -> None:
template = tmp_path / "ref.png"
make_png(template)
params = OpenRouterEditModule.validate_module_params(
{"prompt": "match style", "template_image": template}
)
assert params["template_image"] == template
def test_build_payload_with_template(self) -> None:
payload = OpenRouterEditModule._build_payload(
"data:image/jpeg;base64,abc",
"match colors",
"google/gemini-3-pro-image",
0.3,
template_data_url="data:image/jpeg;base64,ref",
)
assert payload["modalities"] == ["image", "text"]
assert "image_config" not in payload
content = payload["messages"][0]["content"]
assert content[0]["type"] == "text"
assert "FIRST image" in content[0]["text"]
assert content[1]["image_url"]["url"] == "data:image/jpeg;base64,ref"
assert content[2]["image_url"]["url"] == "data:image/jpeg;base64,abc"
def test_build_payload_flux_keeps_strength(self) -> None:
payload = OpenRouterEditModule._build_payload(
"data:image/jpeg;base64,abc",
"brighten",
"black-forest-labs/flux.2-klein-4b",
0.25,
)
assert payload["modalities"] == ["image"]
assert payload["image_config"] == {"strength": 0.25}
def test_save_result_matching_source_preserves_png_size(
self, tmp_path: Path
) -> None:
try:
from PIL import Image
except ImportError:
pytest.skip("Pillow not installed")
source = tmp_path / "source.png"
dest = tmp_path / "out.png"
with Image.new("RGBA", (16, 12), (10, 20, 30, 128)) as image:
image.save(source, format="PNG")
with Image.new("RGB", (8, 6), (200, 100, 50)) as edited:
buffer = io.BytesIO()
edited.save(buffer, format="PNG")
result_bytes = buffer.getvalue()
OpenRouterEditModule._save_result_matching_source(source, result_bytes, dest)
with Image.open(dest) as saved:
assert saved.size == (16, 12)
assert saved.mode == "RGBA"
assert saved.getchannel("A").getextrema() == (128, 128)
def test_missing_template_image_raises(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("OPENROUTER_API_KEY", "test-key")
src = tmp_path / "photo.png"
make_png(src)
output_dir = tmp_path / "out"
output_dir.mkdir()
ctx = ModuleContext(
input_paths=[src],
matched_groups=[],
output_dir=output_dir,
params=OpenRouterEditModule.validate_module_params(
{
"prompt": "match",
"template_image": tmp_path / "missing.png",
"max_edge": 0,
}
),
pipeline_output_root=tmp_path,
step_id="openrouter_edit_01",
logger=None,
)
with pytest.raises(FileNotFoundError, match="Template image not found"):
OpenRouterEditModule().run(ctx)
def test_comfy_requires_prompt(self) -> None:
with pytest.raises(ValueError, match="required"):