From 2a2ed6088d4c011225def70211ed85b728d9c430 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 10:47:14 +0000 Subject: [PATCH] fix(tools): tolerate description=null and name=null in workflow catalogue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real KeeperHub catalogues sometimes return workflow entries with description: null. The auto-generated tool builder was calling .strip() on that None, crashing build_workflow_tools() and silently falling back to just the 5 static tools at server startup. Coerce description/name through str() with slug as final fallback, and add a regression test. Co-Authored-By: Данила Вербина --- src/keeperkit/tools/_common.py | 3 ++- tests/test_tool_specs.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/keeperkit/tools/_common.py b/src/keeperkit/tools/_common.py index 93ca059..a828775 100644 --- a/src/keeperkit/tools/_common.py +++ b/src/keeperkit/tools/_common.py @@ -263,7 +263,8 @@ def build_workflow_tools(client: _ClientProto) -> list[ToolSpec]: wf_type = wf.get("workflowType") or "read" chain = wf.get("chain") - descr_parts = [wf.get("description", "").strip() or wf.get("name", slug)] + raw_descr = wf.get("description") or wf.get("name") or slug + descr_parts = [str(raw_descr).strip() or slug] descr_parts.append(f"[type={wf_type}, slug='{slug}'" + (f", chain={chain}" if chain else "") + (f", price={price} USDC" if price else ", free") diff --git a/tests/test_tool_specs.py b/tests/test_tool_specs.py index 0482ac5..00b91ac 100644 --- a/tests/test_tool_specs.py +++ b/tests/test_tool_specs.py @@ -40,6 +40,26 @@ def test_build_all_combines_static_and_workflow(): assert len(all_specs) == len(STATIC_TOOL_SPECS) + len(build_workflow_tools(client)) +def test_workflow_tools_handle_null_description_and_name(): + """Real KeeperHub catalogue can return entries with description=null; + the spec builder must fall back to name/slug instead of crashing.""" + client = MockKeeperHubClient(catalogue=[ + { + "id": "wf_null_desc", + "name": None, + "description": None, + "listedSlug": "weird-workflow", + "workflowType": "read", + "priceUsdcPerCall": None, + "inputSchema": {"type": "object", "properties": {}}, + } + ]) + specs = build_workflow_tools(client) + assert len(specs) == 1 + assert specs[0].name == "keeperhub_weird_workflow" + assert "weird-workflow" in specs[0].description + + def test_safe_dispatch_handles_payment_required(): client = MockKeeperHubClient() specs = build_workflow_tools(client)