fix: complete Hunyuan image provider integration

This commit is contained in:
calesthio
2026-08-13 09:18:49 -07:00
parent 00745a5f6d
commit 8143266ead
5 changed files with 81 additions and 2 deletions

View File

@@ -44,6 +44,9 @@ DOUBAO_SPEECH_VOICE_TYPE= # Default Doubao speaker/voice type, e.g. zh_female
DASHSCOPE_API_KEY= # Qwen image gen (qwen-image-2.0-pro), TTS (qwen3-tts-flash), ASR with word timestamps (qwen3-asr-flash-filetrans)
# Get one at https://dashscope.aliyun.com/
# --- Tencent Hunyuan TokenHub ---
TENCENT_TOKENHUB_API_KEY= # Hunyuan Image 3.0 via Tencent TokenHub
# --- Music ---
SUNO_API_KEY= # Suno AI music generation (full songs, instrumentals, any genre)

View File

@@ -188,6 +188,24 @@ The ASR tool (`qwen3-asr-flash-filetrans`) uses an async submit-poll pattern. Au
---
### Tencent Hunyuan Cloud — Image Generation
> **Chinese-friendly first-party image generation.** `hunyuan_image` accesses
> Hunyuan Image 3.0 through Tencent TokenHub with Bearer-token authentication.
> It supports seeded text-to-image, up to three reference images, custom
> resolutions, prompt rewriting, and watermark controls.
**Tool unlocked:** `hunyuan_image`
**Env var:** `TENCENT_TOKENHUB_API_KEY`
Generate an API key in the Tencent Cloud TokenHub console and add it to
`.env`. The tool reports approximately $0.08 per generated image based on
TokenHub's credit price. It is available through `image_selector`; shared
reference-image inputs are normalized to the provider's `images` array.
---
### fal.ai — Multi-Model Gateway
> **Broad single-key coverage.** One API key unlocks image and video providers across multiple models.

View File

@@ -46,6 +46,18 @@ def test_hunyuan_image_metadata():
assert info["supports"]["reference_image"] is True
assert info["supports"]["prompt_rewrite"] is True
assert info["supports"]["negative_prompt"] is False
assert "env:TENCENT_TOKENHUB_API_KEY" in info["dependencies"]
assert "visual-style" in info["agent_skills"]
def test_idempotency_includes_custom_watermark():
from tools.graphics.hunyuan_image import HunyuanImage
tool = HunyuanImage()
base = {"prompt": "x"}
assert tool.idempotency_key(base) != tool.idempotency_key(
{**base, "logo_param": {"logo_url": "https://example.com/logo.png"}}
)
# ---------------------------------------------------------------------------
@@ -355,6 +367,40 @@ def test_execute_returns_error_without_api_key(monkeypatch):
assert "TENCENT_TOKENHUB_API_KEY" in result.error
def test_image_selector_maps_shared_reference_input(monkeypatch, tmp_path):
from tools.base_tool import ToolResult
from tools.graphics.hunyuan_image import HunyuanImage
from tools.graphics.image_selector import ImageSelector
monkeypatch.setenv("TENCENT_TOKENHUB_API_KEY", "test-key")
tool = HunyuanImage()
selector = ImageSelector()
monkeypatch.setattr(selector, "_providers", lambda: [tool])
monkeypatch.setattr(
selector,
"_select_best_tool",
lambda _inputs, _candidates, _context: (tool, None),
)
observed = {}
def fake_execute(inputs):
observed.update(inputs)
return ToolResult(success=True, data={}, artifacts=[inputs["output_path"]])
monkeypatch.setattr(tool, "execute", fake_execute)
result = selector.execute(
{
"prompt": "adapt this frame",
"preferred_provider": "hunyuan_cloud",
"image_path": str(tmp_path / "reference.png"),
"output_path": str(tmp_path / "out.png"),
}
)
assert result.success
assert observed["images"] == [str(tmp_path / "reference.png")]
assert result.data["selected_tool"] == "hunyuan_image"
# ---------------------------------------------------------------------------
# Dry run
# ---------------------------------------------------------------------------

View File

@@ -52,12 +52,12 @@ class HunyuanImage(BaseTool):
determinism = Determinism.SEEDED
runtime = ToolRuntime.API
dependencies = []
dependencies = ["env:TENCENT_TOKENHUB_API_KEY"]
install_instructions = (
"Set TENCENT_TOKENHUB_API_KEY to your Tencent Cloud TokenHub API key.\n"
" Get it at https://console.cloud.tencent.com/tokenhub"
)
agent_skills = []
agent_skills = ["visual-style"]
capabilities = ["generate_image", "text_to_image"]
supports = {
@@ -196,6 +196,7 @@ class HunyuanImage(BaseTool):
"seed",
"revise",
"logo_add",
"logo_param",
]
side_effects = [
"writes image file to output_path",

View File

@@ -242,6 +242,17 @@ class ImageSelector(BaseTool):
props = tool.input_schema.get("properties", {})
if "query" in props and "query" not in adapted:
adapted["query"] = adapted.get("prompt", "")
# Normalize the selector's shared reference-image inputs for
# providers whose native contract accepts an ``images`` array.
if "images" in props and "images" not in adapted:
refs = (
adapted.get("image_paths")
or adapted.get("image_urls")
or ([adapted["image_path"]] if adapted.get("image_path") else None)
or ([adapted["image_url"]] if adapted.get("image_url") else None)
)
if refs:
adapted["images"] = refs
# Strip selector-only keys that downstream tools don't understand
adapted.pop("preferred_provider", None)