kimi-cli 0.44__py3-none-any.whl → 0.78__py3-none-any.whl
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/CHANGELOG.md +349 -40
- kimi_cli/__init__.py +6 -0
- kimi_cli/acp/AGENTS.md +91 -0
- kimi_cli/acp/__init__.py +13 -0
- kimi_cli/acp/convert.py +111 -0
- kimi_cli/acp/kaos.py +270 -0
- kimi_cli/acp/mcp.py +46 -0
- kimi_cli/acp/server.py +335 -0
- kimi_cli/acp/session.py +445 -0
- kimi_cli/acp/tools.py +158 -0
- kimi_cli/acp/types.py +13 -0
- kimi_cli/agents/default/agent.yaml +4 -4
- kimi_cli/agents/default/sub.yaml +2 -1
- kimi_cli/agents/default/system.md +79 -21
- kimi_cli/agents/okabe/agent.yaml +17 -0
- kimi_cli/agentspec.py +53 -25
- kimi_cli/app.py +180 -52
- kimi_cli/cli/__init__.py +595 -0
- kimi_cli/cli/__main__.py +8 -0
- kimi_cli/cli/info.py +63 -0
- kimi_cli/cli/mcp.py +349 -0
- kimi_cli/config.py +153 -17
- kimi_cli/constant.py +3 -0
- kimi_cli/exception.py +23 -2
- kimi_cli/flow/__init__.py +117 -0
- kimi_cli/flow/d2.py +376 -0
- kimi_cli/flow/mermaid.py +218 -0
- kimi_cli/llm.py +129 -23
- kimi_cli/metadata.py +32 -7
- kimi_cli/platforms.py +262 -0
- kimi_cli/prompts/__init__.py +2 -0
- kimi_cli/prompts/compact.md +4 -5
- kimi_cli/session.py +223 -31
- kimi_cli/share.py +2 -0
- kimi_cli/skill.py +145 -0
- kimi_cli/skills/kimi-cli-help/SKILL.md +55 -0
- kimi_cli/skills/skill-creator/SKILL.md +351 -0
- kimi_cli/soul/__init__.py +51 -20
- kimi_cli/soul/agent.py +213 -85
- kimi_cli/soul/approval.py +86 -17
- kimi_cli/soul/compaction.py +64 -53
- kimi_cli/soul/context.py +38 -5
- kimi_cli/soul/denwarenji.py +2 -0
- kimi_cli/soul/kimisoul.py +442 -60
- kimi_cli/soul/message.py +54 -54
- kimi_cli/soul/slash.py +72 -0
- kimi_cli/soul/toolset.py +387 -6
- kimi_cli/toad.py +74 -0
- kimi_cli/tools/AGENTS.md +5 -0
- kimi_cli/tools/__init__.py +42 -34
- kimi_cli/tools/display.py +25 -0
- kimi_cli/tools/dmail/__init__.py +10 -10
- kimi_cli/tools/dmail/dmail.md +11 -9
- kimi_cli/tools/file/__init__.py +1 -3
- kimi_cli/tools/file/glob.py +20 -23
- kimi_cli/tools/file/grep.md +1 -1
- kimi_cli/tools/file/{grep.py → grep_local.py} +51 -23
- kimi_cli/tools/file/read.md +24 -6
- kimi_cli/tools/file/read.py +134 -50
- kimi_cli/tools/file/replace.md +1 -1
- kimi_cli/tools/file/replace.py +36 -29
- kimi_cli/tools/file/utils.py +282 -0
- kimi_cli/tools/file/write.py +43 -22
- kimi_cli/tools/multiagent/__init__.py +7 -0
- kimi_cli/tools/multiagent/create.md +11 -0
- kimi_cli/tools/multiagent/create.py +50 -0
- kimi_cli/tools/{task/__init__.py → multiagent/task.py} +48 -53
- kimi_cli/tools/shell/__init__.py +120 -0
- kimi_cli/tools/{bash → shell}/bash.md +1 -2
- kimi_cli/tools/shell/powershell.md +25 -0
- kimi_cli/tools/test.py +4 -4
- kimi_cli/tools/think/__init__.py +2 -2
- kimi_cli/tools/todo/__init__.py +14 -8
- kimi_cli/tools/utils.py +64 -24
- kimi_cli/tools/web/fetch.py +68 -13
- kimi_cli/tools/web/search.py +10 -12
- kimi_cli/ui/acp/__init__.py +65 -412
- kimi_cli/ui/print/__init__.py +37 -49
- kimi_cli/ui/print/visualize.py +179 -0
- kimi_cli/ui/shell/__init__.py +141 -84
- kimi_cli/ui/shell/console.py +2 -0
- kimi_cli/ui/shell/debug.py +28 -23
- kimi_cli/ui/shell/keyboard.py +5 -1
- kimi_cli/ui/shell/prompt.py +220 -194
- kimi_cli/ui/shell/replay.py +111 -46
- kimi_cli/ui/shell/setup.py +89 -82
- kimi_cli/ui/shell/slash.py +422 -0
- kimi_cli/ui/shell/update.py +4 -2
- kimi_cli/ui/shell/usage.py +271 -0
- kimi_cli/ui/shell/visualize.py +574 -72
- kimi_cli/ui/wire/__init__.py +267 -0
- kimi_cli/ui/wire/jsonrpc.py +142 -0
- kimi_cli/ui/wire/protocol.py +1 -0
- kimi_cli/utils/__init__.py +0 -0
- kimi_cli/utils/aiohttp.py +2 -0
- kimi_cli/utils/aioqueue.py +72 -0
- kimi_cli/utils/broadcast.py +37 -0
- kimi_cli/utils/changelog.py +12 -7
- kimi_cli/utils/clipboard.py +12 -0
- kimi_cli/utils/datetime.py +37 -0
- kimi_cli/utils/environment.py +58 -0
- kimi_cli/utils/envvar.py +12 -0
- kimi_cli/utils/frontmatter.py +44 -0
- kimi_cli/utils/logging.py +7 -6
- kimi_cli/utils/message.py +9 -14
- kimi_cli/utils/path.py +99 -9
- kimi_cli/utils/pyinstaller.py +6 -0
- kimi_cli/utils/rich/__init__.py +33 -0
- kimi_cli/utils/rich/columns.py +99 -0
- kimi_cli/utils/rich/markdown.py +961 -0
- kimi_cli/utils/rich/markdown_sample.md +108 -0
- kimi_cli/utils/rich/markdown_sample_short.md +2 -0
- kimi_cli/utils/signals.py +2 -0
- kimi_cli/utils/slashcmd.py +124 -0
- kimi_cli/utils/string.py +2 -0
- kimi_cli/utils/term.py +168 -0
- kimi_cli/utils/typing.py +20 -0
- kimi_cli/wire/__init__.py +98 -29
- kimi_cli/wire/serde.py +45 -0
- kimi_cli/wire/types.py +299 -0
- kimi_cli-0.78.dist-info/METADATA +200 -0
- kimi_cli-0.78.dist-info/RECORD +135 -0
- kimi_cli-0.78.dist-info/entry_points.txt +4 -0
- kimi_cli/cli.py +0 -250
- kimi_cli/soul/runtime.py +0 -96
- kimi_cli/tools/bash/__init__.py +0 -99
- kimi_cli/tools/file/patch.md +0 -8
- kimi_cli/tools/file/patch.py +0 -143
- kimi_cli/tools/mcp.py +0 -85
- kimi_cli/ui/shell/liveview.py +0 -386
- kimi_cli/ui/shell/metacmd.py +0 -262
- kimi_cli/wire/message.py +0 -91
- kimi_cli-0.44.dist-info/METADATA +0 -188
- kimi_cli-0.44.dist-info/RECORD +0 -89
- kimi_cli-0.44.dist-info/entry_points.txt +0 -3
- /kimi_cli/tools/{task → multiagent}/task.md +0 -0
- {kimi_cli-0.44.dist-info → kimi_cli-0.78.dist-info}/WHEEL +0 -0
kimi_cli/tools/utils.py
CHANGED
|
@@ -2,17 +2,34 @@ import re
|
|
|
2
2
|
import string
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from jinja2 import Environment
|
|
6
|
+
from kosong.tooling import BriefDisplayBlock, DisplayBlock, ToolError, ToolReturnValue
|
|
7
|
+
from kosong.utils.typing import JsonType
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
def load_desc(path: Path, substitutions: dict[str, str] | None = None) -> str:
|
|
9
11
|
"""Load a tool description from a file, with optional substitutions."""
|
|
10
12
|
description = path.read_text(encoding="utf-8")
|
|
11
13
|
if substitutions:
|
|
12
|
-
description = string.Template(description).
|
|
14
|
+
description = string.Template(description).safe_substitute(substitutions)
|
|
13
15
|
return description
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
def load_desc_jinja(path: Path, context: dict[str, object] | None = None) -> str:
|
|
19
|
+
"""Load a tool description from a file, rendered via Jinja2."""
|
|
20
|
+
description = path.read_text(encoding="utf-8")
|
|
21
|
+
env = Environment(
|
|
22
|
+
autoescape=False,
|
|
23
|
+
keep_trailing_newline=True,
|
|
24
|
+
lstrip_blocks=True,
|
|
25
|
+
trim_blocks=True,
|
|
26
|
+
variable_start_string="${",
|
|
27
|
+
variable_end_string="}",
|
|
28
|
+
)
|
|
29
|
+
template = env.from_string(description)
|
|
30
|
+
return template.render(context or {})
|
|
31
|
+
|
|
32
|
+
|
|
16
33
|
def truncate_line(line: str, max_length: int, marker: str = "...") -> str:
|
|
17
34
|
"""
|
|
18
35
|
Truncate a line if it exceeds `max_length`, preserving the beginning and the line break.
|
|
@@ -53,6 +70,23 @@ class ToolResultBuilder:
|
|
|
53
70
|
self._n_chars = 0
|
|
54
71
|
self._n_lines = 0
|
|
55
72
|
self._truncation_happened = False
|
|
73
|
+
self._display: list[DisplayBlock] = []
|
|
74
|
+
self._extras: dict[str, JsonType] | None = None
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def is_full(self) -> bool:
|
|
78
|
+
"""Check if output buffer is full due to character limit."""
|
|
79
|
+
return self._n_chars >= self.max_chars
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def n_chars(self) -> int:
|
|
83
|
+
"""Get current character count."""
|
|
84
|
+
return self._n_chars
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def n_lines(self) -> int:
|
|
88
|
+
"""Get current line count."""
|
|
89
|
+
return self._n_lines
|
|
56
90
|
|
|
57
91
|
def write(self, text: str) -> int:
|
|
58
92
|
"""
|
|
@@ -93,8 +127,18 @@ class ToolResultBuilder:
|
|
|
93
127
|
|
|
94
128
|
return chars_written
|
|
95
129
|
|
|
96
|
-
def
|
|
97
|
-
"""
|
|
130
|
+
def display(self, *blocks: DisplayBlock) -> None:
|
|
131
|
+
"""Add display blocks to the tool result."""
|
|
132
|
+
self._display.extend(blocks)
|
|
133
|
+
|
|
134
|
+
def extras(self, **extras: JsonType) -> None:
|
|
135
|
+
"""Add extra data to the tool result."""
|
|
136
|
+
if self._extras is None:
|
|
137
|
+
self._extras = {}
|
|
138
|
+
self._extras.update(extras)
|
|
139
|
+
|
|
140
|
+
def ok(self, message: str = "", *, brief: str = "") -> ToolReturnValue:
|
|
141
|
+
"""Create a ToolReturnValue with is_error=False and the current output."""
|
|
98
142
|
output = "".join(self._buffer)
|
|
99
143
|
|
|
100
144
|
final_message = message
|
|
@@ -106,11 +150,16 @@ class ToolResultBuilder:
|
|
|
106
150
|
final_message += f" {truncation_msg}"
|
|
107
151
|
else:
|
|
108
152
|
final_message = truncation_msg
|
|
153
|
+
return ToolReturnValue(
|
|
154
|
+
is_error=False,
|
|
155
|
+
output=output,
|
|
156
|
+
message=final_message,
|
|
157
|
+
display=([BriefDisplayBlock(text=brief)] if brief else []) + self._display,
|
|
158
|
+
extras=self._extras,
|
|
159
|
+
)
|
|
109
160
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def error(self, message: str, *, brief: str) -> ToolError:
|
|
113
|
-
"""Create a ToolError result with the current output."""
|
|
161
|
+
def error(self, message: str, *, brief: str) -> ToolReturnValue:
|
|
162
|
+
"""Create a ToolReturnValue with is_error=True and the current output."""
|
|
114
163
|
output = "".join(self._buffer)
|
|
115
164
|
|
|
116
165
|
final_message = message
|
|
@@ -121,22 +170,13 @@ class ToolResultBuilder:
|
|
|
121
170
|
else:
|
|
122
171
|
final_message = truncation_msg
|
|
123
172
|
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@property
|
|
132
|
-
def n_chars(self) -> int:
|
|
133
|
-
"""Get current character count."""
|
|
134
|
-
return self._n_chars
|
|
135
|
-
|
|
136
|
-
@property
|
|
137
|
-
def n_lines(self) -> int:
|
|
138
|
-
"""Get current line count."""
|
|
139
|
-
return self._n_lines
|
|
173
|
+
return ToolReturnValue(
|
|
174
|
+
is_error=True,
|
|
175
|
+
output=output,
|
|
176
|
+
message=final_message,
|
|
177
|
+
display=([BriefDisplayBlock(text=brief)] if brief else []) + self._display,
|
|
178
|
+
extras=self._extras,
|
|
179
|
+
)
|
|
140
180
|
|
|
141
181
|
|
|
142
182
|
class ToolRejectedError(ToolError):
|
kimi_cli/tools/web/fetch.py
CHANGED
|
@@ -3,11 +3,15 @@ from typing import override
|
|
|
3
3
|
|
|
4
4
|
import aiohttp
|
|
5
5
|
import trafilatura
|
|
6
|
-
from kosong.tooling import CallableTool2,
|
|
6
|
+
from kosong.tooling import CallableTool2, ToolReturnValue
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
|
+
from kimi_cli.config import Config
|
|
10
|
+
from kimi_cli.constant import USER_AGENT
|
|
11
|
+
from kimi_cli.soul.toolset import get_current_tool_call_or_none
|
|
9
12
|
from kimi_cli.tools.utils import ToolResultBuilder, load_desc
|
|
10
13
|
from kimi_cli.utils.aiohttp import new_client_session
|
|
14
|
+
from kimi_cli.utils.logging import logger
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
class Params(BaseModel):
|
|
@@ -19,10 +23,23 @@ class FetchURL(CallableTool2[Params]):
|
|
|
19
23
|
description: str = load_desc(Path(__file__).parent / "fetch.md", {})
|
|
20
24
|
params: type[Params] = Params
|
|
21
25
|
|
|
26
|
+
def __init__(self, config: Config):
|
|
27
|
+
super().__init__()
|
|
28
|
+
self._service_config = config.services.moonshot_fetch
|
|
29
|
+
|
|
22
30
|
@override
|
|
23
|
-
async def __call__(self, params: Params) ->
|
|
31
|
+
async def __call__(self, params: Params) -> ToolReturnValue:
|
|
32
|
+
if self._service_config:
|
|
33
|
+
ret = await self._fetch_with_service(params)
|
|
34
|
+
if not ret.is_error:
|
|
35
|
+
return ret
|
|
36
|
+
logger.warning("Failed to fetch URL via service: {error}", error=ret.message)
|
|
37
|
+
# fallback to local fetch if service fetch fails
|
|
38
|
+
return await self.fetch_with_http_get(params)
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
async def fetch_with_http_get(params: Params) -> ToolReturnValue:
|
|
24
42
|
builder = ToolResultBuilder(max_line_length=None)
|
|
25
|
-
|
|
26
43
|
try:
|
|
27
44
|
async with (
|
|
28
45
|
new_client_session() as session,
|
|
@@ -45,7 +62,12 @@ class FetchURL(CallableTool2[Params]):
|
|
|
45
62
|
brief=f"HTTP {response.status} error",
|
|
46
63
|
)
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
resp_text = await response.text()
|
|
66
|
+
|
|
67
|
+
content_type = response.headers.get(aiohttp.hdrs.CONTENT_TYPE, "").lower()
|
|
68
|
+
if content_type.startswith(("text/plain", "text/markdown")):
|
|
69
|
+
builder.write(resp_text)
|
|
70
|
+
return builder.ok("The returned content is the full content of the page.")
|
|
49
71
|
except aiohttp.ClientError as e:
|
|
50
72
|
return builder.error(
|
|
51
73
|
(
|
|
@@ -55,14 +77,14 @@ class FetchURL(CallableTool2[Params]):
|
|
|
55
77
|
brief="Network error",
|
|
56
78
|
)
|
|
57
79
|
|
|
58
|
-
if not
|
|
80
|
+
if not resp_text:
|
|
59
81
|
return builder.ok(
|
|
60
82
|
"The response body is empty.",
|
|
61
83
|
brief="Empty response body",
|
|
62
84
|
)
|
|
63
85
|
|
|
64
86
|
extracted_text = trafilatura.extract(
|
|
65
|
-
|
|
87
|
+
resp_text,
|
|
66
88
|
include_comments=True,
|
|
67
89
|
include_tables=True,
|
|
68
90
|
include_formatting=False,
|
|
@@ -83,13 +105,46 @@ class FetchURL(CallableTool2[Params]):
|
|
|
83
105
|
builder.write(extracted_text)
|
|
84
106
|
return builder.ok("The returned content is the main text content extracted from the page.")
|
|
85
107
|
|
|
108
|
+
async def _fetch_with_service(self, params: Params) -> ToolReturnValue:
|
|
109
|
+
assert self._service_config is not None
|
|
86
110
|
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
tool_call = get_current_tool_call_or_none()
|
|
112
|
+
assert tool_call is not None, "Tool call is expected to be set"
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
114
|
+
builder = ToolResultBuilder(max_line_length=None)
|
|
115
|
+
headers = {
|
|
116
|
+
"User-Agent": USER_AGENT,
|
|
117
|
+
"Authorization": f"Bearer {self._service_config.api_key.get_secret_value()}",
|
|
118
|
+
"Accept": "text/markdown",
|
|
119
|
+
"X-Msh-Tool-Call-Id": tool_call.id,
|
|
120
|
+
**(self._service_config.custom_headers or {}),
|
|
121
|
+
}
|
|
94
122
|
|
|
95
|
-
|
|
123
|
+
try:
|
|
124
|
+
async with (
|
|
125
|
+
new_client_session() as session,
|
|
126
|
+
session.post(
|
|
127
|
+
self._service_config.base_url,
|
|
128
|
+
headers=headers,
|
|
129
|
+
json={"url": params.url},
|
|
130
|
+
) as response,
|
|
131
|
+
):
|
|
132
|
+
if response.status != 200:
|
|
133
|
+
return builder.error(
|
|
134
|
+
f"Failed to fetch URL via service. Status: {response.status}.",
|
|
135
|
+
brief="Failed to fetch URL via fetch service",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
content = await response.text()
|
|
139
|
+
builder.write(content)
|
|
140
|
+
return builder.ok(
|
|
141
|
+
"The returned content is the main content extracted from the page."
|
|
142
|
+
)
|
|
143
|
+
except aiohttp.ClientError as e:
|
|
144
|
+
return builder.error(
|
|
145
|
+
(
|
|
146
|
+
f"Failed to fetch URL via service due to network error: {str(e)}. "
|
|
147
|
+
"This may indicate the service is unreachable."
|
|
148
|
+
),
|
|
149
|
+
brief="Network error when calling fetch service",
|
|
150
|
+
)
|
kimi_cli/tools/web/search.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import override
|
|
3
3
|
|
|
4
|
-
from kosong.tooling import CallableTool2,
|
|
4
|
+
from kosong.tooling import CallableTool2, ToolReturnValue
|
|
5
5
|
from pydantic import BaseModel, Field, ValidationError
|
|
6
6
|
|
|
7
7
|
from kimi_cli.config import Config
|
|
8
8
|
from kimi_cli.constant import USER_AGENT
|
|
9
9
|
from kimi_cli.soul.toolset import get_current_tool_call_or_none
|
|
10
|
+
from kimi_cli.tools import SkipThisTool
|
|
10
11
|
from kimi_cli.tools.utils import ToolResultBuilder, load_desc
|
|
11
12
|
from kimi_cli.utils.aiohttp import new_client_session
|
|
12
13
|
|
|
@@ -39,19 +40,16 @@ class SearchWeb(CallableTool2[Params]):
|
|
|
39
40
|
description: str = load_desc(Path(__file__).parent / "search.md", {})
|
|
40
41
|
params: type[Params] = Params
|
|
41
42
|
|
|
42
|
-
def __init__(self, config: Config
|
|
43
|
-
super().__init__(
|
|
44
|
-
if config.services.moonshot_search is
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self._base_url = ""
|
|
50
|
-
self._api_key = ""
|
|
51
|
-
self._custom_headers = {}
|
|
43
|
+
def __init__(self, config: Config):
|
|
44
|
+
super().__init__()
|
|
45
|
+
if config.services.moonshot_search is None:
|
|
46
|
+
raise SkipThisTool()
|
|
47
|
+
self._base_url = config.services.moonshot_search.base_url
|
|
48
|
+
self._api_key = config.services.moonshot_search.api_key.get_secret_value()
|
|
49
|
+
self._custom_headers = config.services.moonshot_search.custom_headers or {}
|
|
52
50
|
|
|
53
51
|
@override
|
|
54
|
-
async def __call__(self, params: Params) ->
|
|
52
|
+
async def __call__(self, params: Params) -> ToolReturnValue:
|
|
55
53
|
builder = ToolResultBuilder(max_line_length=None)
|
|
56
54
|
|
|
57
55
|
if not self._base_url or not self._api_key:
|