kimi-cli 0.52__tar.gz → 0.56__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kimi-cli might be problematic. Click here for more details.
- {kimi_cli-0.52 → kimi_cli-0.56}/PKG-INFO +2 -2
- {kimi_cli-0.52 → kimi_cli-0.56}/pyproject.toml +5 -5
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/CHANGELOG.md +33 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/agents/default/agent.yaml +1 -1
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/agentspec.py +17 -8
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/app.py +29 -5
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/cli.py +49 -23
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/config.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/constant.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/exception.py +3 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/llm.py +22 -10
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/metadata.py +7 -3
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/prompts/__init__.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/session.py +7 -27
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/share.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/__init__.py +10 -7
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/agent.py +10 -5
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/approval.py +8 -1
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/compaction.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/context.py +3 -1
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/denwarenji.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/kimisoul.py +25 -14
- kimi_cli-0.56/src/kimi_cli/soul/message.py +74 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/runtime.py +10 -27
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/soul/toolset.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/glob.py +3 -4
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/mcp.py +29 -4
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/task/__init__.py +14 -4
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/todo/__init__.py +7 -1
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/web/fetch.py +8 -3
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/acp/__init__.py +18 -11
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/print/__init__.py +11 -38
- kimi_cli-0.56/src/kimi_cli/ui/print/visualize.py +129 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/__init__.py +14 -1
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/console.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/debug.py +3 -1
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/keyboard.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/metacmd.py +23 -10
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/prompt.py +5 -3
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/replay.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/setup.py +4 -2
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/update.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/shell/visualize.py +15 -26
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/wire/__init__.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/wire/jsonrpc.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/aiohttp.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/changelog.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/clipboard.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/logging.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/message.py +2 -0
- kimi_cli-0.56/src/kimi_cli/utils/path.py +86 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/pyinstaller.py +2 -0
- kimi_cli-0.56/src/kimi_cli/utils/rich/columns.py +99 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/signals.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/string.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/term.py +2 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/wire/__init__.py +12 -3
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/wire/message.py +38 -29
- kimi_cli-0.52/src/kimi_cli/soul/message.py +0 -76
- kimi_cli-0.52/src/kimi_cli/utils/path.py +0 -23
- {kimi_cli-0.52 → kimi_cli-0.56}/README.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/agents/default/sub.yaml +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/agents/default/system.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/prompts/compact.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/prompts/init.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/py.typed +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/bash/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/bash/bash.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/bash/cmd.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/dmail/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/dmail/dmail.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/glob.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/grep.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/grep.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/patch.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/patch.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/read.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/read.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/replace.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/replace.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/write.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/file/write.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/task/task.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/test.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/think/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/think/think.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/todo/set_todo_list.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/utils.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/web/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/web/fetch.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/web/search.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/tools/web/search.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/ui/wire/README.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/rich/__init__.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/rich/markdown.py +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/rich/markdown_sample.md +0 -0
- {kimi_cli-0.52 → kimi_cli-0.56}/src/kimi_cli/utils/rich/markdown_sample_short.md +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: kimi-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.56
|
|
4
4
|
Summary: Kimi CLI is your next CLI agent.
|
|
5
5
|
Requires-Dist: agent-client-protocol==0.6.3
|
|
6
6
|
Requires-Dist: aiofiles==25.1.0
|
|
7
7
|
Requires-Dist: aiohttp==3.13.2
|
|
8
8
|
Requires-Dist: typer==0.20.0
|
|
9
|
-
Requires-Dist: kosong==0.
|
|
9
|
+
Requires-Dist: kosong==0.26.1
|
|
10
10
|
Requires-Dist: loguru==0.7.3
|
|
11
11
|
Requires-Dist: patch-ng==1.19.0
|
|
12
12
|
Requires-Dist: prompt-toolkit==3.0.52
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kimi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.56"
|
|
4
4
|
description = "Kimi CLI is your next CLI agent."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -9,7 +9,7 @@ dependencies = [
|
|
|
9
9
|
"aiofiles==25.1.0",
|
|
10
10
|
"aiohttp==3.13.2",
|
|
11
11
|
"typer==0.20.0",
|
|
12
|
-
"kosong==0.
|
|
12
|
+
"kosong==0.26.1",
|
|
13
13
|
"loguru==0.7.3",
|
|
14
14
|
"patch-ng==1.19.0",
|
|
15
15
|
"prompt-toolkit==3.0.52",
|
|
@@ -30,8 +30,8 @@ dev = [
|
|
|
30
30
|
"inline-snapshot[black]>=0.31.1",
|
|
31
31
|
"pyinstaller>=6.16.0",
|
|
32
32
|
"pyright>=1.1.407",
|
|
33
|
-
"pytest>=
|
|
34
|
-
"pytest-asyncio>=1.
|
|
33
|
+
"pytest>=9.0.1",
|
|
34
|
+
"pytest-asyncio>=1.3.0",
|
|
35
35
|
"ruff>=0.14.4",
|
|
36
36
|
]
|
|
37
37
|
|
|
@@ -40,7 +40,7 @@ requires = ["uv_build>=0.8.5,<0.9.0"]
|
|
|
40
40
|
build-backend = "uv_build"
|
|
41
41
|
|
|
42
42
|
[tool.uv.build-backend]
|
|
43
|
-
module-name =
|
|
43
|
+
module-name = "kimi_cli"
|
|
44
44
|
source-exclude = ["tests/**/*", "src/kimi_cli/deps/**/*"]
|
|
45
45
|
|
|
46
46
|
[project.scripts]
|
|
@@ -9,6 +9,39 @@ Internal builds may append content to the Unreleased section.
|
|
|
9
9
|
Only write entries that are worth mentioning to users.
|
|
10
10
|
-->
|
|
11
11
|
|
|
12
|
+
## [0.56] - 2025-11-19
|
|
13
|
+
|
|
14
|
+
- LLM: Add support for Google GenAI provider
|
|
15
|
+
|
|
16
|
+
## [0.55] - 2025-11-18
|
|
17
|
+
|
|
18
|
+
- Lib: Add `kimi_cli.app.enable_logging` function to enable logging when directly using `KimiCLI` class
|
|
19
|
+
- Core: Fix relative path resolution in agent spec files
|
|
20
|
+
- Core: Prevent from panic when LLM API connection failed
|
|
21
|
+
- Tool: Optimize `FetchURL` tool for better content extraction
|
|
22
|
+
- Tool: Increase MCP tool call timeout to 60 seconds
|
|
23
|
+
- Tool: Provide better error message in `Glob` tool when pattern is `**`
|
|
24
|
+
- ACP: Fix thinking content not displayed properly
|
|
25
|
+
- UI: Minor UI improvements in shell mode
|
|
26
|
+
|
|
27
|
+
## [0.54] - 2025-11-13
|
|
28
|
+
|
|
29
|
+
- Lib: Move `WireMessage` from `kimi_cli.wire.message` to `kimi_cli.wire`
|
|
30
|
+
- Print: Fix `stream-json` output format missing the last assistant message
|
|
31
|
+
- UI: Add warning when API key is overridden by `KIMI_API_KEY` environment variable
|
|
32
|
+
- UI: Make a bell sound when there's an approval request
|
|
33
|
+
- Core: Fix context compaction and clearing on Windows
|
|
34
|
+
|
|
35
|
+
## [0.53] - 2025-11-12
|
|
36
|
+
|
|
37
|
+
- UI: Remove unnecessary trailing spaces in console output
|
|
38
|
+
- Core: Throw error when there are unsupported message parts
|
|
39
|
+
- MetaCmd: Add `/yolo` meta command to enable YOLO mode after startup
|
|
40
|
+
- Tool: Add approval request for MCP tools
|
|
41
|
+
- Tool: Disable `Think` tool in default agent
|
|
42
|
+
- CLI: Restore thinking mode from last time when `--thinking` is not specified
|
|
43
|
+
- CLI: Fix `/reload` not working in binary packed by PyInstaller
|
|
44
|
+
|
|
12
45
|
## [0.52] - 2025-11-10
|
|
13
46
|
|
|
14
47
|
- CLI: Remove `--ui` option in favor of `--print`, `--acp`, and `--wire` flags (shell is still the default)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
1
4
|
from pathlib import Path
|
|
2
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
3
6
|
|
|
4
7
|
import yaml
|
|
5
8
|
from pydantic import BaseModel, Field
|
|
@@ -27,7 +30,7 @@ class AgentSpec(BaseModel):
|
|
|
27
30
|
)
|
|
28
31
|
tools: list[str] | None = Field(default=None, description="Tools") # required
|
|
29
32
|
exclude_tools: list[str] | None = Field(default=None, description="Tools to exclude")
|
|
30
|
-
subagents: dict[str,
|
|
33
|
+
subagents: dict[str, SubagentSpec] | None = Field(default=None, description="Subagents")
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
class SubagentSpec(BaseModel):
|
|
@@ -37,7 +40,8 @@ class SubagentSpec(BaseModel):
|
|
|
37
40
|
description: str = Field(description="Subagent description")
|
|
38
41
|
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
44
|
+
class ResolvedAgentSpec:
|
|
41
45
|
"""Resolved agent specification."""
|
|
42
46
|
|
|
43
47
|
name: str
|
|
@@ -45,7 +49,7 @@ class ResolvedAgentSpec(NamedTuple):
|
|
|
45
49
|
system_prompt_args: dict[str, str]
|
|
46
50
|
tools: list[str]
|
|
47
51
|
exclude_tools: list[str]
|
|
48
|
-
subagents: dict[str,
|
|
52
|
+
subagents: dict[str, SubagentSpec]
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
|
|
@@ -75,7 +79,10 @@ def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
|
|
|
75
79
|
|
|
76
80
|
|
|
77
81
|
def _load_agent_spec(agent_file: Path) -> AgentSpec:
|
|
78
|
-
|
|
82
|
+
if not agent_file.exists():
|
|
83
|
+
raise AgentSpecError(f"Agent spec file not found: {agent_file}")
|
|
84
|
+
if not agent_file.is_file():
|
|
85
|
+
raise AgentSpecError(f"Agent spec path is not a file: {agent_file}")
|
|
79
86
|
try:
|
|
80
87
|
with open(agent_file, encoding="utf-8") as f:
|
|
81
88
|
data: dict[str, Any] = yaml.safe_load(f)
|
|
@@ -88,15 +95,17 @@ def _load_agent_spec(agent_file: Path) -> AgentSpec:
|
|
|
88
95
|
|
|
89
96
|
agent_spec = AgentSpec(**data.get("agent", {}))
|
|
90
97
|
if agent_spec.system_prompt_path is not None:
|
|
91
|
-
agent_spec.system_prompt_path =
|
|
98
|
+
agent_spec.system_prompt_path = (
|
|
99
|
+
agent_file.parent / agent_spec.system_prompt_path
|
|
100
|
+
).absolute()
|
|
92
101
|
if agent_spec.subagents is not None:
|
|
93
102
|
for v in agent_spec.subagents.values():
|
|
94
|
-
v.path = agent_file.parent / v.path
|
|
103
|
+
v.path = (agent_file.parent / v.path).absolute()
|
|
95
104
|
if agent_spec.extend:
|
|
96
105
|
if agent_spec.extend == "default":
|
|
97
106
|
base_agent_file = DEFAULT_AGENT_FILE
|
|
98
107
|
else:
|
|
99
|
-
base_agent_file = agent_file.parent / agent_spec.extend
|
|
108
|
+
base_agent_file = (agent_file.parent / agent_spec.extend).absolute()
|
|
100
109
|
base_agent_spec = _load_agent_spec(base_agent_file)
|
|
101
110
|
if agent_spec.name is not None:
|
|
102
111
|
base_agent_spec.name = agent_spec.name
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import contextlib
|
|
2
4
|
import os
|
|
3
5
|
import warnings
|
|
@@ -12,12 +14,26 @@ from kimi_cli.cli import InputFormat, OutputFormat
|
|
|
12
14
|
from kimi_cli.config import LLMModel, LLMProvider, load_config
|
|
13
15
|
from kimi_cli.llm import augment_provider_with_env_vars, create_llm
|
|
14
16
|
from kimi_cli.session import Session
|
|
17
|
+
from kimi_cli.share import get_share_dir
|
|
15
18
|
from kimi_cli.soul import LLMNotSet, LLMNotSupported
|
|
16
19
|
from kimi_cli.soul.agent import load_agent
|
|
17
20
|
from kimi_cli.soul.context import Context
|
|
18
21
|
from kimi_cli.soul.kimisoul import KimiSoul
|
|
19
22
|
from kimi_cli.soul.runtime import Runtime
|
|
20
23
|
from kimi_cli.utils.logging import StreamToLogger, logger
|
|
24
|
+
from kimi_cli.utils.path import shorten_home
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def enable_logging(debug: bool = False) -> None:
|
|
28
|
+
if debug:
|
|
29
|
+
logger.enable("kosong")
|
|
30
|
+
logger.add(
|
|
31
|
+
get_share_dir() / "logs" / "kimi.log",
|
|
32
|
+
# FIXME: configure level for different modules
|
|
33
|
+
level="TRACE" if debug else "INFO",
|
|
34
|
+
rotation="06:00",
|
|
35
|
+
retention="10 days",
|
|
36
|
+
)
|
|
21
37
|
|
|
22
38
|
|
|
23
39
|
class KimiCLI:
|
|
@@ -26,20 +42,18 @@ class KimiCLI:
|
|
|
26
42
|
session: Session,
|
|
27
43
|
*,
|
|
28
44
|
yolo: bool = False,
|
|
29
|
-
stream: bool = True, # TODO: remove this when we have a correct print mode impl
|
|
30
45
|
mcp_configs: list[dict[str, Any]] | None = None,
|
|
31
46
|
config_file: Path | None = None,
|
|
32
47
|
model_name: str | None = None,
|
|
33
48
|
thinking: bool = False,
|
|
34
49
|
agent_file: Path | None = None,
|
|
35
|
-
) ->
|
|
50
|
+
) -> KimiCLI:
|
|
36
51
|
"""
|
|
37
52
|
Create a KimiCLI instance.
|
|
38
53
|
|
|
39
54
|
Args:
|
|
40
55
|
session (Session): A session created by `Session.create` or `Session.continue_`.
|
|
41
56
|
yolo (bool, optional): Approve all actions without confirmation. Defaults to False.
|
|
42
|
-
stream (bool, optional): Use stream mode when calling LLM API. Defaults to True.
|
|
43
57
|
config_file (Path | None, optional): Path to the configuration file. Defaults to None.
|
|
44
58
|
model_name (str | None, optional): Name of the model to use. Defaults to None.
|
|
45
59
|
agent_file (Path | None, optional): Path to the agent file. Defaults to None.
|
|
@@ -79,7 +93,7 @@ class KimiCLI:
|
|
|
79
93
|
else:
|
|
80
94
|
logger.info("Using LLM provider: {provider}", provider=provider)
|
|
81
95
|
logger.info("Using LLM model: {model}", model=model)
|
|
82
|
-
llm = create_llm(provider, model,
|
|
96
|
+
llm = create_llm(provider, model, session_id=session.id)
|
|
83
97
|
|
|
84
98
|
runtime = await Runtime.create(config, llm, session, yolo)
|
|
85
99
|
|
|
@@ -137,7 +151,9 @@ class KimiCLI:
|
|
|
137
151
|
from kimi_cli.ui.shell import ShellApp, WelcomeInfoItem
|
|
138
152
|
|
|
139
153
|
welcome_info = [
|
|
140
|
-
WelcomeInfoItem(
|
|
154
|
+
WelcomeInfoItem(
|
|
155
|
+
name="Directory", value=str(shorten_home(self._runtime.session.work_dir))
|
|
156
|
+
),
|
|
141
157
|
WelcomeInfoItem(name="Session", value=self._runtime.session.id),
|
|
142
158
|
]
|
|
143
159
|
if base_url := self._env_overrides.get("KIMI_BASE_URL"):
|
|
@@ -148,6 +164,14 @@ class KimiCLI:
|
|
|
148
164
|
level=WelcomeInfoItem.Level.WARN,
|
|
149
165
|
)
|
|
150
166
|
)
|
|
167
|
+
if self._env_overrides.get("KIMI_API_KEY"):
|
|
168
|
+
welcome_info.append(
|
|
169
|
+
WelcomeInfoItem(
|
|
170
|
+
name="API Key",
|
|
171
|
+
value="****** (from KIMI_API_KEY)",
|
|
172
|
+
level=WelcomeInfoItem.Level.WARN,
|
|
173
|
+
)
|
|
174
|
+
)
|
|
151
175
|
if not self._runtime.llm:
|
|
152
176
|
welcome_info.append(
|
|
153
177
|
WelcomeInfoItem(
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import json
|
|
3
5
|
import sys
|
|
@@ -185,19 +187,18 @@ def kimi(
|
|
|
185
187
|
),
|
|
186
188
|
] = False,
|
|
187
189
|
thinking: Annotated[
|
|
188
|
-
bool,
|
|
190
|
+
bool | None,
|
|
189
191
|
typer.Option(
|
|
190
192
|
"--thinking",
|
|
191
|
-
help="Enable thinking mode if supported. Default:
|
|
193
|
+
help="Enable thinking mode if supported. Default: same as last time.",
|
|
192
194
|
),
|
|
193
|
-
] =
|
|
195
|
+
] = None,
|
|
194
196
|
):
|
|
195
197
|
"""Kimi, your next CLI agent."""
|
|
196
198
|
del version # handled in the callback
|
|
197
199
|
|
|
198
|
-
from kimi_cli.app import KimiCLI
|
|
200
|
+
from kimi_cli.app import KimiCLI, enable_logging
|
|
199
201
|
from kimi_cli.session import Session
|
|
200
|
-
from kimi_cli.share import get_share_dir
|
|
201
202
|
from kimi_cli.utils.logging import logger
|
|
202
203
|
|
|
203
204
|
def _noop_echo(*args: Any, **kwargs: Any):
|
|
@@ -224,16 +225,7 @@ def kimi(
|
|
|
224
225
|
ui = "wire"
|
|
225
226
|
|
|
226
227
|
echo: Callable[..., None] = typer.echo if verbose else _noop_echo
|
|
227
|
-
|
|
228
|
-
if debug:
|
|
229
|
-
logger.enable("kosong")
|
|
230
|
-
logger.add(
|
|
231
|
-
get_share_dir() / "logs" / "kimi.log",
|
|
232
|
-
# FIXME: configure level for different modules
|
|
233
|
-
level="TRACE" if debug else "INFO",
|
|
234
|
-
rotation="06:00",
|
|
235
|
-
retention="10 days",
|
|
236
|
-
)
|
|
228
|
+
enable_logging(debug)
|
|
237
229
|
|
|
238
230
|
work_dir = (work_dir or Path.cwd()).absolute()
|
|
239
231
|
if continue_:
|
|
@@ -279,20 +271,27 @@ def kimi(
|
|
|
279
271
|
raise typer.BadParameter(f"Invalid JSON: {e}", param_hint="--mcp-config") from e
|
|
280
272
|
|
|
281
273
|
async def _run() -> bool:
|
|
274
|
+
from kimi_cli.metadata import WorkDirMeta, load_metadata, save_metadata
|
|
275
|
+
|
|
276
|
+
if thinking is None:
|
|
277
|
+
metadata = load_metadata()
|
|
278
|
+
thinking_mode = metadata.thinking
|
|
279
|
+
else:
|
|
280
|
+
thinking_mode = thinking
|
|
281
|
+
|
|
282
282
|
instance = await KimiCLI.create(
|
|
283
283
|
session,
|
|
284
284
|
yolo=yolo or (ui == "print"), # print mode implies yolo
|
|
285
|
-
stream=ui != "print", # use non-streaming mode only for print UI
|
|
286
285
|
mcp_configs=mcp_configs,
|
|
287
286
|
model_name=model_name,
|
|
288
|
-
thinking=
|
|
287
|
+
thinking=thinking_mode,
|
|
289
288
|
agent_file=agent_file,
|
|
290
289
|
)
|
|
291
290
|
match ui:
|
|
292
291
|
case "shell":
|
|
293
|
-
|
|
292
|
+
succeeded = await instance.run_shell_mode(command)
|
|
294
293
|
case "print":
|
|
295
|
-
|
|
294
|
+
succeeded = await instance.run_print_mode(
|
|
296
295
|
input_format or "text",
|
|
297
296
|
output_format or "text",
|
|
298
297
|
command,
|
|
@@ -300,17 +299,41 @@ def kimi(
|
|
|
300
299
|
case "acp":
|
|
301
300
|
if command is not None:
|
|
302
301
|
logger.warning("ACP server ignores command argument")
|
|
303
|
-
|
|
302
|
+
succeeded = await instance.run_acp_server()
|
|
304
303
|
case "wire":
|
|
305
304
|
if command is not None:
|
|
306
305
|
logger.warning("Wire server ignores command argument")
|
|
307
|
-
|
|
306
|
+
succeeded = await instance.run_wire_server()
|
|
307
|
+
|
|
308
|
+
if succeeded:
|
|
309
|
+
metadata = load_metadata()
|
|
310
|
+
|
|
311
|
+
# Update work_dir metadata with last session
|
|
312
|
+
work_dir_meta = next(
|
|
313
|
+
(wd for wd in metadata.work_dirs if wd.path == str(session.work_dir)), None
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if work_dir_meta is None:
|
|
317
|
+
logger.warning(
|
|
318
|
+
"Work dir metadata missing when marking last session, recreating: {work_dir}",
|
|
319
|
+
work_dir=session.work_dir,
|
|
320
|
+
)
|
|
321
|
+
work_dir_meta = WorkDirMeta(path=str(session.work_dir))
|
|
322
|
+
metadata.work_dirs.append(work_dir_meta)
|
|
323
|
+
|
|
324
|
+
work_dir_meta.last_session_id = session.id
|
|
325
|
+
|
|
326
|
+
# Update thinking mode
|
|
327
|
+
metadata.thinking = instance.soul.thinking
|
|
328
|
+
|
|
329
|
+
save_metadata(metadata)
|
|
330
|
+
|
|
331
|
+
return succeeded
|
|
308
332
|
|
|
309
333
|
while True:
|
|
310
334
|
try:
|
|
311
335
|
succeeded = asyncio.run(_run())
|
|
312
336
|
if succeeded:
|
|
313
|
-
session.mark_as_last()
|
|
314
337
|
break
|
|
315
338
|
sys.exit(1)
|
|
316
339
|
except Reload:
|
|
@@ -318,4 +341,7 @@ def kimi(
|
|
|
318
341
|
|
|
319
342
|
|
|
320
343
|
if __name__ == "__main__":
|
|
321
|
-
cli
|
|
344
|
+
if "kimi_cli.cli" not in sys.modules:
|
|
345
|
+
sys.modules["kimi_cli.cli"] = sys.modules[__name__]
|
|
346
|
+
|
|
347
|
+
sys.exit(cli())
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from typing import TYPE_CHECKING, Literal, cast, get_args
|
|
@@ -10,7 +12,14 @@ from kimi_cli.constant import USER_AGENT
|
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from kimi_cli.config import LLMModel, LLMProvider
|
|
12
14
|
|
|
13
|
-
type ProviderType = Literal[
|
|
15
|
+
type ProviderType = Literal[
|
|
16
|
+
"kimi",
|
|
17
|
+
"openai_legacy",
|
|
18
|
+
"openai_responses",
|
|
19
|
+
"anthropic",
|
|
20
|
+
"google_genai",
|
|
21
|
+
"_chaos",
|
|
22
|
+
]
|
|
14
23
|
|
|
15
24
|
type ModelCapability = Literal["image_in", "thinking"]
|
|
16
25
|
ALL_MODEL_CAPABILITIES: set[ModelCapability] = set(get_args(ModelCapability))
|
|
@@ -27,7 +36,7 @@ class LLM:
|
|
|
27
36
|
return self.chat_provider.model_name
|
|
28
37
|
|
|
29
38
|
|
|
30
|
-
def augment_provider_with_env_vars(provider:
|
|
39
|
+
def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel) -> dict[str, str]:
|
|
31
40
|
"""Override provider/model settings from environment variables.
|
|
32
41
|
|
|
33
42
|
Returns:
|
|
@@ -69,10 +78,9 @@ def augment_provider_with_env_vars(provider: "LLMProvider", model: "LLMModel") -
|
|
|
69
78
|
|
|
70
79
|
|
|
71
80
|
def create_llm(
|
|
72
|
-
provider:
|
|
73
|
-
model:
|
|
81
|
+
provider: LLMProvider,
|
|
82
|
+
model: LLMModel,
|
|
74
83
|
*,
|
|
75
|
-
stream: bool = True,
|
|
76
84
|
session_id: str | None = None,
|
|
77
85
|
) -> LLM:
|
|
78
86
|
match provider.type:
|
|
@@ -83,7 +91,6 @@ def create_llm(
|
|
|
83
91
|
model=model.model,
|
|
84
92
|
base_url=provider.base_url,
|
|
85
93
|
api_key=provider.api_key.get_secret_value(),
|
|
86
|
-
stream=stream,
|
|
87
94
|
default_headers={
|
|
88
95
|
"User-Agent": USER_AGENT,
|
|
89
96
|
**(provider.custom_headers or {}),
|
|
@@ -98,7 +105,6 @@ def create_llm(
|
|
|
98
105
|
model=model.model,
|
|
99
106
|
base_url=provider.base_url,
|
|
100
107
|
api_key=provider.api_key.get_secret_value(),
|
|
101
|
-
stream=stream,
|
|
102
108
|
)
|
|
103
109
|
case "openai_responses":
|
|
104
110
|
from kosong.contrib.chat_provider.openai_responses import OpenAIResponses
|
|
@@ -107,7 +113,6 @@ def create_llm(
|
|
|
107
113
|
model=model.model,
|
|
108
114
|
base_url=provider.base_url,
|
|
109
115
|
api_key=provider.api_key.get_secret_value(),
|
|
110
|
-
stream=stream,
|
|
111
116
|
)
|
|
112
117
|
case "anthropic":
|
|
113
118
|
from kosong.contrib.chat_provider.anthropic import Anthropic
|
|
@@ -116,9 +121,16 @@ def create_llm(
|
|
|
116
121
|
model=model.model,
|
|
117
122
|
base_url=provider.base_url,
|
|
118
123
|
api_key=provider.api_key.get_secret_value(),
|
|
119
|
-
stream=stream,
|
|
120
124
|
default_max_tokens=50000,
|
|
121
125
|
)
|
|
126
|
+
case "google_genai":
|
|
127
|
+
from kosong.contrib.chat_provider.google_genai import GoogleGenAI
|
|
128
|
+
|
|
129
|
+
chat_provider = GoogleGenAI(
|
|
130
|
+
model=model.model,
|
|
131
|
+
base_url=provider.base_url,
|
|
132
|
+
api_key=provider.api_key.get_secret_value(),
|
|
133
|
+
)
|
|
122
134
|
case "_chaos":
|
|
123
135
|
from kosong.chat_provider.chaos import ChaosChatProvider, ChaosConfig
|
|
124
136
|
|
|
@@ -139,7 +151,7 @@ def create_llm(
|
|
|
139
151
|
)
|
|
140
152
|
|
|
141
153
|
|
|
142
|
-
def _derive_capabilities(provider:
|
|
154
|
+
def _derive_capabilities(provider: LLMProvider, model: LLMModel) -> set[ModelCapability]:
|
|
143
155
|
capabilities = model.capabilities or set()
|
|
144
156
|
if provider.type != "kimi":
|
|
145
157
|
return capabilities
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
from hashlib import md5
|
|
3
5
|
from pathlib import Path
|
|
@@ -31,9 +33,11 @@ class WorkDirMeta(BaseModel):
|
|
|
31
33
|
class Metadata(BaseModel):
|
|
32
34
|
"""Kimi metadata structure."""
|
|
33
35
|
|
|
34
|
-
work_dirs: list[WorkDirMeta] = Field(
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
work_dirs: list[WorkDirMeta] = Field(default_factory=list[WorkDirMeta])
|
|
37
|
+
"""Work directory list."""
|
|
38
|
+
|
|
39
|
+
thinking: bool = False
|
|
40
|
+
"""Whether the last session was in thinking mode."""
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
def load_metadata() -> Metadata:
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import uuid
|
|
4
|
+
from dataclasses import dataclass
|
|
2
5
|
from pathlib import Path
|
|
3
|
-
from typing import NamedTuple
|
|
4
6
|
|
|
5
7
|
from kimi_cli.metadata import WorkDirMeta, load_metadata, save_metadata
|
|
6
8
|
from kimi_cli.utils.logging import logger
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
12
|
+
class Session:
|
|
10
13
|
"""A session of a work directory."""
|
|
11
14
|
|
|
12
15
|
id: str
|
|
@@ -14,7 +17,7 @@ class Session(NamedTuple):
|
|
|
14
17
|
history_file: Path
|
|
15
18
|
|
|
16
19
|
@staticmethod
|
|
17
|
-
def create(work_dir: Path, _history_file: Path | None = None) ->
|
|
20
|
+
def create(work_dir: Path, _history_file: Path | None = None) -> Session:
|
|
18
21
|
"""Create a new session for a work directory."""
|
|
19
22
|
logger.debug("Creating new session for work directory: {work_dir}", work_dir=work_dir)
|
|
20
23
|
|
|
@@ -53,7 +56,7 @@ class Session(NamedTuple):
|
|
|
53
56
|
)
|
|
54
57
|
|
|
55
58
|
@staticmethod
|
|
56
|
-
def continue_(work_dir: Path) ->
|
|
59
|
+
def continue_(work_dir: Path) -> Session | None:
|
|
57
60
|
"""Get the last session for a work directory."""
|
|
58
61
|
logger.debug("Continuing session for work directory: {work_dir}", work_dir=work_dir)
|
|
59
62
|
|
|
@@ -78,26 +81,3 @@ class Session(NamedTuple):
|
|
|
78
81
|
work_dir=work_dir,
|
|
79
82
|
history_file=history_file,
|
|
80
83
|
)
|
|
81
|
-
|
|
82
|
-
def mark_as_last(self) -> None:
|
|
83
|
-
"""Mark this session as the last completed session for its work directory."""
|
|
84
|
-
metadata = load_metadata()
|
|
85
|
-
work_dir_meta = next(
|
|
86
|
-
(wd for wd in metadata.work_dirs if wd.path == str(self.work_dir)), None
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
if work_dir_meta is None:
|
|
90
|
-
logger.warning(
|
|
91
|
-
"Work directory metadata missing when marking last session, recreating: {work_dir}",
|
|
92
|
-
work_dir=self.work_dir,
|
|
93
|
-
)
|
|
94
|
-
work_dir_meta = WorkDirMeta(path=str(self.work_dir))
|
|
95
|
-
metadata.work_dirs.append(work_dir_meta)
|
|
96
|
-
|
|
97
|
-
work_dir_meta.last_session_id = self.id
|
|
98
|
-
logger.debug(
|
|
99
|
-
"Updated last session for work directory: {work_dir} -> {session_id}",
|
|
100
|
-
work_dir=self.work_dir,
|
|
101
|
-
session_id=self.id,
|
|
102
|
-
)
|
|
103
|
-
save_metadata(metadata)
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import contextlib
|
|
3
5
|
from collections.abc import Callable, Coroutine
|
|
4
6
|
from contextvars import ContextVar
|
|
5
|
-
from
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
6
9
|
|
|
7
10
|
from kosong.message import ContentPart
|
|
8
11
|
|
|
9
12
|
from kimi_cli.utils.logging import logger
|
|
10
|
-
from kimi_cli.wire import Wire, WireUISide
|
|
11
|
-
from kimi_cli.wire.message import WireMessage
|
|
13
|
+
from kimi_cli.wire import Wire, WireMessage, WireUISide
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING:
|
|
14
16
|
from kimi_cli.llm import LLM, ModelCapability
|
|
@@ -23,7 +25,7 @@ class LLMNotSet(Exception):
|
|
|
23
25
|
class LLMNotSupported(Exception):
|
|
24
26
|
"""Raised when the LLM does not have required capabilities."""
|
|
25
27
|
|
|
26
|
-
def __init__(self, llm:
|
|
28
|
+
def __init__(self, llm: LLM, capabilities: list[ModelCapability]):
|
|
27
29
|
self.llm = llm
|
|
28
30
|
self.capabilities = capabilities
|
|
29
31
|
capabilities_str = "capability" if len(capabilities) == 1 else "capabilities"
|
|
@@ -43,7 +45,8 @@ class MaxStepsReached(Exception):
|
|
|
43
45
|
self.n_steps = n_steps
|
|
44
46
|
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
@dataclass(frozen=True, slots=True)
|
|
49
|
+
class StatusSnapshot:
|
|
47
50
|
context_usage: float
|
|
48
51
|
"""The usage of the context, in percentage."""
|
|
49
52
|
|
|
@@ -61,7 +64,7 @@ class Soul(Protocol):
|
|
|
61
64
|
...
|
|
62
65
|
|
|
63
66
|
@property
|
|
64
|
-
def model_capabilities(self) ->
|
|
67
|
+
def model_capabilities(self) -> set[ModelCapability] | None:
|
|
65
68
|
"""The capabilities of the LLM model used by the soul. None indicates no LLM configured."""
|
|
66
69
|
...
|
|
67
70
|
|
|
@@ -96,7 +99,7 @@ class RunCancelled(Exception):
|
|
|
96
99
|
|
|
97
100
|
|
|
98
101
|
async def run_soul(
|
|
99
|
-
soul:
|
|
102
|
+
soul: Soul,
|
|
100
103
|
user_input: str | list[ContentPart],
|
|
101
104
|
ui_loop_fn: UILoopFn,
|
|
102
105
|
cancel_event: asyncio.Event,
|