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, } }