calesthio/OpenMontage · error · ValueError

Kling TTS response item contained no downloadable URL: {item

Error message

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

What it means

Raised as ValueError by the static _output_url helper when a single audios entry has none of the known URL keys: url, audio_url, resource_url, or resource.url. It is a per-item shape check with the entire item embedded in the message, so the offending object is visible in the error.

Source

Thrown at tools/audio/kling_tts.py:302

        base_path = Path(inputs.get("output_path", "kling_tts.mp3"))
        paths: list[Path] = []
        for index, item in enumerate(outputs):
            url = self._output_url(item)
            suffix = extension_from_url(url, ".mp3")
            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("audio_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 TTS response item contained no downloadable URL: {item}")

    @staticmethod
    def _copy_common_task_fields(inputs: dict[str, Any], payload: dict[str, Any]) -> None:
        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"]

    @staticmethod
    def _callback_result_data(inputs: dict[str, Any], task_id: str) -> dict[str, Any]:
        callback_url = inputs.get("callback_url")
        if not callback_url:
            return {}
        return {
            "callback_url": str(callback_url),
            "callback_requested": True,
            "polling_used": True,

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Inspect the embedded item in the error message to find the new key name
  2. Extend _output_url's key chain (url → audio_url → resource_url → resource.url) with the new key
  3. If the item holds an id/path, add a follow-up fetch to resolve it to a URL

Example fix

// before
url = item.get("url") or item.get("audio_url") or item.get("resource_url")

// after
url = (
    item.get("url")
    or item.get("audio_url")
    or item.get("resource_url")
    or item.get("file_url")  # new upstream key
)
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

def is_downloadable_item(item: Any) -> bool:
    if not isinstance(item, dict):
        return False
    resource = item.get("resource")
    return bool(
        item.get("url") or item.get("audio_url") or item.get("resource_url")
        or (isinstance(resource, dict) and resource.get("url"))
    )

Try / catch

try:
    paths = kling_tts_tool._download_audios(client, outputs, inputs)
except ValueError as e:
    if "no downloadable URL" in str(e):
        logger.error("kling audio item schema changed: %s", e)
    raise

Prevention

When it happens

Trigger: Kling changes the per-audio object schema (new key like 'file_url'); item contains only a task-relative path or an id requiring a second fetch; gateway strips URL fields.

Common situations: API version drift adding/renameing URL fields; items that reference a resource endpoint instead of a direct URL; test fixtures built from outdated docs.

Related errors


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