diff --git a/src/specify_cli/integrations/kimi/__init__.py b/src/specify_cli/integrations/kimi/__init__.py index 07142db854..dafc1aed4e 100644 --- a/src/specify_cli/integrations/kimi/__init__.py +++ b/src/specify_cli/integrations/kimi/__init__.py @@ -12,6 +12,7 @@ from __future__ import annotations import shutil +from collections.abc import Mapping, Sequence from pathlib import Path from typing import Any @@ -61,6 +62,34 @@ def post_process_skill_content(self, content: str) -> str: """Ensure in-skill cross-command references use Kimi's `/skill:` syntax.""" return super().post_process_skill_content(content) + def build_exec_args( + self, + prompt: str, + *, + model: str | None = None, + output_json: bool = True, + integration_args: Sequence[str] | None = None, + integration_options: Mapping[str, Any] | None = None, + project_root: Path | None = None, + ) -> list[str] | None: + """Build CLI arguments for non-interactive ``kimi`` execution. + + ``SkillsIntegration``'s default requests ``--output-format json``, + which Kimi Code CLI does not accept: the option only allows ``text`` + and ``stream-json``, so the CLI exits 1 before the prompt runs. + Structured output maps to ``stream-json`` (JSONL events) instead. + """ + self.validate_runtime_config(integration_args, integration_options) + if not self.config or not self.config.get("requires_cli"): + return None + args = [self._resolve_executable(), "-p", prompt] + self._apply_extra_args_env_var(args) + if model: + args.extend(["--model", model]) + if output_json: + args.extend(["--output-format", "stream-json"]) + return args + @classmethod def options(cls) -> list[IntegrationOption]: return [ diff --git a/tests/integrations/test_integration_kimi.py b/tests/integrations/test_integration_kimi.py index 9e70edba7c..043b3c87b9 100644 --- a/tests/integrations/test_integration_kimi.py +++ b/tests/integrations/test_integration_kimi.py @@ -36,6 +36,70 @@ class TestKimiIntegration(SkillsIntegrationTests): COMMANDS_SUBDIR = "skills" REGISTRAR_DIR = ".kimi-code/skills" + def test_build_exec_args_uses_prompt_flag(self): + integration = get_integration("kimi") + + args = integration.build_exec_args( + "/skill:speckit-specify build a login page", + output_json=False, + ) + + assert args == ["kimi", "-p", "/skill:speckit-specify build a login page"] + + def test_build_exec_args_requests_stream_json(self): + """Kimi only accepts `text` and `stream-json`; `json` exits 1 before the prompt runs.""" + integration = get_integration("kimi") + + args = integration.build_exec_args( + "/skill:speckit-plan add OAuth", output_json=True + ) + + assert args == [ + "kimi", + "-p", + "/skill:speckit-plan add OAuth", + "--output-format", + "stream-json", + ] + assert "json" not in args + + def test_build_exec_args_forwards_model(self): + integration = get_integration("kimi") + + args = integration.build_exec_args( + "/skill:speckit-specify build a login page", + model="kimi-code/k3", + output_json=False, + ) + + assert args == [ + "kimi", + "-p", + "/skill:speckit-specify build a login page", + "--model", + "kimi-code/k3", + ] + + def test_build_exec_args_applies_extra_args(self, monkeypatch): + monkeypatch.setenv("SPECKIT_INTEGRATION_KIMI_EXTRA_ARGS", "--add-dir ../shared") + integration = get_integration("kimi") + + args = integration.build_exec_args( + "check the build", model="kimi-code/k3", output_json=True + ) + + assert args == [ + "kimi", + "-p", + "check the build", + "--add-dir", + "../shared", + "--model", + "kimi-code/k3", + "--output-format", + "stream-json", + ] + class TestKimiOptions: """Kimi declares --skills and --migrate-legacy options."""