calesthio/OpenMontage · error · ValueError

Kling image response item contained no downloadable URL: {it

Error message

Kling image response item contained no downloadable URL: {item}

What it means

Raised by KlingOfficialImage._output_url when a completed Kling image task returns a results item that contains none of the URL fields the tool knows how to download ('url', 'image_url', 'resource_url', or a nested 'resource.url'). It means the task reached a terminal state but the response shape does not match any expected layout, so there is nothing to fetch and write to disk. This is almost always an API-side schema change or an unusual response variant rather than a caller mistake.

Source

Thrown at tools/graphics/kling_official_image.py:390

        base_path = Path(inputs.get("output_path", "kling_official_image.png"))
        paths: list[Path] = []
        for index, item in enumerate(outputs):
            url = self._output_url(item)
            suffix = extension_from_url(url, ".png")
            output_path = numbered_output_path(output_path_with_suffix(base_path, suffix), index, suffix)
            client.download(url, output_path)
            paths.append(output_path)
        return paths

    @staticmethod
    def _output_url(item: dict[str, Any]) -> str:
        url = item.get("url") or item.get("image_url") or item.get("resource_url")
        if url:
            return str(url)
        resource = item.get("resource") or {}
        if isinstance(resource, dict) and resource.get("url"):
            return str(resource["url"])
        raise ValueError(f"Kling image response item contained no downloadable URL: {item}")

    @staticmethod
    def _prompt(inputs: dict[str, Any]) -> str:
        prompt = str(inputs.get("prompt") or "").strip()
        if not prompt:
            raise ValueError("prompt is required")
        return prompt

    @staticmethod
    def _copy_common_task_fields(inputs: dict[str, Any], payload: dict[str, Any]) -> None:
        if "watermark" in inputs:
            payload["watermark_info"] = {"enabled": bool(inputs.get("watermark"))}
        callback_url = validate_callback_url(inputs.get("callback_url"))
        if callback_url:
            payload["callback_url"] = callback_url
        if inputs.get("external_task_id"):
            payload["external_task_id"] = inputs["external_task_id"]

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Print/inspect the full item dict from the exception message to see which field actually carries the URL
  2. Add the observed key to the _output_url candidate chain (url/image_url/resource_url/resource.url)
  3. Check the Kling API changelog for the model used and update the tool
  4. If items contain base64 data instead of URLs, decode and write directly instead of downloading

Example fix

// before
url = item.get("url") or item.get("image_url") or item.get("resource_url")
// after
url = (
    item.get("url")
    or item.get("image_url")
    or item.get("resource_url")
    or (item.get("resource") or {}).get("url")
    or item.get("download_url")  # newly observed field
)
Defensive patterns

Strategy: try-catch

Validate before calling

def has_downloadable_url(item: dict) -> bool:
    if item.get("url") or item.get("image_url") or item.get("resource_url"):
        return True
    res = item.get("resource")
    return isinstance(res, dict) and bool(res.get("url"))

Type guard

def is_kling_result_item(item: Any) -> bool:
    return isinstance(item, dict) and bool(
        item.get("url") or item.get("image_url")
        or item.get("resource_url")
        or (isinstance(item.get("resource"), dict) and item["resource"].get("url"))
    )

Try / catch

try:
    paths = tool.run(inputs)
except ValueError as e:
    if "no downloadable URL" in str(e):
        logger.error("Kling response schema drift; item=%s", e)
        # surface item payload for schema fix; do not retry unchanged
    raise

Prevention

When it happens

Trigger: Calling kling_official_image with a prompt that succeeds at task level; the polled task detail returns items in results whose entries only carry, e.g., a base64 field or a differently-named key. Also triggered if the API starts wrapping URLs one level deeper than 'resource'.

Common situations: Kling ships a new model variant whose response payload differs; a proxy/gateway rewrites the response; the tool's supported field list falls behind the live API version.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/470e9dcbabbe5fae. Report an issue: GitHub.