kimi-cli 0.42__tar.gz → 0.44__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.42 → kimi_cli-0.44}/PKG-INFO +7 -6
- {kimi_cli-0.42 → kimi_cli-0.44}/README.md +2 -2
- {kimi_cli-0.42 → kimi_cli-0.44}/pyproject.toml +5 -4
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/CHANGELOG.md +23 -0
- kimi_cli-0.42/src/kimi_cli/__init__.py → kimi_cli-0.44/src/kimi_cli/app.py +58 -18
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/cli.py +6 -5
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/config.py +7 -2
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/llm.py +37 -7
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/__init__.py +22 -4
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/agent.py +4 -2
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/kimisoul.py +21 -4
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/message.py +1 -1
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/runtime.py +7 -5
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/task/__init__.py +2 -1
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/web/search.py +1 -1
- kimi_cli-0.44/src/kimi_cli/ui/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/acp/__init__.py +24 -28
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/print/__init__.py +25 -33
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/__init__.py +55 -36
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/keyboard.py +82 -14
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/prompt.py +199 -3
- kimi_cli-0.44/src/kimi_cli/ui/shell/replay.py +104 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/setup.py +1 -1
- kimi_cli-0.44/src/kimi_cli/ui/shell/visualize.py +111 -0
- kimi_cli-0.44/src/kimi_cli/utils/message.py +22 -0
- kimi_cli-0.44/src/kimi_cli/utils/signals.py +41 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/utils/string.py +8 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/wire/__init__.py +1 -0
- kimi_cli-0.42/src/kimi_cli/ui/shell/visualize.py +0 -114
- kimi_cli-0.42/src/kimi_cli/utils/message.py +0 -8
- {kimi_cli-0.42/src/kimi_cli/ui → kimi_cli-0.44/src/kimi_cli}/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/agents/default/agent.yaml +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/agents/default/sub.yaml +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/agents/default/system.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/agentspec.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/constant.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/exception.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/metadata.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/prompts/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/prompts/compact.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/prompts/init.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/py.typed +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/session.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/share.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/approval.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/compaction.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/context.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/denwarenji.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/soul/toolset.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/bash/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/bash/bash.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/dmail/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/dmail/dmail.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/glob.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/glob.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/grep.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/grep.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/patch.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/patch.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/read.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/read.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/replace.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/replace.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/write.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/file/write.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/mcp.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/task/task.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/test.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/think/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/think/think.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/todo/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/todo/set_todo_list.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/utils.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/web/__init__.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/web/fetch.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/web/fetch.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/tools/web/search.md +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/console.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/debug.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/liveview.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/metacmd.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/ui/shell/update.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/utils/aiohttp.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/utils/changelog.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/utils/logging.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/utils/path.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/utils/pyinstaller.py +0 -0
- {kimi_cli-0.42 → kimi_cli-0.44}/src/kimi_cli/wire/message.py +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: kimi-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.44
|
|
4
4
|
Summary: Kimi CLI is your next CLI agent.
|
|
5
|
-
Requires-Dist: agent-client-protocol==0.
|
|
5
|
+
Requires-Dist: agent-client-protocol==0.6.2
|
|
6
6
|
Requires-Dist: aiofiles==25.1.0
|
|
7
|
-
Requires-Dist: aiohttp==3.13.
|
|
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.16.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
|
|
13
|
+
Requires-Dist: pillow==12.0.0
|
|
13
14
|
Requires-Dist: pyyaml==6.0.3
|
|
14
15
|
Requires-Dist: rich==14.2.0
|
|
15
16
|
Requires-Dist: ripgrepy==2.2.0
|
|
@@ -84,7 +85,7 @@ After setup, Kimi CLI will be ready to use. You can send `/help` to get more inf
|
|
|
84
85
|
|
|
85
86
|
### Shell mode
|
|
86
87
|
|
|
87
|
-
Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-
|
|
88
|
+
Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-X`. In shell mode, you can directly run shell commands without leaving Kimi CLI.
|
|
88
89
|
|
|
89
90
|
> [!NOTE]
|
|
90
91
|
> Built-in shell commands like `cd` are not supported yet.
|
|
@@ -109,7 +110,7 @@ Then add `kimi-cli` to your Zsh plugin list in `~/.zshrc`:
|
|
|
109
110
|
plugins=(... kimi-cli)
|
|
110
111
|
```
|
|
111
112
|
|
|
112
|
-
After restarting Zsh, you can switch to agent mode by pressing `Ctrl-
|
|
113
|
+
After restarting Zsh, you can switch to agent mode by pressing `Ctrl-X`.
|
|
113
114
|
|
|
114
115
|
### ACP support
|
|
115
116
|
|
|
@@ -60,7 +60,7 @@ After setup, Kimi CLI will be ready to use. You can send `/help` to get more inf
|
|
|
60
60
|
|
|
61
61
|
### Shell mode
|
|
62
62
|
|
|
63
|
-
Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-
|
|
63
|
+
Kimi CLI is not only a coding agent, but also a shell. You can switch the mode by pressing `Ctrl-X`. In shell mode, you can directly run shell commands without leaving Kimi CLI.
|
|
64
64
|
|
|
65
65
|
> [!NOTE]
|
|
66
66
|
> Built-in shell commands like `cd` are not supported yet.
|
|
@@ -85,7 +85,7 @@ Then add `kimi-cli` to your Zsh plugin list in `~/.zshrc`:
|
|
|
85
85
|
plugins=(... kimi-cli)
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
After restarting Zsh, you can switch to agent mode by pressing `Ctrl-
|
|
88
|
+
After restarting Zsh, you can switch to agent mode by pressing `Ctrl-X`.
|
|
89
89
|
|
|
90
90
|
### ACP support
|
|
91
91
|
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kimi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.44"
|
|
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.
|
|
8
|
+
"agent-client-protocol==0.6.2",
|
|
9
9
|
"aiofiles==25.1.0",
|
|
10
|
-
"aiohttp==3.13.
|
|
10
|
+
"aiohttp==3.13.2",
|
|
11
11
|
"click==8.3.0",
|
|
12
|
-
"kosong==0.
|
|
12
|
+
"kosong==0.16.1",
|
|
13
13
|
"loguru==0.7.3",
|
|
14
14
|
"patch-ng==1.19.0",
|
|
15
15
|
"prompt-toolkit==3.0.52",
|
|
16
|
+
"pillow==12.0.0",
|
|
16
17
|
"pyyaml==6.0.3",
|
|
17
18
|
"rich==14.2.0",
|
|
18
19
|
"ripgrepy==2.2.0",
|
|
@@ -9,6 +9,29 @@ 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.44] - 2025-10-30
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Improve startup time
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fix potential invalid bytes in user input
|
|
21
|
+
|
|
22
|
+
## [0.43] - 2025-10-30
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Basic Windows support (experimental)
|
|
27
|
+
- Display warnings when base URL or API key is overridden in environment variables
|
|
28
|
+
- Support image input if the LLM model supports it
|
|
29
|
+
- Replay recent context history when continuing a session
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Ensure new line after executing shell commands
|
|
34
|
+
|
|
12
35
|
## [0.42] - 2025-10-28
|
|
13
36
|
|
|
14
37
|
### 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
|
|
21
19
|
from kimi_cli.utils.logging import StreamToLogger, logger
|
|
22
20
|
|
|
23
21
|
|
|
@@ -72,7 +70,7 @@ class KimiCLI:
|
|
|
72
70
|
# try overwrite with environment variables
|
|
73
71
|
assert provider is not None
|
|
74
72
|
assert model is not None
|
|
75
|
-
augment_provider_with_env_vars(provider, model)
|
|
73
|
+
env_overrides = augment_provider_with_env_vars(provider, model)
|
|
76
74
|
|
|
77
75
|
if not provider.base_url or not model.model:
|
|
78
76
|
llm = None
|
|
@@ -95,11 +93,17 @@ class KimiCLI:
|
|
|
95
93
|
runtime,
|
|
96
94
|
context=context,
|
|
97
95
|
)
|
|
98
|
-
return KimiCLI(soul,
|
|
96
|
+
return KimiCLI(soul, runtime, env_overrides)
|
|
99
97
|
|
|
100
|
-
def __init__(
|
|
101
|
-
self
|
|
102
|
-
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
_soul: KimiSoul,
|
|
101
|
+
_runtime: Runtime,
|
|
102
|
+
_env_overrides: dict[str, str],
|
|
103
|
+
) -> None:
|
|
104
|
+
self._soul = _soul
|
|
105
|
+
self._runtime = _runtime
|
|
106
|
+
self._env_overrides = _env_overrides
|
|
103
107
|
|
|
104
108
|
@property
|
|
105
109
|
def soul(self) -> KimiSoul:
|
|
@@ -109,12 +113,12 @@ class KimiCLI:
|
|
|
109
113
|
@property
|
|
110
114
|
def session(self) -> Session:
|
|
111
115
|
"""Get the Session instance."""
|
|
112
|
-
return self.
|
|
116
|
+
return self._runtime.session
|
|
113
117
|
|
|
114
118
|
@contextlib.contextmanager
|
|
115
119
|
def _app_env(self) -> Generator[None]:
|
|
116
120
|
original_cwd = Path.cwd()
|
|
117
|
-
os.chdir(self.
|
|
121
|
+
os.chdir(self._runtime.session.work_dir)
|
|
118
122
|
try:
|
|
119
123
|
# to ignore possible warnings from dateparser
|
|
120
124
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
@@ -124,14 +128,46 @@ class KimiCLI:
|
|
|
124
128
|
os.chdir(original_cwd)
|
|
125
129
|
|
|
126
130
|
async def run_shell_mode(self, command: str | None = None) -> bool:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
)
|
|
134
160
|
)
|
|
161
|
+
else:
|
|
162
|
+
welcome_info.append(
|
|
163
|
+
WelcomeInfoItem(
|
|
164
|
+
name="Model",
|
|
165
|
+
value=self._soul.model,
|
|
166
|
+
level=WelcomeInfoItem.Level.INFO,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
with self._app_env():
|
|
170
|
+
app = ShellApp(self._soul, welcome_info=welcome_info)
|
|
135
171
|
return await app.run(command)
|
|
136
172
|
|
|
137
173
|
async def run_print_mode(
|
|
@@ -140,16 +176,20 @@ class KimiCLI:
|
|
|
140
176
|
output_format: OutputFormat,
|
|
141
177
|
command: str | None = None,
|
|
142
178
|
) -> bool:
|
|
179
|
+
from kimi_cli.ui.print import PrintApp
|
|
180
|
+
|
|
143
181
|
with self._app_env():
|
|
144
182
|
app = PrintApp(
|
|
145
183
|
self._soul,
|
|
146
184
|
input_format,
|
|
147
185
|
output_format,
|
|
148
|
-
self.
|
|
186
|
+
self._runtime.session.history_file,
|
|
149
187
|
)
|
|
150
188
|
return await app.run(command)
|
|
151
189
|
|
|
152
190
|
async def run_acp_server(self) -> bool:
|
|
191
|
+
from kimi_cli.ui.acp import ACPServer
|
|
192
|
+
|
|
153
193
|
with self._app_env():
|
|
154
194
|
app = ACPServer(self._soul)
|
|
155
195
|
return await app.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):
|
|
@@ -22,6 +17,8 @@ class Reload(Exception):
|
|
|
22
17
|
|
|
23
18
|
|
|
24
19
|
UIMode = Literal["shell", "print", "acp"]
|
|
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"]))
|
|
@@ -156,6 +153,10 @@ def kimi(
|
|
|
156
153
|
yolo: bool,
|
|
157
154
|
):
|
|
158
155
|
"""Kimi, your next CLI agent."""
|
|
156
|
+
from kimi_cli.app import KimiCLI
|
|
157
|
+
from kimi_cli.session import Session
|
|
158
|
+
from kimi_cli.share import get_share_dir
|
|
159
|
+
from kimi_cli.utils.logging import logger
|
|
159
160
|
|
|
160
161
|
def _noop_echo(*args: Any, **kwargs: Any):
|
|
161
162
|
pass
|
|
@@ -18,7 +18,7 @@ class LLMProvider(BaseModel):
|
|
|
18
18
|
"""API base URL"""
|
|
19
19
|
api_key: SecretStr
|
|
20
20
|
"""API key"""
|
|
21
|
-
custom_headers: dict[str, str] =
|
|
21
|
+
custom_headers: dict[str, str] | None = None
|
|
22
22
|
"""Custom headers to include in API requests"""
|
|
23
23
|
|
|
24
24
|
@field_serializer("api_key", when_used="json")
|
|
@@ -26,6 +26,9 @@ class LLMProvider(BaseModel):
|
|
|
26
26
|
return v.get_secret_value()
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
LLMModelCapability = Literal["image_in"]
|
|
30
|
+
|
|
31
|
+
|
|
29
32
|
class LLMModel(BaseModel):
|
|
30
33
|
"""LLM model configuration."""
|
|
31
34
|
|
|
@@ -35,6 +38,8 @@ class LLMModel(BaseModel):
|
|
|
35
38
|
"""Model name"""
|
|
36
39
|
max_context_size: int
|
|
37
40
|
"""Maximum context size (unit: tokens)"""
|
|
41
|
+
capabilities: set[LLMModelCapability] | None = None
|
|
42
|
+
"""Model capabilities"""
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
class LoopControl(BaseModel):
|
|
@@ -53,7 +58,7 @@ class MoonshotSearchConfig(BaseModel):
|
|
|
53
58
|
"""Base URL for Moonshot Search service."""
|
|
54
59
|
api_key: SecretStr
|
|
55
60
|
"""API key for Moonshot Search service."""
|
|
56
|
-
custom_headers: dict[str, str] =
|
|
61
|
+
custom_headers: dict[str, str] | None = None
|
|
57
62
|
"""Custom headers to include in API requests."""
|
|
58
63
|
|
|
59
64
|
@field_serializer("api_key", when_used="json")
|
|
@@ -2,31 +2,49 @@ import os
|
|
|
2
2
|
from typing import NamedTuple
|
|
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
|
-
from kimi_cli.config import LLMModel, LLMProvider
|
|
7
|
+
from kimi_cli.config import LLMModel, LLMModelCapability, LLMProvider
|
|
11
8
|
from kimi_cli.constant import USER_AGENT
|
|
12
9
|
|
|
13
10
|
|
|
14
11
|
class LLM(NamedTuple):
|
|
15
12
|
chat_provider: ChatProvider
|
|
16
13
|
max_context_size: int
|
|
14
|
+
capabilities: set[LLMModelCapability]
|
|
15
|
+
# TODO: these additional fields should be moved to ChatProvider
|
|
17
16
|
|
|
17
|
+
@property
|
|
18
|
+
def model_name(self) -> str:
|
|
19
|
+
return self.chat_provider.model_name
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def supports_image_in(self) -> bool:
|
|
23
|
+
return "image_in" in self.capabilities
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel) -> dict[str, str]:
|
|
27
|
+
"""Override provider/model settings from environment variables.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Mapping of environment variables that were applied.
|
|
31
|
+
"""
|
|
32
|
+
applied: dict[str, str] = {}
|
|
18
33
|
|
|
19
|
-
def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel):
|
|
20
34
|
match provider.type:
|
|
21
35
|
case "kimi":
|
|
22
36
|
if base_url := os.getenv("KIMI_BASE_URL"):
|
|
23
37
|
provider.base_url = base_url
|
|
38
|
+
applied["KIMI_BASE_URL"] = base_url
|
|
24
39
|
if api_key := os.getenv("KIMI_API_KEY"):
|
|
25
40
|
provider.api_key = SecretStr(api_key)
|
|
41
|
+
applied["KIMI_API_KEY"] = "******"
|
|
26
42
|
if model_name := os.getenv("KIMI_MODEL_NAME"):
|
|
27
43
|
model.model = model_name
|
|
44
|
+
applied["KIMI_MODEL_NAME"] = model.model
|
|
28
45
|
if max_context_size := os.getenv("KIMI_MODEL_MAX_CONTEXT_SIZE"):
|
|
29
46
|
model.max_context_size = int(max_context_size)
|
|
47
|
+
applied["KIMI_MODEL_MAX_CONTEXT_SIZE"] = str(model.max_context_size)
|
|
30
48
|
case "openai_legacy":
|
|
31
49
|
if base_url := os.getenv("OPENAI_BASE_URL"):
|
|
32
50
|
provider.base_url = base_url
|
|
@@ -35,6 +53,8 @@ def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel):
|
|
|
35
53
|
case _:
|
|
36
54
|
pass
|
|
37
55
|
|
|
56
|
+
return applied
|
|
57
|
+
|
|
38
58
|
|
|
39
59
|
def create_llm(
|
|
40
60
|
provider: LLMProvider,
|
|
@@ -45,6 +65,8 @@ def create_llm(
|
|
|
45
65
|
) -> LLM:
|
|
46
66
|
match provider.type:
|
|
47
67
|
case "kimi":
|
|
68
|
+
from kosong.chat_provider.kimi import Kimi
|
|
69
|
+
|
|
48
70
|
chat_provider = Kimi(
|
|
49
71
|
model=model.model,
|
|
50
72
|
base_url=provider.base_url,
|
|
@@ -52,12 +74,14 @@ def create_llm(
|
|
|
52
74
|
stream=stream,
|
|
53
75
|
default_headers={
|
|
54
76
|
"User-Agent": USER_AGENT,
|
|
55
|
-
**provider.custom_headers,
|
|
77
|
+
**(provider.custom_headers or {}),
|
|
56
78
|
},
|
|
57
79
|
)
|
|
58
80
|
if session_id:
|
|
59
81
|
chat_provider = chat_provider.with_generation_kwargs(prompt_cache_key=session_id)
|
|
60
82
|
case "openai_legacy":
|
|
83
|
+
from kosong.chat_provider.openai_legacy import OpenAILegacy
|
|
84
|
+
|
|
61
85
|
chat_provider = OpenAILegacy(
|
|
62
86
|
model=model.model,
|
|
63
87
|
base_url=provider.base_url,
|
|
@@ -65,6 +89,8 @@ def create_llm(
|
|
|
65
89
|
stream=stream,
|
|
66
90
|
)
|
|
67
91
|
case "_chaos":
|
|
92
|
+
from kosong.chat_provider.chaos import ChaosChatProvider, ChaosConfig
|
|
93
|
+
|
|
68
94
|
chat_provider = ChaosChatProvider(
|
|
69
95
|
model=model.model,
|
|
70
96
|
base_url=provider.base_url,
|
|
@@ -75,4 +101,8 @@ def create_llm(
|
|
|
75
101
|
),
|
|
76
102
|
)
|
|
77
103
|
|
|
78
|
-
return LLM(
|
|
104
|
+
return LLM(
|
|
105
|
+
chat_provider=chat_provider,
|
|
106
|
+
max_context_size=model.max_context_size,
|
|
107
|
+
capabilities=model.capabilities or set(),
|
|
108
|
+
)
|
|
@@ -4,6 +4,9 @@ from collections.abc import Callable, Coroutine
|
|
|
4
4
|
from contextvars import ContextVar
|
|
5
5
|
from typing import Any, NamedTuple, Protocol, runtime_checkable
|
|
6
6
|
|
|
7
|
+
from kosong.base.message import ContentPart
|
|
8
|
+
|
|
9
|
+
from kimi_cli.llm import LLM
|
|
7
10
|
from kimi_cli.utils.logging import logger
|
|
8
11
|
from kimi_cli.wire import Wire, WireUISide
|
|
9
12
|
from kimi_cli.wire.message import WireMessage
|
|
@@ -15,6 +18,19 @@ class LLMNotSet(Exception):
|
|
|
15
18
|
pass
|
|
16
19
|
|
|
17
20
|
|
|
21
|
+
class LLMNotSupported(Exception):
|
|
22
|
+
"""Raised when the LLM does not have required capabilities."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, llm: LLM, capabilities: list[str]):
|
|
25
|
+
self.llm = llm
|
|
26
|
+
self.capabilities = capabilities
|
|
27
|
+
capabilities_str = "capability" if len(capabilities) == 1 else "capabilities"
|
|
28
|
+
super().__init__(
|
|
29
|
+
f"The LLM model '{llm.model_name}' does not support required {capabilities_str}: "
|
|
30
|
+
f"{', '.join(capabilities)}."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
18
34
|
class MaxStepsReached(Exception):
|
|
19
35
|
"""Raised when the maximum number of steps is reached."""
|
|
20
36
|
|
|
@@ -47,15 +63,16 @@ class Soul(Protocol):
|
|
|
47
63
|
"""The current status of the soul. The returned value is immutable."""
|
|
48
64
|
...
|
|
49
65
|
|
|
50
|
-
async def run(self, user_input: str):
|
|
66
|
+
async def run(self, user_input: str | list[ContentPart]):
|
|
51
67
|
"""
|
|
52
68
|
Run the agent with the given user input until the max steps or no more tool calls.
|
|
53
69
|
|
|
54
70
|
Args:
|
|
55
|
-
user_input (str): The user input to the agent.
|
|
71
|
+
user_input (str | list[ContentPart]): The user input to the agent.
|
|
56
72
|
|
|
57
73
|
Raises:
|
|
58
74
|
LLMNotSet: When the LLM is not set.
|
|
75
|
+
LLMNotSupported: When the LLM does not have required capabilities.
|
|
59
76
|
ChatProviderError: When the LLM provider returns an error.
|
|
60
77
|
MaxStepsReached: When the maximum number of steps is reached.
|
|
61
78
|
asyncio.CancelledError: When the run is cancelled by user.
|
|
@@ -73,7 +90,7 @@ class RunCancelled(Exception):
|
|
|
73
90
|
|
|
74
91
|
async def run_soul(
|
|
75
92
|
soul: "Soul",
|
|
76
|
-
user_input: str,
|
|
93
|
+
user_input: str | list[ContentPart],
|
|
77
94
|
ui_loop_fn: UILoopFn,
|
|
78
95
|
cancel_event: asyncio.Event,
|
|
79
96
|
) -> None:
|
|
@@ -85,6 +102,7 @@ async def run_soul(
|
|
|
85
102
|
|
|
86
103
|
Raises:
|
|
87
104
|
LLMNotSet: When the LLM is not set.
|
|
105
|
+
LLMNotSupported: When the LLM does not have required capabilities.
|
|
88
106
|
ChatProviderError: When the LLM provider returns an error.
|
|
89
107
|
MaxStepsReached: When the maximum number of steps is reached.
|
|
90
108
|
RunCancelled: When the run is cancelled by the cancel event.
|
|
@@ -125,7 +143,7 @@ async def run_soul(
|
|
|
125
143
|
try:
|
|
126
144
|
await asyncio.wait_for(ui_task, timeout=0.5)
|
|
127
145
|
except asyncio.QueueShutDown:
|
|
128
|
-
|
|
146
|
+
logger.debug("UI loop shut down")
|
|
129
147
|
pass
|
|
130
148
|
except TimeoutError:
|
|
131
149
|
logger.warning("UI loop timed out")
|
|
@@ -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,6 @@ 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.mcp import MCPTool
|
|
18
16
|
from kimi_cli.utils.logging import logger
|
|
19
17
|
|
|
20
18
|
|
|
@@ -143,6 +141,10 @@ async def _load_mcp_tools(
|
|
|
143
141
|
ValueError: If the MCP config is not valid.
|
|
144
142
|
RuntimeError: If the MCP server cannot be connected.
|
|
145
143
|
"""
|
|
144
|
+
import fastmcp
|
|
145
|
+
|
|
146
|
+
from kimi_cli.tools.mcp import MCPTool
|
|
147
|
+
|
|
146
148
|
for mcp_config in mcp_configs:
|
|
147
149
|
logger.info("Loading MCP tools from: {mcp_config}", mcp_config=mcp_config)
|
|
148
150
|
client = fastmcp.Client(mcp_config)
|
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
import kosong
|
|
7
7
|
import tenacity
|
|
8
8
|
from kosong import StepResult
|
|
9
|
-
from kosong.base.message import Message
|
|
9
|
+
from kosong.base.message import ContentPart, ImageURLPart, Message
|
|
10
10
|
from kosong.chat_provider import (
|
|
11
11
|
APIConnectionError,
|
|
12
12
|
APIStatusError,
|
|
@@ -16,7 +16,14 @@ from kosong.chat_provider import (
|
|
|
16
16
|
from kosong.tooling import ToolResult
|
|
17
17
|
from tenacity import RetryCallState, retry_if_exception, stop_after_attempt, wait_exponential_jitter
|
|
18
18
|
|
|
19
|
-
from kimi_cli.soul import
|
|
19
|
+
from kimi_cli.soul import (
|
|
20
|
+
LLMNotSet,
|
|
21
|
+
LLMNotSupported,
|
|
22
|
+
MaxStepsReached,
|
|
23
|
+
Soul,
|
|
24
|
+
StatusSnapshot,
|
|
25
|
+
wire_send,
|
|
26
|
+
)
|
|
20
27
|
from kimi_cli.soul.agent import Agent
|
|
21
28
|
from kimi_cli.soul.compaction import SimpleCompaction
|
|
22
29
|
from kimi_cli.soul.context import Context
|
|
@@ -53,7 +60,6 @@ class KimiSoul(Soul):
|
|
|
53
60
|
agent (Agent): The agent to run.
|
|
54
61
|
runtime (Runtime): Runtime parameters and states.
|
|
55
62
|
context (Context): The context of the agent.
|
|
56
|
-
loop_control (LoopControl): The control parameters for the agent loop.
|
|
57
63
|
"""
|
|
58
64
|
self._agent = agent
|
|
59
65
|
self._runtime = runtime
|
|
@@ -85,6 +91,10 @@ class KimiSoul(Soul):
|
|
|
85
91
|
def status(self) -> StatusSnapshot:
|
|
86
92
|
return StatusSnapshot(context_usage=self._context_usage)
|
|
87
93
|
|
|
94
|
+
@property
|
|
95
|
+
def context(self) -> Context:
|
|
96
|
+
return self._context
|
|
97
|
+
|
|
88
98
|
@property
|
|
89
99
|
def _context_usage(self) -> float:
|
|
90
100
|
if self._runtime.llm is not None:
|
|
@@ -94,10 +104,17 @@ class KimiSoul(Soul):
|
|
|
94
104
|
async def _checkpoint(self):
|
|
95
105
|
await self._context.checkpoint(self._checkpoint_with_user_message)
|
|
96
106
|
|
|
97
|
-
async def run(self, user_input: str):
|
|
107
|
+
async def run(self, user_input: str | list[ContentPart]):
|
|
98
108
|
if self._runtime.llm is None:
|
|
99
109
|
raise LLMNotSet()
|
|
100
110
|
|
|
111
|
+
if (
|
|
112
|
+
isinstance(user_input, list)
|
|
113
|
+
and any(isinstance(part, ImageURLPart) for part in user_input)
|
|
114
|
+
and not self._runtime.llm.supports_image_in
|
|
115
|
+
):
|
|
116
|
+
raise LLMNotSupported(self._runtime.llm, ["image_in"])
|
|
117
|
+
|
|
101
118
|
await self._checkpoint() # this creates the checkpoint 0 on first run
|
|
102
119
|
await self._context.append_message(Message(role="user", content=user_input))
|
|
103
120
|
logger.debug("Appended user message to context")
|
|
@@ -14,7 +14,7 @@ def tool_result_to_messages(tool_result: ToolResult) -> list[Message]:
|
|
|
14
14
|
message = tool_result.result.message
|
|
15
15
|
if isinstance(tool_result.result, ToolRuntimeError):
|
|
16
16
|
message += "\nThis is an unexpected error and the tool is probably not working."
|
|
17
|
-
content: list[ContentPart] = [system(message)]
|
|
17
|
+
content: list[ContentPart] = [system(f"ERROR: {message}")]
|
|
18
18
|
if tool_result.result.output:
|
|
19
19
|
content.append(TextPart(text=tool_result.result.output))
|
|
20
20
|
return [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import subprocess
|
|
2
3
|
import sys
|
|
3
4
|
from datetime import datetime
|
|
@@ -59,7 +60,7 @@ def _list_work_dir(work_dir: Path) -> str:
|
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
class Runtime(NamedTuple):
|
|
62
|
-
"""Agent
|
|
63
|
+
"""Agent runtime."""
|
|
63
64
|
|
|
64
65
|
config: Config
|
|
65
66
|
llm: LLM | None
|
|
@@ -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),
|
|
@@ -68,7 +68,8 @@ class Task(CallableTool2[Params]):
|
|
|
68
68
|
self._subagents: dict[str, Agent] = {}
|
|
69
69
|
|
|
70
70
|
try:
|
|
71
|
-
|
|
71
|
+
loop = asyncio.get_running_loop()
|
|
72
|
+
self._load_task = loop.create_task(self._load_subagents(agent_spec.subagents))
|
|
72
73
|
except RuntimeError:
|
|
73
74
|
# In case there's no running event loop, e.g., during synchronous tests
|
|
74
75
|
self._load_task = None
|
|
@@ -44,7 +44,7 @@ class SearchWeb(CallableTool2[Params]):
|
|
|
44
44
|
if config.services.moonshot_search is not None:
|
|
45
45
|
self._base_url = config.services.moonshot_search.base_url
|
|
46
46
|
self._api_key = config.services.moonshot_search.api_key.get_secret_value()
|
|
47
|
-
self._custom_headers = config.services.moonshot_search.custom_headers
|
|
47
|
+
self._custom_headers = config.services.moonshot_search.custom_headers or {}
|
|
48
48
|
else:
|
|
49
49
|
self._base_url = ""
|
|
50
50
|
self._api_key = ""
|
|
File without changes
|