kimi-cli 0.43__tar.gz → 0.47__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.43 → kimi_cli-0.47}/PKG-INFO +4 -3
- {kimi_cli-0.43 → kimi_cli-0.47}/README.md +1 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/pyproject.toml +3 -3
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/CHANGELOG.md +40 -0
- kimi_cli-0.43/src/kimi_cli/__init__.py → kimi_cli-0.47/src/kimi_cli/app.py +62 -52
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/cli.py +23 -8
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/config.py +1 -1
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/llm.py +41 -7
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/agent.py +10 -3
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/runtime.py +6 -4
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/toolset.py +2 -1
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/__init__.py +19 -10
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/bash/__init__.py +13 -4
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/dmail/__init__.py +3 -3
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/glob.py +2 -2
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/grep.py +1 -1
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/patch.py +38 -8
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/read.py +5 -4
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/replace.py +2 -2
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/write.py +2 -2
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/mcp.py +6 -3
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/task/__init__.py +2 -2
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/web/search.py +8 -10
- kimi_cli-0.47/src/kimi_cli/ui/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/print/__init__.py +1 -4
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/__init__.py +11 -2
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/liveview.py +36 -5
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/prompt.py +29 -16
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/setup.py +1 -1
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/visualize.py +7 -3
- kimi_cli-0.47/src/kimi_cli/ui/wire/README.md +109 -0
- kimi_cli-0.47/src/kimi_cli/ui/wire/__init__.py +340 -0
- kimi_cli-0.47/src/kimi_cli/ui/wire/jsonrpc.py +48 -0
- kimi_cli-0.47/src/kimi_cli/utils/clipboard.py +10 -0
- kimi_cli-0.47/src/kimi_cli/wire/message.py +178 -0
- kimi_cli-0.43/src/kimi_cli/wire/message.py +0 -91
- {kimi_cli-0.43/src/kimi_cli/ui → kimi_cli-0.47/src/kimi_cli}/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/agents/default/agent.yaml +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/agents/default/sub.yaml +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/agents/default/system.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/agentspec.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/constant.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/exception.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/metadata.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/prompts/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/prompts/compact.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/prompts/init.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/py.typed +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/session.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/share.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/approval.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/compaction.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/context.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/denwarenji.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/kimisoul.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/soul/message.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/bash/bash.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/dmail/dmail.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/glob.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/grep.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/patch.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/read.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/replace.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/file/write.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/task/task.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/test.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/think/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/think/think.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/todo/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/todo/set_todo_list.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/utils.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/web/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/web/fetch.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/web/fetch.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/tools/web/search.md +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/acp/__init__.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/console.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/debug.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/keyboard.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/metacmd.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/replay.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/ui/shell/update.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/aiohttp.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/changelog.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/logging.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/message.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/path.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/pyinstaller.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/signals.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/utils/string.py +0 -0
- {kimi_cli-0.43 → kimi_cli-0.47}/src/kimi_cli/wire/__init__.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: kimi-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.47
|
|
4
4
|
Summary: Kimi CLI is your next CLI agent.
|
|
5
|
-
Requires-Dist: agent-client-protocol==0.6.
|
|
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: click==8.3.0
|
|
9
|
-
Requires-Dist: kosong==0.
|
|
9
|
+
Requires-Dist: kosong==0.17.0
|
|
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
|
|
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
[](https://github.com/MoonshotAI/kimi-cli/actions)
|
|
30
30
|
[](https://pypi.org/project/kimi-cli/)
|
|
31
31
|
[](https://pypistats.org/packages/kimi-cli)
|
|
32
|
+
[](https://deepwiki.com/MoonshotAI/kimi-cli)
|
|
32
33
|
|
|
33
34
|
[中文](https://www.kimi.com/coding/docs/kimi-cli.html)
|
|
34
35
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://github.com/MoonshotAI/kimi-cli/actions)
|
|
5
5
|
[](https://pypi.org/project/kimi-cli/)
|
|
6
6
|
[](https://pypistats.org/packages/kimi-cli)
|
|
7
|
+
[](https://deepwiki.com/MoonshotAI/kimi-cli)
|
|
7
8
|
|
|
8
9
|
[中文](https://www.kimi.com/coding/docs/kimi-cli.html)
|
|
9
10
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kimi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.47"
|
|
4
4
|
description = "Kimi CLI is your next CLI agent."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"agent-client-protocol==0.6.
|
|
8
|
+
"agent-client-protocol==0.6.3",
|
|
9
9
|
"aiofiles==25.1.0",
|
|
10
10
|
"aiohttp==3.13.2",
|
|
11
11
|
"click==8.3.0",
|
|
12
|
-
"kosong==0.
|
|
12
|
+
"kosong==0.17.0",
|
|
13
13
|
"loguru==0.7.3",
|
|
14
14
|
"patch-ng==1.19.0",
|
|
15
15
|
"prompt-toolkit==3.0.52",
|
|
@@ -9,6 +9,46 @@ 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.47] - 2025-11-05
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fix Ctrl-W not working in some environments
|
|
17
|
+
- Do not load SearchWeb tool when the search service is not configured
|
|
18
|
+
|
|
19
|
+
## [0.46] - 2025-11-03
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Introduce Wire over stdio for local IPC (experimental, subject to change)
|
|
24
|
+
- Support Anthropic provider type
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Fix binary packed by PyInstaller not working due to wrong entrypoint
|
|
29
|
+
|
|
30
|
+
## [0.45] - 2025-10-31
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Allow `KIMI_MODEL_CAPABILITIES` environment variable to override model capabilities
|
|
35
|
+
- Add `--no-markdown` option to disable markdown rendering
|
|
36
|
+
- Support `openai_responses` LLM provider type
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Fix crash when continuing a session
|
|
41
|
+
|
|
42
|
+
## [0.44] - 2025-10-30
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Improve startup time
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- Fix potential invalid bytes in user input
|
|
51
|
+
|
|
12
52
|
## [0.43] - 2025-10-30
|
|
13
53
|
|
|
14
54
|
### Added
|
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
|
8
8
|
from pydantic import SecretStr
|
|
9
9
|
|
|
10
10
|
from kimi_cli.agentspec import DEFAULT_AGENT_FILE
|
|
11
|
+
from kimi_cli.cli import InputFormat, OutputFormat
|
|
11
12
|
from kimi_cli.config import LLMModel, LLMProvider, load_config
|
|
12
13
|
from kimi_cli.llm import augment_provider_with_env_vars, create_llm
|
|
13
14
|
from kimi_cli.session import Session
|
|
@@ -15,9 +16,6 @@ from kimi_cli.soul.agent import load_agent
|
|
|
15
16
|
from kimi_cli.soul.context import Context
|
|
16
17
|
from kimi_cli.soul.kimisoul import KimiSoul
|
|
17
18
|
from kimi_cli.soul.runtime import Runtime
|
|
18
|
-
from kimi_cli.ui.acp import ACPServer
|
|
19
|
-
from kimi_cli.ui.print import InputFormat, OutputFormat, PrintApp
|
|
20
|
-
from kimi_cli.ui.shell import ShellApp, WelcomeInfoItem
|
|
21
19
|
from kimi_cli.utils.logging import StreamToLogger, logger
|
|
22
20
|
|
|
23
21
|
|
|
@@ -81,43 +79,6 @@ class KimiCLI:
|
|
|
81
79
|
logger.info("Using LLM model: {model}", model=model)
|
|
82
80
|
llm = create_llm(provider, model, stream=stream, session_id=session.id)
|
|
83
81
|
|
|
84
|
-
welcome_info = [
|
|
85
|
-
WelcomeInfoItem(name="Directory", value=str(session.work_dir)),
|
|
86
|
-
WelcomeInfoItem(name="Session", value=session.id),
|
|
87
|
-
]
|
|
88
|
-
if base_url := env_overrides.get("KIMI_BASE_URL"):
|
|
89
|
-
welcome_info.append(
|
|
90
|
-
WelcomeInfoItem(
|
|
91
|
-
name="API URL",
|
|
92
|
-
value=f"{base_url} (from KIMI_BASE_URL)",
|
|
93
|
-
level=WelcomeInfoItem.Level.WARN,
|
|
94
|
-
)
|
|
95
|
-
)
|
|
96
|
-
if not llm:
|
|
97
|
-
welcome_info.append(
|
|
98
|
-
WelcomeInfoItem(
|
|
99
|
-
name="Model",
|
|
100
|
-
value="not set, send /setup to configure",
|
|
101
|
-
level=WelcomeInfoItem.Level.WARN,
|
|
102
|
-
)
|
|
103
|
-
)
|
|
104
|
-
elif "KIMI_MODEL_NAME" in env_overrides:
|
|
105
|
-
welcome_info.append(
|
|
106
|
-
WelcomeInfoItem(
|
|
107
|
-
name="Model",
|
|
108
|
-
value=f"{model.model} (from KIMI_MODEL_NAME)",
|
|
109
|
-
level=WelcomeInfoItem.Level.WARN,
|
|
110
|
-
)
|
|
111
|
-
)
|
|
112
|
-
else:
|
|
113
|
-
welcome_info.append(
|
|
114
|
-
WelcomeInfoItem(
|
|
115
|
-
name="Model",
|
|
116
|
-
value=model.model,
|
|
117
|
-
level=WelcomeInfoItem.Level.INFO,
|
|
118
|
-
)
|
|
119
|
-
)
|
|
120
|
-
|
|
121
82
|
runtime = await Runtime.create(config, llm, session, yolo)
|
|
122
83
|
|
|
123
84
|
if agent_file is None:
|
|
@@ -132,17 +93,17 @@ class KimiCLI:
|
|
|
132
93
|
runtime,
|
|
133
94
|
context=context,
|
|
134
95
|
)
|
|
135
|
-
return KimiCLI(soul,
|
|
96
|
+
return KimiCLI(soul, runtime, env_overrides)
|
|
136
97
|
|
|
137
98
|
def __init__(
|
|
138
99
|
self,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
100
|
+
_soul: KimiSoul,
|
|
101
|
+
_runtime: Runtime,
|
|
102
|
+
_env_overrides: dict[str, str],
|
|
142
103
|
) -> None:
|
|
143
|
-
self._soul =
|
|
144
|
-
self.
|
|
145
|
-
self.
|
|
104
|
+
self._soul = _soul
|
|
105
|
+
self._runtime = _runtime
|
|
106
|
+
self._env_overrides = _env_overrides
|
|
146
107
|
|
|
147
108
|
@property
|
|
148
109
|
def soul(self) -> KimiSoul:
|
|
@@ -152,12 +113,12 @@ class KimiCLI:
|
|
|
152
113
|
@property
|
|
153
114
|
def session(self) -> Session:
|
|
154
115
|
"""Get the Session instance."""
|
|
155
|
-
return self.
|
|
116
|
+
return self._runtime.session
|
|
156
117
|
|
|
157
118
|
@contextlib.contextmanager
|
|
158
119
|
def _app_env(self) -> Generator[None]:
|
|
159
120
|
original_cwd = Path.cwd()
|
|
160
|
-
os.chdir(self.
|
|
121
|
+
os.chdir(self._runtime.session.work_dir)
|
|
161
122
|
try:
|
|
162
123
|
# to ignore possible warnings from dateparser
|
|
163
124
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
@@ -166,9 +127,47 @@ class KimiCLI:
|
|
|
166
127
|
finally:
|
|
167
128
|
os.chdir(original_cwd)
|
|
168
129
|
|
|
169
|
-
async def run_shell_mode(self, command: str | None = None) -> bool:
|
|
130
|
+
async def run_shell_mode(self, command: str | None = None, markdown: bool = True) -> bool:
|
|
131
|
+
from kimi_cli.ui.shell import ShellApp, WelcomeInfoItem
|
|
132
|
+
|
|
133
|
+
welcome_info = [
|
|
134
|
+
WelcomeInfoItem(name="Directory", value=str(self._runtime.session.work_dir)),
|
|
135
|
+
WelcomeInfoItem(name="Session", value=self._runtime.session.id),
|
|
136
|
+
]
|
|
137
|
+
if base_url := self._env_overrides.get("KIMI_BASE_URL"):
|
|
138
|
+
welcome_info.append(
|
|
139
|
+
WelcomeInfoItem(
|
|
140
|
+
name="API URL",
|
|
141
|
+
value=f"{base_url} (from KIMI_BASE_URL)",
|
|
142
|
+
level=WelcomeInfoItem.Level.WARN,
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
if not self._runtime.llm:
|
|
146
|
+
welcome_info.append(
|
|
147
|
+
WelcomeInfoItem(
|
|
148
|
+
name="Model",
|
|
149
|
+
value="not set, send /setup to configure",
|
|
150
|
+
level=WelcomeInfoItem.Level.WARN,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
elif "KIMI_MODEL_NAME" in self._env_overrides:
|
|
154
|
+
welcome_info.append(
|
|
155
|
+
WelcomeInfoItem(
|
|
156
|
+
name="Model",
|
|
157
|
+
value=f"{self._soul.model} (from KIMI_MODEL_NAME)",
|
|
158
|
+
level=WelcomeInfoItem.Level.WARN,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
welcome_info.append(
|
|
163
|
+
WelcomeInfoItem(
|
|
164
|
+
name="Model",
|
|
165
|
+
value=self._soul.model,
|
|
166
|
+
level=WelcomeInfoItem.Level.INFO,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
170
169
|
with self._app_env():
|
|
171
|
-
app = ShellApp(self._soul, welcome_info=
|
|
170
|
+
app = ShellApp(self._soul, welcome_info=welcome_info, markdown=markdown)
|
|
172
171
|
return await app.run(command)
|
|
173
172
|
|
|
174
173
|
async def run_print_mode(
|
|
@@ -177,16 +176,27 @@ class KimiCLI:
|
|
|
177
176
|
output_format: OutputFormat,
|
|
178
177
|
command: str | None = None,
|
|
179
178
|
) -> bool:
|
|
179
|
+
from kimi_cli.ui.print import PrintApp
|
|
180
|
+
|
|
180
181
|
with self._app_env():
|
|
181
182
|
app = PrintApp(
|
|
182
183
|
self._soul,
|
|
183
184
|
input_format,
|
|
184
185
|
output_format,
|
|
185
|
-
self.
|
|
186
|
+
self._runtime.session.history_file,
|
|
186
187
|
)
|
|
187
188
|
return await app.run(command)
|
|
188
189
|
|
|
189
190
|
async def run_acp_server(self) -> bool:
|
|
191
|
+
from kimi_cli.ui.acp import ACPServer
|
|
192
|
+
|
|
190
193
|
with self._app_env():
|
|
191
194
|
app = ACPServer(self._soul)
|
|
192
195
|
return await app.run()
|
|
196
|
+
|
|
197
|
+
async def run_wire_server(self) -> bool:
|
|
198
|
+
from kimi_cli.ui.wire import WireServer
|
|
199
|
+
|
|
200
|
+
with self._app_env():
|
|
201
|
+
server = WireServer(self._soul)
|
|
202
|
+
return await server.run()
|
|
@@ -7,12 +7,7 @@ from typing import Any, Literal, get_args
|
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
|
|
10
|
-
from kimi_cli import KimiCLI
|
|
11
10
|
from kimi_cli.constant import VERSION
|
|
12
|
-
from kimi_cli.session import Session
|
|
13
|
-
from kimi_cli.share import get_share_dir
|
|
14
|
-
from kimi_cli.ui.print import InputFormat, OutputFormat
|
|
15
|
-
from kimi_cli.utils.logging import logger
|
|
16
11
|
|
|
17
12
|
|
|
18
13
|
class Reload(Exception):
|
|
@@ -21,7 +16,9 @@ class Reload(Exception):
|
|
|
21
16
|
pass
|
|
22
17
|
|
|
23
18
|
|
|
24
|
-
UIMode = Literal["shell", "print", "acp"]
|
|
19
|
+
UIMode = Literal["shell", "print", "acp", "wire"]
|
|
20
|
+
InputFormat = Literal["text", "stream-json"]
|
|
21
|
+
OutputFormat = Literal["text", "stream-json"]
|
|
25
22
|
|
|
26
23
|
|
|
27
24
|
@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
|
|
@@ -140,6 +137,12 @@ UIMode = Literal["shell", "print", "acp"]
|
|
|
140
137
|
default=False,
|
|
141
138
|
help="Automatically approve all actions. Default: no.",
|
|
142
139
|
)
|
|
140
|
+
@click.option(
|
|
141
|
+
"--markdown/--no-markdown",
|
|
142
|
+
is_flag=True,
|
|
143
|
+
default=True,
|
|
144
|
+
help="Enable/disable markdown rendering in shell UI. Default: yes.",
|
|
145
|
+
)
|
|
143
146
|
def kimi(
|
|
144
147
|
verbose: bool,
|
|
145
148
|
debug: bool,
|
|
@@ -154,17 +157,25 @@ def kimi(
|
|
|
154
157
|
mcp_config_file: list[Path],
|
|
155
158
|
mcp_config: list[str],
|
|
156
159
|
yolo: bool,
|
|
160
|
+
markdown: bool,
|
|
157
161
|
):
|
|
158
162
|
"""Kimi, your next CLI agent."""
|
|
163
|
+
from kimi_cli.app import KimiCLI
|
|
164
|
+
from kimi_cli.session import Session
|
|
165
|
+
from kimi_cli.share import get_share_dir
|
|
166
|
+
from kimi_cli.utils.logging import logger
|
|
159
167
|
|
|
160
168
|
def _noop_echo(*args: Any, **kwargs: Any):
|
|
161
169
|
pass
|
|
162
170
|
|
|
163
171
|
echo: Callable[..., None] = click.echo if verbose else _noop_echo
|
|
164
172
|
|
|
173
|
+
if debug:
|
|
174
|
+
logger.enable("kosong")
|
|
165
175
|
logger.add(
|
|
166
176
|
get_share_dir() / "logs" / "kimi.log",
|
|
167
|
-
level
|
|
177
|
+
# FIXME: configure level for different modules
|
|
178
|
+
level="TRACE" if debug else "INFO",
|
|
168
179
|
rotation="06:00",
|
|
169
180
|
retention="10 days",
|
|
170
181
|
)
|
|
@@ -219,7 +230,7 @@ def kimi(
|
|
|
219
230
|
)
|
|
220
231
|
match ui:
|
|
221
232
|
case "shell":
|
|
222
|
-
return await instance.run_shell_mode(command)
|
|
233
|
+
return await instance.run_shell_mode(command, markdown=markdown)
|
|
223
234
|
case "print":
|
|
224
235
|
return await instance.run_print_mode(
|
|
225
236
|
input_format or "text",
|
|
@@ -230,6 +241,10 @@ def kimi(
|
|
|
230
241
|
if command is not None:
|
|
231
242
|
logger.warning("ACP server ignores command argument")
|
|
232
243
|
return await instance.run_acp_server()
|
|
244
|
+
case "wire":
|
|
245
|
+
if command is not None:
|
|
246
|
+
logger.warning("Wire server ignores command argument")
|
|
247
|
+
return await instance.run_wire_server()
|
|
233
248
|
|
|
234
249
|
while True:
|
|
235
250
|
try:
|
|
@@ -12,7 +12,7 @@ from kimi_cli.utils.logging import logger
|
|
|
12
12
|
class LLMProvider(BaseModel):
|
|
13
13
|
"""LLM provider configuration."""
|
|
14
14
|
|
|
15
|
-
type: Literal["kimi", "openai_legacy", "_chaos"]
|
|
15
|
+
type: Literal["kimi", "openai_legacy", "openai_responses", "anthropic", "_chaos"]
|
|
16
16
|
"""Provider type"""
|
|
17
17
|
base_url: str
|
|
18
18
|
"""API base URL"""
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import NamedTuple
|
|
2
|
+
from typing import NamedTuple, cast, get_args
|
|
3
3
|
|
|
4
4
|
from kosong.base.chat_provider import ChatProvider
|
|
5
|
-
from kosong.chat_provider.chaos import ChaosChatProvider, ChaosConfig
|
|
6
|
-
from kosong.chat_provider.kimi import Kimi
|
|
7
|
-
from kosong.chat_provider.openai_legacy import OpenAILegacy
|
|
8
5
|
from pydantic import SecretStr
|
|
9
6
|
|
|
10
7
|
from kimi_cli.config import LLMModel, LLMModelCapability, LLMProvider
|
|
@@ -44,11 +41,19 @@ def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel) -> di
|
|
|
44
41
|
applied["KIMI_API_KEY"] = "******"
|
|
45
42
|
if model_name := os.getenv("KIMI_MODEL_NAME"):
|
|
46
43
|
model.model = model_name
|
|
47
|
-
applied["KIMI_MODEL_NAME"] =
|
|
44
|
+
applied["KIMI_MODEL_NAME"] = model_name
|
|
48
45
|
if max_context_size := os.getenv("KIMI_MODEL_MAX_CONTEXT_SIZE"):
|
|
49
46
|
model.max_context_size = int(max_context_size)
|
|
50
|
-
applied["KIMI_MODEL_MAX_CONTEXT_SIZE"] =
|
|
51
|
-
|
|
47
|
+
applied["KIMI_MODEL_MAX_CONTEXT_SIZE"] = max_context_size
|
|
48
|
+
if capabilities := os.getenv("KIMI_MODEL_CAPABILITIES"):
|
|
49
|
+
caps_lower = (cap.strip().lower() for cap in capabilities.split(",") if cap.strip())
|
|
50
|
+
model.capabilities = set(
|
|
51
|
+
cast(LLMModelCapability, cap)
|
|
52
|
+
for cap in caps_lower
|
|
53
|
+
if cap in get_args(LLMModelCapability)
|
|
54
|
+
)
|
|
55
|
+
applied["KIMI_MODEL_CAPABILITIES"] = capabilities
|
|
56
|
+
case "openai_legacy" | "openai_responses":
|
|
52
57
|
if base_url := os.getenv("OPENAI_BASE_URL"):
|
|
53
58
|
provider.base_url = base_url
|
|
54
59
|
if api_key := os.getenv("OPENAI_API_KEY"):
|
|
@@ -68,6 +73,8 @@ def create_llm(
|
|
|
68
73
|
) -> LLM:
|
|
69
74
|
match provider.type:
|
|
70
75
|
case "kimi":
|
|
76
|
+
from kosong.chat_provider.kimi import Kimi
|
|
77
|
+
|
|
71
78
|
chat_provider = Kimi(
|
|
72
79
|
model=model.model,
|
|
73
80
|
base_url=provider.base_url,
|
|
@@ -81,13 +88,40 @@ def create_llm(
|
|
|
81
88
|
if session_id:
|
|
82
89
|
chat_provider = chat_provider.with_generation_kwargs(prompt_cache_key=session_id)
|
|
83
90
|
case "openai_legacy":
|
|
91
|
+
from kosong.chat_provider.openai_legacy import OpenAILegacy
|
|
92
|
+
|
|
84
93
|
chat_provider = OpenAILegacy(
|
|
85
94
|
model=model.model,
|
|
86
95
|
base_url=provider.base_url,
|
|
87
96
|
api_key=provider.api_key.get_secret_value(),
|
|
88
97
|
stream=stream,
|
|
89
98
|
)
|
|
99
|
+
case "openai_responses":
|
|
100
|
+
from kosong.chat_provider.openai_responses import OpenAIResponses
|
|
101
|
+
|
|
102
|
+
chat_provider = OpenAIResponses(
|
|
103
|
+
model=model.model,
|
|
104
|
+
base_url=provider.base_url,
|
|
105
|
+
api_key=provider.api_key.get_secret_value(),
|
|
106
|
+
stream=stream,
|
|
107
|
+
)
|
|
108
|
+
case "anthropic":
|
|
109
|
+
from kosong.chat_provider.anthropic import Anthropic
|
|
110
|
+
|
|
111
|
+
chat_provider = Anthropic(
|
|
112
|
+
model=model.model,
|
|
113
|
+
base_url=provider.base_url,
|
|
114
|
+
api_key=provider.api_key.get_secret_value(),
|
|
115
|
+
stream=stream,
|
|
116
|
+
default_max_tokens=50000,
|
|
117
|
+
).with_generation_kwargs(
|
|
118
|
+
# TODO: support configurable values
|
|
119
|
+
thinking={"type": "enabled", "budget_tokens": 1024},
|
|
120
|
+
beta_features=["interleaved-thinking-2025-05-14"],
|
|
121
|
+
)
|
|
90
122
|
case "_chaos":
|
|
123
|
+
from kosong.chat_provider.chaos import ChaosChatProvider, ChaosConfig
|
|
124
|
+
|
|
91
125
|
chat_provider = ChaosChatProvider(
|
|
92
126
|
model=model.model,
|
|
93
127
|
base_url=provider.base_url,
|
|
@@ -4,7 +4,6 @@ import string
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any, NamedTuple
|
|
6
6
|
|
|
7
|
-
import fastmcp
|
|
8
7
|
from kosong.tooling import CallableTool, CallableTool2, Toolset
|
|
9
8
|
|
|
10
9
|
from kimi_cli.agentspec import ResolvedAgentSpec, load_agent_spec
|
|
@@ -14,7 +13,7 @@ from kimi_cli.soul.approval import Approval
|
|
|
14
13
|
from kimi_cli.soul.denwarenji import DenwaRenji
|
|
15
14
|
from kimi_cli.soul.runtime import BuiltinSystemPromptArgs, Runtime
|
|
16
15
|
from kimi_cli.soul.toolset import CustomToolset
|
|
17
|
-
from kimi_cli.tools
|
|
16
|
+
from kimi_cli.tools import SkipThisTool
|
|
18
17
|
from kimi_cli.utils.logging import logger
|
|
19
18
|
|
|
20
19
|
|
|
@@ -101,7 +100,11 @@ def _load_tools(
|
|
|
101
100
|
) -> list[str]:
|
|
102
101
|
bad_tools: list[str] = []
|
|
103
102
|
for tool_path in tool_paths:
|
|
104
|
-
|
|
103
|
+
try:
|
|
104
|
+
tool = _load_tool(tool_path, dependencies)
|
|
105
|
+
except SkipThisTool:
|
|
106
|
+
logger.info("Skipping tool: {tool_path}", tool_path=tool_path)
|
|
107
|
+
continue
|
|
105
108
|
if tool:
|
|
106
109
|
toolset += tool
|
|
107
110
|
else:
|
|
@@ -143,6 +146,10 @@ async def _load_mcp_tools(
|
|
|
143
146
|
ValueError: If the MCP config is not valid.
|
|
144
147
|
RuntimeError: If the MCP server cannot be connected.
|
|
145
148
|
"""
|
|
149
|
+
import fastmcp
|
|
150
|
+
|
|
151
|
+
from kimi_cli.tools.mcp import MCPTool
|
|
152
|
+
|
|
146
153
|
for mcp_config in mcp_configs:
|
|
147
154
|
logger.info("Loading MCP tools from: {mcp_config}", mcp_config=mcp_config)
|
|
148
155
|
client = fastmcp.Client(mcp_config)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import subprocess
|
|
2
3
|
import sys
|
|
3
4
|
from datetime import datetime
|
|
@@ -75,9 +76,10 @@ class Runtime(NamedTuple):
|
|
|
75
76
|
session: Session,
|
|
76
77
|
yolo: bool,
|
|
77
78
|
) -> "Runtime":
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
ls_output, agents_md = await asyncio.gather(
|
|
80
|
+
asyncio.to_thread(_list_work_dir, session.work_dir),
|
|
81
|
+
asyncio.to_thread(load_agents_md, session.work_dir),
|
|
82
|
+
)
|
|
81
83
|
|
|
82
84
|
return Runtime(
|
|
83
85
|
config=config,
|
|
@@ -87,7 +89,7 @@ class Runtime(NamedTuple):
|
|
|
87
89
|
KIMI_NOW=datetime.now().astimezone().isoformat(),
|
|
88
90
|
KIMI_WORK_DIR=session.work_dir,
|
|
89
91
|
KIMI_WORK_DIR_LS=ls_output,
|
|
90
|
-
KIMI_AGENTS_MD=agents_md,
|
|
92
|
+
KIMI_AGENTS_MD=agents_md or "",
|
|
91
93
|
),
|
|
92
94
|
denwa_renji=DenwaRenji(),
|
|
93
95
|
approval=Approval(yolo=yolo),
|
|
@@ -2,7 +2,8 @@ from contextvars import ContextVar
|
|
|
2
2
|
from typing import override
|
|
3
3
|
|
|
4
4
|
from kosong.base.message import ToolCall
|
|
5
|
-
from kosong.tooling import HandleResult
|
|
5
|
+
from kosong.tooling import HandleResult
|
|
6
|
+
from kosong.tooling.simple import SimpleToolset
|
|
6
7
|
|
|
7
8
|
current_tool_call = ContextVar[ToolCall | None]("current_tool_call", default=None)
|
|
8
9
|
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
4
|
-
import streamingjson
|
|
5
|
+
import streamingjson # pyright: ignore[reportMissingTypeStubs]
|
|
5
6
|
from kosong.utils.typing import JsonType
|
|
6
7
|
|
|
7
8
|
from kimi_cli.utils.string import shorten_middle
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
class SkipThisTool(Exception):
|
|
12
|
+
"""Raised when a tool decides to skip itself from the loading process."""
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
10
17
|
def extract_subtitle(lexer: streamingjson.Lexer, tool_name: str) -> str | None:
|
|
11
18
|
try:
|
|
12
19
|
curr_args: JsonType = json.loads(lexer.complete_json())
|
|
@@ -29,15 +36,15 @@ def extract_subtitle(lexer: streamingjson.Lexer, tool_name: str) -> str | None:
|
|
|
29
36
|
case "SetTodoList":
|
|
30
37
|
if not isinstance(curr_args, dict) or not curr_args.get("todos"):
|
|
31
38
|
return None
|
|
32
|
-
|
|
39
|
+
|
|
40
|
+
from kimi_cli.tools.todo import Params
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
todo_params = Params.model_validate(curr_args)
|
|
44
|
+
for todo in todo_params.todos:
|
|
45
|
+
subtitle += f"• {todo.title} [{todo.status}]\n"
|
|
46
|
+
except Exception:
|
|
33
47
|
return None
|
|
34
|
-
for todo in curr_args["todos"]:
|
|
35
|
-
if not isinstance(todo, dict) or not todo.get("title"):
|
|
36
|
-
continue
|
|
37
|
-
subtitle += f"• {todo['title']}"
|
|
38
|
-
if todo.get("status"):
|
|
39
|
-
subtitle += f" [{todo['status']}]"
|
|
40
|
-
subtitle += "\n"
|
|
41
48
|
return "\n" + subtitle.strip()
|
|
42
49
|
case "Bash":
|
|
43
50
|
if not isinstance(curr_args, dict) or not curr_args.get("command"):
|
|
@@ -72,7 +79,9 @@ def extract_subtitle(lexer: streamingjson.Lexer, tool_name: str) -> str | None:
|
|
|
72
79
|
return None
|
|
73
80
|
subtitle = str(curr_args["url"])
|
|
74
81
|
case _:
|
|
75
|
-
|
|
82
|
+
# lexer.json_content is list[str] based on streamingjson source code
|
|
83
|
+
content: list[str] = cast(list[str], lexer.json_content) # pyright: ignore[reportUnknownMemberType]
|
|
84
|
+
subtitle = "".join(content)
|
|
76
85
|
if tool_name not in ["SetTodoList"]:
|
|
77
86
|
subtitle = shorten_middle(subtitle, width=50)
|
|
78
87
|
return subtitle
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
from typing import override
|
|
4
|
+
from typing import Any, override
|
|
4
5
|
|
|
5
6
|
from kosong.tooling import CallableTool2, ToolReturnType
|
|
6
7
|
from pydantic import BaseModel, Field
|
|
@@ -29,7 +30,7 @@ class Bash(CallableTool2[Params]):
|
|
|
29
30
|
description: str = load_desc(Path(__file__).parent / "bash.md", {})
|
|
30
31
|
params: type[Params] = Params
|
|
31
32
|
|
|
32
|
-
def __init__(self, approval: Approval, **kwargs):
|
|
33
|
+
def __init__(self, approval: Approval, **kwargs: Any):
|
|
33
34
|
super().__init__(**kwargs)
|
|
34
35
|
self._approval = approval
|
|
35
36
|
|
|
@@ -71,8 +72,13 @@ class Bash(CallableTool2[Params]):
|
|
|
71
72
|
)
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
async def _stream_subprocess(
|
|
75
|
-
|
|
75
|
+
async def _stream_subprocess(
|
|
76
|
+
command: str,
|
|
77
|
+
stdout_cb: Callable[[bytes], None],
|
|
78
|
+
stderr_cb: Callable[[bytes], None],
|
|
79
|
+
timeout: int,
|
|
80
|
+
) -> int:
|
|
81
|
+
async def _read_stream(stream: asyncio.StreamReader, cb: Callable[[bytes], None]):
|
|
76
82
|
while True:
|
|
77
83
|
line = await stream.readline()
|
|
78
84
|
if line:
|
|
@@ -85,6 +91,9 @@ async def _stream_subprocess(command: str, stdout_cb, stderr_cb, timeout: int) -
|
|
|
85
91
|
command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
86
92
|
)
|
|
87
93
|
|
|
94
|
+
assert process.stdout is not None, "stdout is None"
|
|
95
|
+
assert process.stderr is not None, "stderr is None"
|
|
96
|
+
|
|
88
97
|
try:
|
|
89
98
|
await asyncio.wait_for(
|
|
90
99
|
asyncio.gather(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import override
|
|
2
|
+
from typing import Any, override
|
|
3
3
|
|
|
4
4
|
from kosong.tooling import CallableTool2, ToolError, ToolReturnType
|
|
5
5
|
|
|
@@ -8,12 +8,12 @@ from kimi_cli.soul.denwarenji import DenwaRenji, DenwaRenjiError, DMail
|
|
|
8
8
|
NAME = "SendDMail"
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class SendDMail(CallableTool2):
|
|
11
|
+
class SendDMail(CallableTool2[DMail]):
|
|
12
12
|
name: str = NAME
|
|
13
13
|
description: str = (Path(__file__).parent / "dmail.md").read_text(encoding="utf-8")
|
|
14
14
|
params: type[DMail] = DMail
|
|
15
15
|
|
|
16
|
-
def __init__(self, denwa_renji: DenwaRenji, **kwargs):
|
|
16
|
+
def __init__(self, denwa_renji: DenwaRenji, **kwargs: Any) -> None:
|
|
17
17
|
super().__init__(**kwargs)
|
|
18
18
|
self._denwa_renji = denwa_renji
|
|
19
19
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import override
|
|
5
|
+
from typing import Any, override
|
|
6
6
|
|
|
7
7
|
import aiofiles.os
|
|
8
8
|
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnType
|
|
@@ -38,7 +38,7 @@ class Glob(CallableTool2[Params]):
|
|
|
38
38
|
)
|
|
39
39
|
params: type[Params] = Params
|
|
40
40
|
|
|
41
|
-
def __init__(self, builtin_args: BuiltinSystemPromptArgs, **kwargs):
|
|
41
|
+
def __init__(self, builtin_args: BuiltinSystemPromptArgs, **kwargs: Any) -> None:
|
|
42
42
|
super().__init__(**kwargs)
|
|
43
43
|
self._work_dir = builtin_args.KIMI_WORK_DIR
|
|
44
44
|
|