diff --git a/.claude/skills/design/scripts/logo/generate.py b/.claude/skills/design/scripts/logo/generate.py index 4987598..d006c13 100644 --- a/.claude/skills/design/scripts/logo/generate.py +++ b/.claude/skills/design/scripts/logo/generate.py @@ -334,6 +334,31 @@ def _muapi_error(response): return "MuAPI request failed" +def _muapi_result_url(response): + """Return the documented result URL from the creation response.""" + for item in _muapi_response_objects(response): + urls = item.get("urls") + if not isinstance(urls, dict) or "get" not in urls: + continue + + result_url = urls.get("get") + if not isinstance(result_url, str) or not result_url: + raise RuntimeError( + "MuAPI creation response did not include a valid HTTPS result URL" + ) + try: + _validate_public_https_url(result_url) + except ValueError as exc: + raise RuntimeError( + "MuAPI creation response did not include a valid HTTPS result URL" + ) from exc + return result_url + + raise RuntimeError( + "MuAPI creation response did not include a valid HTTPS result URL" + ) + + def _muapi_output_url(response): for item in _muapi_response_objects(response): outputs = item.get("outputs") @@ -376,6 +401,7 @@ def _generate_with_muapi(prompt, output_path, aspect_ratio, api_key, model): request_id = _muapi_response_value(response, ("request_id", "id")) if not isinstance(request_id, str) or not request_id: raise RuntimeError("MuAPI did not return a request ID") + result_url = _muapi_result_url(response) data = response for poll_number in range(MUAPI_MAX_POLLS + 1): @@ -397,7 +423,7 @@ def _generate_with_muapi(prompt, output_path, aspect_ratio, api_key, model): time.sleep(MUAPI_POLL_INTERVAL) data = _json_request( - f"{MUAPI_API_BASE}/predictions/{request_id}/result", + result_url, api_key, api_key_header="x-api-key", ) diff --git a/.claude/skills/design/scripts/logo/tests/test_generate.py b/.claude/skills/design/scripts/logo/tests/test_generate.py index d1adb98..9c2addc 100644 --- a/.claude/skills/design/scripts/logo/tests/test_generate.py +++ b/.claude/skills/design/scripts/logo/tests/test_generate.py @@ -136,7 +136,15 @@ class MuapiGenerationTests(unittest.TestCase): self, json_request, sleep, download ): json_request.side_effect = [ - {"id": "req-123", "status": "created"}, + { + "id": "req-123", + "status": "created", + "output": { + "urls": { + "get": "https://api.muapi.ai/api/v1/results/req-123" + } + }, + }, {"id": "req-123", "status": "processing"}, { "id": "req-123", @@ -164,12 +172,12 @@ class MuapiGenerationTests(unittest.TestCase): json_request.call_args_list[1:], [ call( - f"{logo_generate.MUAPI_API_BASE}/predictions/req-123/result", + "https://api.muapi.ai/api/v1/results/req-123", "muapi-key", api_key_header="x-api-key", ), call( - f"{logo_generate.MUAPI_API_BASE}/predictions/req-123/result", + "https://api.muapi.ai/api/v1/results/req-123", "muapi-key", api_key_header="x-api-key", ), @@ -233,7 +241,14 @@ class MuapiGenerationTests(unittest.TestCase): @patch.object(logo_generate, "_json_request") def test_muapi_reports_failed_prediction(self, json_request): json_request.side_effect = [ - {"request_id": "req-123"}, + { + "request_id": "req-123", + "output": { + "urls": { + "get": "https://api.muapi.ai/api/v1/results/req-123" + } + }, + }, {"status": "failed", "error": "invalid prompt"}, ] @@ -242,6 +257,32 @@ class MuapiGenerationTests(unittest.TestCase): "logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana" ) + @patch.object(logo_generate, "_json_request") + def test_muapi_requires_creation_result_url(self, json_request): + json_request.return_value = {"request_id": "req-123", "status": "created"} + + with self.assertRaisesRegex(RuntimeError, "valid HTTPS result URL"): + logo_generate._generate_with_muapi( + "logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana" + ) + + json_request.assert_called_once() + + @patch.object(logo_generate, "_json_request") + def test_muapi_rejects_invalid_creation_result_url(self, json_request): + json_request.return_value = { + "request_id": "req-123", + "status": "created", + "output": {"urls": {"get": "http://api.muapi.ai/results/req-123"}}, + } + + with self.assertRaisesRegex(RuntimeError, "valid HTTPS result URL"): + logo_generate._generate_with_muapi( + "logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana" + ) + + json_request.assert_called_once() + if __name__ == "__main__": unittest.main() diff --git a/cli/assets/skills/design/scripts/logo/generate.py b/cli/assets/skills/design/scripts/logo/generate.py index 4987598..d006c13 100644 --- a/cli/assets/skills/design/scripts/logo/generate.py +++ b/cli/assets/skills/design/scripts/logo/generate.py @@ -334,6 +334,31 @@ def _muapi_error(response): return "MuAPI request failed" +def _muapi_result_url(response): + """Return the documented result URL from the creation response.""" + for item in _muapi_response_objects(response): + urls = item.get("urls") + if not isinstance(urls, dict) or "get" not in urls: + continue + + result_url = urls.get("get") + if not isinstance(result_url, str) or not result_url: + raise RuntimeError( + "MuAPI creation response did not include a valid HTTPS result URL" + ) + try: + _validate_public_https_url(result_url) + except ValueError as exc: + raise RuntimeError( + "MuAPI creation response did not include a valid HTTPS result URL" + ) from exc + return result_url + + raise RuntimeError( + "MuAPI creation response did not include a valid HTTPS result URL" + ) + + def _muapi_output_url(response): for item in _muapi_response_objects(response): outputs = item.get("outputs") @@ -376,6 +401,7 @@ def _generate_with_muapi(prompt, output_path, aspect_ratio, api_key, model): request_id = _muapi_response_value(response, ("request_id", "id")) if not isinstance(request_id, str) or not request_id: raise RuntimeError("MuAPI did not return a request ID") + result_url = _muapi_result_url(response) data = response for poll_number in range(MUAPI_MAX_POLLS + 1): @@ -397,7 +423,7 @@ def _generate_with_muapi(prompt, output_path, aspect_ratio, api_key, model): time.sleep(MUAPI_POLL_INTERVAL) data = _json_request( - f"{MUAPI_API_BASE}/predictions/{request_id}/result", + result_url, api_key, api_key_header="x-api-key", ) diff --git a/cli/assets/skills/design/scripts/logo/tests/test_generate.py b/cli/assets/skills/design/scripts/logo/tests/test_generate.py index d1adb98..9c2addc 100644 --- a/cli/assets/skills/design/scripts/logo/tests/test_generate.py +++ b/cli/assets/skills/design/scripts/logo/tests/test_generate.py @@ -136,7 +136,15 @@ class MuapiGenerationTests(unittest.TestCase): self, json_request, sleep, download ): json_request.side_effect = [ - {"id": "req-123", "status": "created"}, + { + "id": "req-123", + "status": "created", + "output": { + "urls": { + "get": "https://api.muapi.ai/api/v1/results/req-123" + } + }, + }, {"id": "req-123", "status": "processing"}, { "id": "req-123", @@ -164,12 +172,12 @@ class MuapiGenerationTests(unittest.TestCase): json_request.call_args_list[1:], [ call( - f"{logo_generate.MUAPI_API_BASE}/predictions/req-123/result", + "https://api.muapi.ai/api/v1/results/req-123", "muapi-key", api_key_header="x-api-key", ), call( - f"{logo_generate.MUAPI_API_BASE}/predictions/req-123/result", + "https://api.muapi.ai/api/v1/results/req-123", "muapi-key", api_key_header="x-api-key", ), @@ -233,7 +241,14 @@ class MuapiGenerationTests(unittest.TestCase): @patch.object(logo_generate, "_json_request") def test_muapi_reports_failed_prediction(self, json_request): json_request.side_effect = [ - {"request_id": "req-123"}, + { + "request_id": "req-123", + "output": { + "urls": { + "get": "https://api.muapi.ai/api/v1/results/req-123" + } + }, + }, {"status": "failed", "error": "invalid prompt"}, ] @@ -242,6 +257,32 @@ class MuapiGenerationTests(unittest.TestCase): "logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana" ) + @patch.object(logo_generate, "_json_request") + def test_muapi_requires_creation_result_url(self, json_request): + json_request.return_value = {"request_id": "req-123", "status": "created"} + + with self.assertRaisesRegex(RuntimeError, "valid HTTPS result URL"): + logo_generate._generate_with_muapi( + "logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana" + ) + + json_request.assert_called_once() + + @patch.object(logo_generate, "_json_request") + def test_muapi_rejects_invalid_creation_result_url(self, json_request): + json_request.return_value = { + "request_id": "req-123", + "status": "created", + "output": {"urls": {"get": "http://api.muapi.ai/results/req-123"}}, + } + + with self.assertRaisesRegex(RuntimeError, "valid HTTPS result URL"): + logo_generate._generate_with_muapi( + "logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana" + ) + + json_request.assert_called_once() + if __name__ == "__main__": unittest.main()