From 327bb7a0cee684458f59f25c794e7944a8e837f2 Mon Sep 17 00:00:00 2001 From: ayaangazali Date: Wed, 5 Aug 2026 11:14:01 -0700 Subject: [PATCH] fix(soul): name the config fix in the unsupported-capability error The error stated which capability was missing but not how to resolve it. For manually configured models, input capabilities like image_in come only from the model entry's `capabilities` setting, so a user hitting this had no indication of what to change. The message now names that setting and shows the exact value to declare. Two inline snapshots pinned the old text and were regenerated. --- CHANGELOG.md | 1 + src/kimi_cli/soul/__init__.py | 6 +++- tests/core/test_exceptions.py | 2 +- tests/core/test_llm_not_supported_message.py | 34 ++++++++++++++++++++ tests_e2e/test_wire_errors.py | 2 +- 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 tests/core/test_llm_not_supported_message.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e500e09c2..bbe88600f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Only write entries that are worth mentioning to users. ## Unreleased - Kosong: Stop sending an empty `anthropic-beta` header when no beta features are declared — adaptive thinking removes the interleaved-thinking beta, which previously left an empty header value that some backends reject +- Core: Point the unsupported-capability error at the fix, naming the `capabilities` setting to declare on the model entry instead of only reporting which capability was missing ## 1.49.0 (2026-07-16) diff --git a/src/kimi_cli/soul/__init__.py b/src/kimi_cli/soul/__init__.py index 5cde510dc5..0f20e41789 100644 --- a/src/kimi_cli/soul/__init__.py +++ b/src/kimi_cli/soul/__init__.py @@ -34,9 +34,13 @@ def __init__(self, llm: LLM, capabilities: list[ModelCapability]): self.llm = llm self.capabilities = capabilities capabilities_str = "capability" if len(capabilities) == 1 else "capabilities" + declared = ", ".join(f'"{c}"' for c in sorted(capabilities)) super().__init__( f"LLM model '{llm.model_name}' does not support required {capabilities_str}: " - f"{', '.join(capabilities)}." + f"{', '.join(capabilities)}. " + f"If the model does support this, declare `capabilities = [{declared}]` " + "on its entry in your config file; input capabilities are only taken from " + "that setting for manually configured models." ) diff --git a/tests/core/test_exceptions.py b/tests/core/test_exceptions.py index d0f7282628..93f567ee61 100644 --- a/tests/core/test_exceptions.py +++ b/tests/core/test_exceptions.py @@ -15,7 +15,7 @@ def test_soul_exceptions(llm: LLM): raise LLMNotSupported(llm, ["image_in"]) except LLMNotSupported as e: assert str(e) == snapshot( - "LLM model 'mock' does not support required capability: image_in." + "LLM model 'mock' does not support required capability: image_in. If the model does support this, declare `capabilities = [\"image_in\"]` on its entry in your config file; input capabilities are only taken from that setting for manually configured models." ) try: diff --git a/tests/core/test_llm_not_supported_message.py b/tests/core/test_llm_not_supported_message.py new file mode 100644 index 0000000000..40de45ed21 --- /dev/null +++ b/tests/core/test_llm_not_supported_message.py @@ -0,0 +1,34 @@ +"""The capability error must name the config setting that fixes it. + +Capabilities like `image_in` are only picked up from a model's `capabilities` +entry for manually configured models, so an error that just states the missing +capability leaves the user with no way to act on it. +""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +from kimi_cli.soul import LLMNotSupported + + +def _message(*capabilities: str) -> str: + llm = MagicMock() + llm.model_name = "Qwen3.6-27B" + return str(LLMNotSupported(llm, list(capabilities))) # type: ignore[arg-type] + + +def test_single_capability_message_points_at_the_config_fix() -> None: + message = _message("image_in") + + assert "Qwen3.6-27B" in message + assert "does not support required capability: image_in" in message + assert 'capabilities = ["image_in"]' in message + + +def test_multiple_capabilities_are_listed_in_the_suggested_setting() -> None: + message = _message("video_in", "image_in") + + assert "does not support required capabilities:" in message + # Sorted so the suggestion is stable regardless of set iteration order. + assert 'capabilities = ["image_in", "video_in"]' in message diff --git a/tests_e2e/test_wire_errors.py b/tests_e2e/test_wire_errors.py index 8c08d386fa..f1a1b781a7 100644 --- a/tests_e2e/test_wire_errors.py +++ b/tests_e2e/test_wire_errors.py @@ -164,7 +164,7 @@ def test_llm_not_supported(tmp_path) -> None: { "error": { "code": -32002, - "message": "LLM model 'scripted_echo' does not support required capability: image_in.", + "message": "LLM model 'scripted_echo' does not support required capability: image_in. If the model does support this, declare `capabilities = [\"image_in\"]` on its entry in your config file; input capabilities are only taken from that setting for manually configured models.", "data": None, } }