kimi-cli 0.45__py3-none-any.whl → 0.46__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 +11 -0
- kimi_cli/app.py +7 -0
- kimi_cli/cli.py +9 -2
- kimi_cli/config.py +1 -1
- kimi_cli/llm.py +14 -0
- kimi_cli/soul/toolset.py +2 -1
- kimi_cli/tools/__init__.py +13 -10
- kimi_cli/tools/bash/__init__.py +13 -4
- kimi_cli/tools/dmail/__init__.py +3 -3
- kimi_cli/tools/file/glob.py +2 -2
- kimi_cli/tools/file/grep.py +1 -1
- kimi_cli/tools/file/patch.py +38 -8
- kimi_cli/tools/file/read.py +5 -4
- kimi_cli/tools/file/replace.py +2 -2
- kimi_cli/tools/file/write.py +2 -2
- kimi_cli/tools/mcp.py +6 -3
- kimi_cli/tools/task/__init__.py +2 -2
- kimi_cli/tools/web/search.py +2 -2
- kimi_cli/ui/wire/README.md +109 -0
- kimi_cli/ui/wire/__init__.py +340 -0
- kimi_cli/ui/wire/jsonrpc.py +48 -0
- kimi_cli/wire/message.py +89 -2
- {kimi_cli-0.45.dist-info → kimi_cli-0.46.dist-info}/METADATA +3 -2
- {kimi_cli-0.45.dist-info → kimi_cli-0.46.dist-info}/RECORD +26 -23
- {kimi_cli-0.45.dist-info → kimi_cli-0.46.dist-info}/WHEEL +0 -0
- {kimi_cli-0.45.dist-info → kimi_cli-0.46.dist-info}/entry_points.txt +0 -0
kimi_cli/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,17 @@ 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.46] - 2025-11-03
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Introduce Wire over stdio for local IPC (experimental, subject to change)
|
|
17
|
+
- Support Anthropic provider type
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Fix binary packed by PyInstaller not working due to wrong entrypoint
|
|
22
|
+
|
|
12
23
|
## [0.45] - 2025-10-31
|
|
13
24
|
|
|
14
25
|
### Added
|
kimi_cli/app.py
CHANGED
|
@@ -193,3 +193,10 @@ class KimiCLI:
|
|
|
193
193
|
with self._app_env():
|
|
194
194
|
app = ACPServer(self._soul)
|
|
195
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()
|
kimi_cli/cli.py
CHANGED
|
@@ -16,7 +16,7 @@ class Reload(Exception):
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
UIMode = Literal["shell", "print", "acp"]
|
|
19
|
+
UIMode = Literal["shell", "print", "acp", "wire"]
|
|
20
20
|
InputFormat = Literal["text", "stream-json"]
|
|
21
21
|
OutputFormat = Literal["text", "stream-json"]
|
|
22
22
|
|
|
@@ -170,9 +170,12 @@ def kimi(
|
|
|
170
170
|
|
|
171
171
|
echo: Callable[..., None] = click.echo if verbose else _noop_echo
|
|
172
172
|
|
|
173
|
+
if debug:
|
|
174
|
+
logger.enable("kosong")
|
|
173
175
|
logger.add(
|
|
174
176
|
get_share_dir() / "logs" / "kimi.log",
|
|
175
|
-
level
|
|
177
|
+
# FIXME: configure level for different modules
|
|
178
|
+
level="TRACE" if debug else "INFO",
|
|
176
179
|
rotation="06:00",
|
|
177
180
|
retention="10 days",
|
|
178
181
|
)
|
|
@@ -238,6 +241,10 @@ def kimi(
|
|
|
238
241
|
if command is not None:
|
|
239
242
|
logger.warning("ACP server ignores command argument")
|
|
240
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()
|
|
241
248
|
|
|
242
249
|
while True:
|
|
243
250
|
try:
|
kimi_cli/config.py
CHANGED
|
@@ -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", "openai_responses", "_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"""
|
kimi_cli/llm.py
CHANGED
|
@@ -105,6 +105,20 @@ def create_llm(
|
|
|
105
105
|
api_key=provider.api_key.get_secret_value(),
|
|
106
106
|
stream=stream,
|
|
107
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
|
+
)
|
|
108
122
|
case "_chaos":
|
|
109
123
|
from kosong.chat_provider.chaos import ChaosChatProvider, ChaosConfig
|
|
110
124
|
|
kimi_cli/soul/toolset.py
CHANGED
|
@@ -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
|
|
kimi_cli/tools/__init__.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
|
@@ -29,15 +30,15 @@ def extract_subtitle(lexer: streamingjson.Lexer, tool_name: str) -> str | None:
|
|
|
29
30
|
case "SetTodoList":
|
|
30
31
|
if not isinstance(curr_args, dict) or not curr_args.get("todos"):
|
|
31
32
|
return None
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
from kimi_cli.tools.todo import Params
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
todo_params = Params.model_validate(curr_args)
|
|
38
|
+
for todo in todo_params.todos:
|
|
39
|
+
subtitle += f"• {todo.title} [{todo.status}]\n"
|
|
40
|
+
except Exception:
|
|
33
41
|
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
42
|
return "\n" + subtitle.strip()
|
|
42
43
|
case "Bash":
|
|
43
44
|
if not isinstance(curr_args, dict) or not curr_args.get("command"):
|
|
@@ -72,7 +73,9 @@ def extract_subtitle(lexer: streamingjson.Lexer, tool_name: str) -> str | None:
|
|
|
72
73
|
return None
|
|
73
74
|
subtitle = str(curr_args["url"])
|
|
74
75
|
case _:
|
|
75
|
-
|
|
76
|
+
# lexer.json_content is list[str] based on streamingjson source code
|
|
77
|
+
content: list[str] = cast(list[str], lexer.json_content) # pyright: ignore[reportUnknownMemberType]
|
|
78
|
+
subtitle = "".join(content)
|
|
76
79
|
if tool_name not in ["SetTodoList"]:
|
|
77
80
|
subtitle = shorten_middle(subtitle, width=50)
|
|
78
81
|
return subtitle
|
kimi_cli/tools/bash/__init__.py
CHANGED
|
@@ -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(
|
kimi_cli/tools/dmail/__init__.py
CHANGED
|
@@ -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
|
|
kimi_cli/tools/file/glob.py
CHANGED
|
@@ -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
|
|
kimi_cli/tools/file/grep.py
CHANGED
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from typing import override
|
|
10
10
|
|
|
11
11
|
import aiohttp
|
|
12
|
-
import ripgrepy
|
|
12
|
+
import ripgrepy # pyright: ignore[reportMissingTypeStubs]
|
|
13
13
|
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnType
|
|
14
14
|
from pydantic import BaseModel, Field
|
|
15
15
|
|
kimi_cli/tools/file/patch.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import override
|
|
2
|
+
from typing import Any, Literal, override
|
|
3
3
|
|
|
4
4
|
import aiofiles
|
|
5
|
-
import patch_ng
|
|
5
|
+
import patch_ng # pyright: ignore[reportMissingTypeStubs]
|
|
6
6
|
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnType
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
@@ -12,6 +12,36 @@ from kimi_cli.tools.file import FileActions
|
|
|
12
12
|
from kimi_cli.tools.utils import ToolRejectedError
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def _parse_patch(diff_bytes: bytes) -> patch_ng.PatchSet | None:
|
|
16
|
+
"""Parse patch from bytes, returning PatchSet or None on error.
|
|
17
|
+
|
|
18
|
+
This wrapper provides type hints for the untyped patch_ng.fromstring function.
|
|
19
|
+
"""
|
|
20
|
+
result: patch_ng.PatchSet | Literal[False] = patch_ng.fromstring(diff_bytes) # pyright: ignore[reportUnknownMemberType]
|
|
21
|
+
return result if result is not False else None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _count_hunks(patch_set: patch_ng.PatchSet) -> int:
|
|
25
|
+
"""Count total hunks across all items in a PatchSet.
|
|
26
|
+
|
|
27
|
+
This wrapper provides type hints for the untyped patch_ng library.
|
|
28
|
+
From source code inspection: PatchSet.items is list[Patch], Patch.hunks is list[Hunk].
|
|
29
|
+
Type ignore needed because patch_ng lacks type annotations.
|
|
30
|
+
"""
|
|
31
|
+
items: list[patch_ng.Patch] = patch_set.items # pyright: ignore[reportUnknownMemberType]
|
|
32
|
+
# Each Patch has a hunks attribute (list[Hunk])
|
|
33
|
+
return sum(len(item.hunks) for item in items) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _apply_patch(patch_set: patch_ng.PatchSet, root: str) -> bool:
|
|
37
|
+
"""Apply a patch to files under the given root directory.
|
|
38
|
+
|
|
39
|
+
This wrapper provides type hints for the untyped patch_ng.apply method.
|
|
40
|
+
"""
|
|
41
|
+
success: Any = patch_set.apply(root=root) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
42
|
+
return bool(success) # pyright: ignore[reportUnknownArgumentType]
|
|
43
|
+
|
|
44
|
+
|
|
15
45
|
class Params(BaseModel):
|
|
16
46
|
path: str = Field(description="The absolute path to the file to apply the patch to.")
|
|
17
47
|
diff: str = Field(description="The diff content in unified format to apply.")
|
|
@@ -22,7 +52,7 @@ class PatchFile(CallableTool2[Params]):
|
|
|
22
52
|
description: str = (Path(__file__).parent / "patch.md").read_text(encoding="utf-8")
|
|
23
53
|
params: type[Params] = Params
|
|
24
54
|
|
|
25
|
-
def __init__(self, builtin_args: BuiltinSystemPromptArgs, approval: Approval, **kwargs):
|
|
55
|
+
def __init__(self, builtin_args: BuiltinSystemPromptArgs, approval: Approval, **kwargs: Any):
|
|
26
56
|
super().__init__(**kwargs)
|
|
27
57
|
self._work_dir = builtin_args.KIMI_WORK_DIR
|
|
28
58
|
self._approval = approval
|
|
@@ -87,10 +117,10 @@ class PatchFile(CallableTool2[Params]):
|
|
|
87
117
|
original_content = await f.read()
|
|
88
118
|
|
|
89
119
|
# Create patch object directly from string (no temporary file needed!)
|
|
90
|
-
patch_set =
|
|
120
|
+
patch_set = _parse_patch(params.diff.encode("utf-8"))
|
|
91
121
|
|
|
92
|
-
# Handle case where
|
|
93
|
-
if
|
|
122
|
+
# Handle case where parsing failed
|
|
123
|
+
if patch_set is None:
|
|
94
124
|
return ToolError(
|
|
95
125
|
message=(
|
|
96
126
|
"Failed to parse diff content: invalid patch format or no valid hunks found"
|
|
@@ -99,7 +129,7 @@ class PatchFile(CallableTool2[Params]):
|
|
|
99
129
|
)
|
|
100
130
|
|
|
101
131
|
# Count total hunks across all items
|
|
102
|
-
total_hunks =
|
|
132
|
+
total_hunks = _count_hunks(patch_set)
|
|
103
133
|
|
|
104
134
|
if total_hunks == 0:
|
|
105
135
|
return ToolError(
|
|
@@ -108,7 +138,7 @@ class PatchFile(CallableTool2[Params]):
|
|
|
108
138
|
)
|
|
109
139
|
|
|
110
140
|
# Apply the patch
|
|
111
|
-
success = patch_set
|
|
141
|
+
success = _apply_patch(patch_set, str(p.parent))
|
|
112
142
|
|
|
113
143
|
if not success:
|
|
114
144
|
return ToolError(
|
kimi_cli/tools/file/read.py
CHANGED
|
@@ -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
|
import aiofiles
|
|
5
5
|
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnType
|
|
@@ -47,8 +47,9 @@ class ReadFile(CallableTool2[Params]):
|
|
|
47
47
|
)
|
|
48
48
|
params: type[Params] = Params
|
|
49
49
|
|
|
50
|
-
def __init__(self, builtin_args: BuiltinSystemPromptArgs, **kwargs):
|
|
50
|
+
def __init__(self, builtin_args: BuiltinSystemPromptArgs, **kwargs: Any) -> None:
|
|
51
51
|
super().__init__(**kwargs)
|
|
52
|
+
|
|
52
53
|
self._work_dir = builtin_args.KIMI_WORK_DIR
|
|
53
54
|
|
|
54
55
|
@override
|
|
@@ -84,7 +85,7 @@ class ReadFile(CallableTool2[Params]):
|
|
|
84
85
|
|
|
85
86
|
lines: list[str] = []
|
|
86
87
|
n_bytes = 0
|
|
87
|
-
truncated_line_numbers = []
|
|
88
|
+
truncated_line_numbers: list[int] = []
|
|
88
89
|
max_lines_reached = False
|
|
89
90
|
max_bytes_reached = False
|
|
90
91
|
async with aiofiles.open(p, encoding="utf-8", errors="replace") as f:
|
|
@@ -108,7 +109,7 @@ class ReadFile(CallableTool2[Params]):
|
|
|
108
109
|
break
|
|
109
110
|
|
|
110
111
|
# Format output with line numbers like `cat -n`
|
|
111
|
-
lines_with_no = []
|
|
112
|
+
lines_with_no: list[str] = []
|
|
112
113
|
for line_num, line in zip(
|
|
113
114
|
range(params.line_offset, params.line_offset + len(lines)), lines, strict=True
|
|
114
115
|
):
|
kimi_cli/tools/file/replace.py
CHANGED
|
@@ -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
|
import aiofiles
|
|
5
5
|
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnType
|
|
@@ -32,7 +32,7 @@ class StrReplaceFile(CallableTool2[Params]):
|
|
|
32
32
|
description: str = (Path(__file__).parent / "replace.md").read_text(encoding="utf-8")
|
|
33
33
|
params: type[Params] = Params
|
|
34
34
|
|
|
35
|
-
def __init__(self, builtin_args: BuiltinSystemPromptArgs, approval: Approval, **kwargs):
|
|
35
|
+
def __init__(self, builtin_args: BuiltinSystemPromptArgs, approval: Approval, **kwargs: Any):
|
|
36
36
|
super().__init__(**kwargs)
|
|
37
37
|
self._work_dir = builtin_args.KIMI_WORK_DIR
|
|
38
38
|
self._approval = approval
|
kimi_cli/tools/file/write.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import Literal, override
|
|
2
|
+
from typing import Any, Literal, override
|
|
3
3
|
|
|
4
4
|
import aiofiles
|
|
5
5
|
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnType
|
|
@@ -29,7 +29,7 @@ class WriteFile(CallableTool2[Params]):
|
|
|
29
29
|
description: str = (Path(__file__).parent / "write.md").read_text(encoding="utf-8")
|
|
30
30
|
params: type[Params] = Params
|
|
31
31
|
|
|
32
|
-
def __init__(self, builtin_args: BuiltinSystemPromptArgs, approval: Approval, **kwargs):
|
|
32
|
+
def __init__(self, builtin_args: BuiltinSystemPromptArgs, approval: Approval, **kwargs: Any):
|
|
33
33
|
super().__init__(**kwargs)
|
|
34
34
|
self._work_dir = builtin_args.KIMI_WORK_DIR
|
|
35
35
|
self._approval = approval
|
kimi_cli/tools/mcp.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
import fastmcp
|
|
2
4
|
import mcp
|
|
3
5
|
from fastmcp.client.client import CallToolResult
|
|
6
|
+
from fastmcp.client.transports import ClientTransport
|
|
4
7
|
from kosong.base.message import AudioURLPart, ContentPart, ImageURLPart, TextPart
|
|
5
8
|
from kosong.tooling import CallableTool, ToolOk, ToolReturnType
|
|
6
9
|
|
|
7
10
|
|
|
8
|
-
class MCPTool(CallableTool):
|
|
9
|
-
def __init__(self, mcp_tool: mcp.Tool, client: fastmcp.Client, **kwargs):
|
|
11
|
+
class MCPTool[T: ClientTransport](CallableTool):
|
|
12
|
+
def __init__(self, mcp_tool: mcp.Tool, client: fastmcp.Client[T], **kwargs: Any):
|
|
10
13
|
super().__init__(
|
|
11
14
|
name=mcp_tool.name,
|
|
12
15
|
description=mcp_tool.description or "",
|
|
@@ -16,7 +19,7 @@ class MCPTool(CallableTool):
|
|
|
16
19
|
self._mcp_tool = mcp_tool
|
|
17
20
|
self._client = client
|
|
18
21
|
|
|
19
|
-
async def __call__(self, *args, **kwargs) -> ToolReturnType:
|
|
22
|
+
async def __call__(self, *args: Any, **kwargs: Any) -> ToolReturnType:
|
|
20
23
|
async with self._client as client:
|
|
21
24
|
result = await client.call_tool(self._mcp_tool.name, kwargs, timeout=20)
|
|
22
25
|
return convert_tool_result(result)
|
kimi_cli/tools/task/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import override
|
|
3
|
+
from typing import Any, override
|
|
4
4
|
|
|
5
5
|
from kosong.tooling import CallableTool2, ToolError, ToolOk, ToolReturnType
|
|
6
6
|
from pydantic import BaseModel, Field
|
|
@@ -49,7 +49,7 @@ class Task(CallableTool2[Params]):
|
|
|
49
49
|
name: str = "Task"
|
|
50
50
|
params: type[Params] = Params
|
|
51
51
|
|
|
52
|
-
def __init__(self, agent_spec: ResolvedAgentSpec, runtime: Runtime, **kwargs):
|
|
52
|
+
def __init__(self, agent_spec: ResolvedAgentSpec, runtime: Runtime, **kwargs: Any):
|
|
53
53
|
super().__init__(
|
|
54
54
|
description=load_desc(
|
|
55
55
|
Path(__file__).parent / "task.md",
|
kimi_cli/tools/web/search.py
CHANGED
|
@@ -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, ToolReturnType
|
|
5
5
|
from pydantic import BaseModel, Field, ValidationError
|
|
@@ -39,7 +39,7 @@ class SearchWeb(CallableTool2[Params]):
|
|
|
39
39
|
description: str = load_desc(Path(__file__).parent / "search.md", {})
|
|
40
40
|
params: type[Params] = Params
|
|
41
41
|
|
|
42
|
-
def __init__(self, config: Config, **kwargs):
|
|
42
|
+
def __init__(self, config: Config, **kwargs: Any):
|
|
43
43
|
super().__init__(**kwargs)
|
|
44
44
|
if config.services.moonshot_search is not None:
|
|
45
45
|
self._base_url = config.services.moonshot_search.base_url
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Wire over STDIO
|
|
2
|
+
|
|
3
|
+
Learn how `WireServer` (src/kimi_cli/ui/wire/__init__.py) exposes the Soul runtime over stdio.
|
|
4
|
+
Use this reference when building clients or SDKs.
|
|
5
|
+
|
|
6
|
+
## Transport
|
|
7
|
+
- The server acquires stdio streams via `acp.stdio_streams()` and stays alive until stdin closes.
|
|
8
|
+
- Messages use newline-delimited JSON. Each object must include `"jsonrpc": "2.0"`.
|
|
9
|
+
- Outbound JSON is UTF-8 encoded with compact separators `(",", ":")`.
|
|
10
|
+
|
|
11
|
+
## Lifecycle
|
|
12
|
+
1. A client launches `kimi` (or another entry point) with the wire UI enabled.
|
|
13
|
+
2. `WireServer.run()` spawns a reader loop on stdin and a writer loop draining an internal queue.
|
|
14
|
+
3. Incoming payloads are validated by `JSONRPC_MESSAGE_ADAPTER`; invalid objects only log warnings.
|
|
15
|
+
4. The Soul uses `Wire` (src/kimi_cli/wire/__init__.py); the UI forwards every message as JSON-RPC.
|
|
16
|
+
5. EOF on stdin or a fatal error cancels the Soul, rejects approvals, and closes stdout.
|
|
17
|
+
|
|
18
|
+
## Client → Server calls
|
|
19
|
+
|
|
20
|
+
### `run`
|
|
21
|
+
- Request:
|
|
22
|
+
```json
|
|
23
|
+
{"jsonrpc": "2.0", "id": "<request-id>", "method": "run", "params": {"input": "<prompt>"}}
|
|
24
|
+
```
|
|
25
|
+
`params.prompt` is accepted as an alias for `params.input`.
|
|
26
|
+
- Success results:
|
|
27
|
+
- `{"status": "finished"}` when the run completes.
|
|
28
|
+
- `{"status": "cancelled"}` when either side interrupts.
|
|
29
|
+
- `{"status": "max_steps_reached", "steps": <int>}` when the step limit triggers.
|
|
30
|
+
- Error codes:
|
|
31
|
+
- `-32000`: A run is already in progress.
|
|
32
|
+
- `-32602`: The `input` or `prompt` parameter is missing or not a string.
|
|
33
|
+
- `-32001`: LLM is not configured.
|
|
34
|
+
- `-32002`: The chat provider reported an error.
|
|
35
|
+
- `-32003`: The requested LLM is unsupported.
|
|
36
|
+
- `-32099`: An unhandled exception occurred during the run.
|
|
37
|
+
|
|
38
|
+
### `interrupt`
|
|
39
|
+
- Request:
|
|
40
|
+
```json
|
|
41
|
+
{"jsonrpc": "2.0", "id": "<request-id>", "method": "interrupt", "params": {}}
|
|
42
|
+
```
|
|
43
|
+
The `id` field is optional; omitting it turns the request into a notification.
|
|
44
|
+
- Success results:
|
|
45
|
+
- `{"status": "ok"}` when a running Soul acknowledges the interrupt.
|
|
46
|
+
- `{"status": "idle"}` when no run is active.
|
|
47
|
+
- Interrupt requests never raise protocol errors.
|
|
48
|
+
|
|
49
|
+
## Server → Client traffic
|
|
50
|
+
|
|
51
|
+
### Event notifications
|
|
52
|
+
Events are JSON-RPC notifications with method `event` and no `id`.
|
|
53
|
+
Payloads come from `serialize_event` (src/kimi_cli/wire/message.py):
|
|
54
|
+
- `step_begin`: payload `{"n": <int>}` with the 1-based step counter.
|
|
55
|
+
- `step_interrupted`: no payload; the Soul paused mid-step.
|
|
56
|
+
- `compaction_begin`: no payload; a compaction pass started.
|
|
57
|
+
- `compaction_end`: no payload; always follows `compaction_begin`.
|
|
58
|
+
- `status_update`: payload `{"context_usage": <int>}` from `StatusSnapshot`.
|
|
59
|
+
- `content_part`: JSON object produced by `ContentPart.model_dump(mode="json", exclude_none=True)`.
|
|
60
|
+
- `tool_call`: JSON object produced by `ToolCall.model_dump(mode="json", exclude_none=True)`.
|
|
61
|
+
- `tool_call_part`: JSON object from `ToolCallPart.model_dump(mode="json", exclude_none=True)`.
|
|
62
|
+
- `tool_result`: object with `tool_call_id`, `ok`, and `result` (`output`, `message`, `brief`).
|
|
63
|
+
When `ok` is true the `output` may be text, a JSON object, or an array of JSON objects for
|
|
64
|
+
multi-part content.
|
|
65
|
+
|
|
66
|
+
Event order mirrors Soul execution because the server uses an `asyncio.Queue` for FIFO delivery.
|
|
67
|
+
|
|
68
|
+
### Approval requests
|
|
69
|
+
- Approval prompts use method `request`; their `id` equals the UUID in `ApprovalRequest.id`:
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"jsonrpc": "2.0",
|
|
73
|
+
"id": "<approval-id>",
|
|
74
|
+
"method": "request",
|
|
75
|
+
"params": {
|
|
76
|
+
"type": "approval",
|
|
77
|
+
"payload": {
|
|
78
|
+
"id": "<approval-id>",
|
|
79
|
+
"tool_call_id": "<tool-call-id>",
|
|
80
|
+
"sender": "<agent>",
|
|
81
|
+
"action": "<action>",
|
|
82
|
+
"description": "<human readable context>"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
- Clients reply with JSON-RPC success.
|
|
88
|
+
`result.response` must be `approve`, `approve_for_session`, or `reject`:
|
|
89
|
+
```json
|
|
90
|
+
{"jsonrpc": "2.0", "id": "<approval-id>", "result": {"response": "approve"}}
|
|
91
|
+
```
|
|
92
|
+
- Error responses or unknown values are interpreted as rejection.
|
|
93
|
+
- Unanswered approvals are auto-rejected during server shutdown.
|
|
94
|
+
|
|
95
|
+
## Error responses from the server
|
|
96
|
+
Errors follow JSON-RPC semantics.
|
|
97
|
+
The error object includes `code` and `message`.
|
|
98
|
+
Custom codes live in the `-320xx` range.
|
|
99
|
+
Clients should allow an optional `data` field even though the server omits it today.
|
|
100
|
+
|
|
101
|
+
## Shutdown semantics
|
|
102
|
+
- Shutdown cancels runs, stops the writer queue, rejects pending approvals, and closes stdout.
|
|
103
|
+
- EOF on stdout signals process exit; clients can treat it as terminal.
|
|
104
|
+
|
|
105
|
+
## Implementation notes for SDK authors
|
|
106
|
+
- Only one `run` call may execute at a time; queue additional runs client side.
|
|
107
|
+
- The payloads for `content_part`, `tool_call`, and `tool_call_part` already contain JSON objects.
|
|
108
|
+
- Approval handling is synchronous; always send a response even if the user cancels.
|
|
109
|
+
- Logging is verbose for non-stream messages; unknown methods are ignored for forward compatibility.
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
import acp # pyright: ignore[reportMissingTypeStubs]
|
|
8
|
+
from kosong.chat_provider import ChatProviderError
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
11
|
+
from kimi_cli.soul import LLMNotSet, LLMNotSupported, MaxStepsReached, RunCancelled, Soul, run_soul
|
|
12
|
+
from kimi_cli.utils.logging import logger
|
|
13
|
+
from kimi_cli.wire import WireUISide
|
|
14
|
+
from kimi_cli.wire.message import (
|
|
15
|
+
ApprovalRequest,
|
|
16
|
+
ApprovalResponse,
|
|
17
|
+
Event,
|
|
18
|
+
serialize_approval_request,
|
|
19
|
+
serialize_event,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .jsonrpc import (
|
|
23
|
+
JSONRPC_MESSAGE_ADAPTER,
|
|
24
|
+
JSONRPC_VERSION,
|
|
25
|
+
JSONRPCErrorResponse,
|
|
26
|
+
JSONRPCRequest,
|
|
27
|
+
JSONRPCSuccessResponse,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
_ResultKind = Literal["ok", "error"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _SoulRunner:
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
soul: Soul,
|
|
37
|
+
send_event: Callable[[Event], Awaitable[None]],
|
|
38
|
+
request_approval: Callable[[ApprovalRequest], Awaitable[ApprovalResponse]],
|
|
39
|
+
):
|
|
40
|
+
self._soul = soul
|
|
41
|
+
self._send_event = send_event
|
|
42
|
+
self._request_approval = request_approval
|
|
43
|
+
self._cancel_event: asyncio.Event | None = None
|
|
44
|
+
self._task: asyncio.Task[tuple[_ResultKind, Any]] | None = None
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def is_running(self) -> bool:
|
|
48
|
+
return self._task is not None and not self._task.done()
|
|
49
|
+
|
|
50
|
+
async def run(self, user_input: str) -> tuple[_ResultKind, Any]:
|
|
51
|
+
if self.is_running:
|
|
52
|
+
raise RuntimeError("Soul is already running")
|
|
53
|
+
|
|
54
|
+
self._cancel_event = asyncio.Event()
|
|
55
|
+
self._task = asyncio.create_task(self._run(user_input))
|
|
56
|
+
try:
|
|
57
|
+
return await self._task
|
|
58
|
+
finally:
|
|
59
|
+
self._task = None
|
|
60
|
+
self._cancel_event = None
|
|
61
|
+
|
|
62
|
+
async def interrupt(self) -> None:
|
|
63
|
+
if self._cancel_event is not None:
|
|
64
|
+
self._cancel_event.set()
|
|
65
|
+
|
|
66
|
+
async def shutdown(self) -> None:
|
|
67
|
+
await self.interrupt()
|
|
68
|
+
if self._task is not None:
|
|
69
|
+
self._task.cancel()
|
|
70
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
71
|
+
await self._task
|
|
72
|
+
self._task = None
|
|
73
|
+
self._cancel_event = None
|
|
74
|
+
|
|
75
|
+
async def _run(self, user_input: str) -> tuple[_ResultKind, Any]:
|
|
76
|
+
assert self._cancel_event is not None
|
|
77
|
+
try:
|
|
78
|
+
await run_soul(
|
|
79
|
+
self._soul,
|
|
80
|
+
user_input,
|
|
81
|
+
self._ui_loop,
|
|
82
|
+
self._cancel_event,
|
|
83
|
+
)
|
|
84
|
+
except LLMNotSet:
|
|
85
|
+
return ("error", (-32001, "LLM is not configured"))
|
|
86
|
+
except LLMNotSupported as e:
|
|
87
|
+
return ("error", (-32003, f"LLM not supported: {e}"))
|
|
88
|
+
except ChatProviderError as e:
|
|
89
|
+
return ("error", (-32002, f"LLM provider error: {e}"))
|
|
90
|
+
except MaxStepsReached as e:
|
|
91
|
+
return ("ok", {"status": "max_steps_reached", "steps": e.n_steps})
|
|
92
|
+
except RunCancelled:
|
|
93
|
+
return ("ok", {"status": "cancelled"})
|
|
94
|
+
except asyncio.CancelledError:
|
|
95
|
+
raise
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.exception("Soul run failed:")
|
|
98
|
+
return ("error", (-32099, f"Run failed: {e}"))
|
|
99
|
+
return ("ok", {"status": "finished"})
|
|
100
|
+
|
|
101
|
+
async def _ui_loop(self, wire: WireUISide) -> None:
|
|
102
|
+
while True:
|
|
103
|
+
message = await wire.receive()
|
|
104
|
+
if isinstance(message, ApprovalRequest):
|
|
105
|
+
response = await self._request_approval(message)
|
|
106
|
+
message.resolve(response)
|
|
107
|
+
else:
|
|
108
|
+
# must be Event
|
|
109
|
+
await self._send_event(message)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class WireServer:
|
|
113
|
+
def __init__(self, soul: Soul):
|
|
114
|
+
self._reader: asyncio.StreamReader | None = None
|
|
115
|
+
self._writer: asyncio.StreamWriter | None = None
|
|
116
|
+
self._write_task: asyncio.Task[None] | None = None
|
|
117
|
+
self._send_queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue()
|
|
118
|
+
self._pending_requests: dict[str, ApprovalRequest] = {}
|
|
119
|
+
self._runner = _SoulRunner(
|
|
120
|
+
soul,
|
|
121
|
+
send_event=self._send_event,
|
|
122
|
+
request_approval=self._request_approval,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
async def run(self) -> bool:
|
|
126
|
+
logger.info("Starting Wire server on stdio")
|
|
127
|
+
|
|
128
|
+
self._reader, self._writer = await acp.stdio_streams()
|
|
129
|
+
self._write_task = asyncio.create_task(self._write_loop())
|
|
130
|
+
try:
|
|
131
|
+
await self._read_loop()
|
|
132
|
+
finally:
|
|
133
|
+
await self._shutdown()
|
|
134
|
+
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
async def _read_loop(self) -> None:
|
|
138
|
+
assert self._reader is not None
|
|
139
|
+
|
|
140
|
+
while True:
|
|
141
|
+
line = await self._reader.readline()
|
|
142
|
+
if not line:
|
|
143
|
+
logger.info("stdin closed, Wire server exiting")
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
payload = json.loads(line.decode("utf-8"))
|
|
148
|
+
except json.JSONDecodeError:
|
|
149
|
+
logger.warning("Invalid JSON line: {line}", line=line)
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
await self._dispatch(payload)
|
|
153
|
+
|
|
154
|
+
async def _dispatch(self, payload: dict[str, Any]) -> None:
|
|
155
|
+
version = payload.get("jsonrpc")
|
|
156
|
+
if version != JSONRPC_VERSION:
|
|
157
|
+
logger.warning("Unexpected jsonrpc version: {version}", version=version)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
message = JSONRPC_MESSAGE_ADAPTER.validate_python(payload)
|
|
162
|
+
except ValidationError as e:
|
|
163
|
+
logger.warning(
|
|
164
|
+
"Ignoring malformed JSON-RPC payload: {message}; error={error}",
|
|
165
|
+
message=payload,
|
|
166
|
+
error=str(e),
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
match message:
|
|
171
|
+
case JSONRPCRequest():
|
|
172
|
+
await self._handle_request(message)
|
|
173
|
+
case JSONRPCSuccessResponse() | JSONRPCErrorResponse():
|
|
174
|
+
await self._handle_response(message)
|
|
175
|
+
|
|
176
|
+
async def _handle_request(self, message: JSONRPCRequest) -> None:
|
|
177
|
+
method = message.method
|
|
178
|
+
msg_id = message.id
|
|
179
|
+
params = message.params
|
|
180
|
+
|
|
181
|
+
if method == "run":
|
|
182
|
+
await self._handle_run(msg_id, params)
|
|
183
|
+
elif method == "interrupt":
|
|
184
|
+
await self._handle_interrupt(msg_id)
|
|
185
|
+
else:
|
|
186
|
+
logger.warning("Unknown method: {method}", method=method)
|
|
187
|
+
if msg_id is not None:
|
|
188
|
+
await self._send_error(msg_id, -32601, f"Unknown method: {method}")
|
|
189
|
+
|
|
190
|
+
async def _handle_response(
|
|
191
|
+
self,
|
|
192
|
+
message: JSONRPCSuccessResponse | JSONRPCErrorResponse,
|
|
193
|
+
) -> None:
|
|
194
|
+
msg_id = message.id
|
|
195
|
+
if msg_id is None:
|
|
196
|
+
logger.warning("Response without id: {message}", message=message.model_dump())
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
pending = self._pending_requests.get(msg_id)
|
|
200
|
+
if pending is None:
|
|
201
|
+
logger.warning("No pending request for response id={id}", id=msg_id)
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
if isinstance(message, JSONRPCErrorResponse):
|
|
206
|
+
pending.resolve(ApprovalResponse.REJECT)
|
|
207
|
+
else:
|
|
208
|
+
response = self._parse_approval_response(message.result)
|
|
209
|
+
pending.resolve(response)
|
|
210
|
+
finally:
|
|
211
|
+
self._pending_requests.pop(msg_id, None)
|
|
212
|
+
|
|
213
|
+
async def _handle_run(self, msg_id: Any, params: dict[str, Any]) -> None:
|
|
214
|
+
if msg_id is None:
|
|
215
|
+
logger.warning("Run notification ignored")
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
if self._runner.is_running:
|
|
219
|
+
await self._send_error(msg_id, -32000, "Run already in progress")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
user_input = params.get("input")
|
|
223
|
+
if not isinstance(user_input, str):
|
|
224
|
+
user_input = params.get("prompt")
|
|
225
|
+
if not isinstance(user_input, str):
|
|
226
|
+
await self._send_error(msg_id, -32602, "`input` (or `prompt`) must be a string")
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
kind, payload = await self._runner.run(user_input)
|
|
231
|
+
except RuntimeError:
|
|
232
|
+
await self._send_error(msg_id, -32000, "Run already in progress")
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
if kind == "error":
|
|
236
|
+
code, message = payload
|
|
237
|
+
await self._send_error(msg_id, code, message)
|
|
238
|
+
else:
|
|
239
|
+
await self._send_response(msg_id, payload)
|
|
240
|
+
|
|
241
|
+
async def _handle_interrupt(self, msg_id: Any) -> None:
|
|
242
|
+
if not self._runner.is_running:
|
|
243
|
+
if msg_id is not None:
|
|
244
|
+
await self._send_response(msg_id, {"status": "idle"})
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
await self._runner.interrupt()
|
|
248
|
+
if msg_id is not None:
|
|
249
|
+
await self._send_response(msg_id, {"status": "ok"})
|
|
250
|
+
|
|
251
|
+
async def _send_event(self, event: Event) -> None:
|
|
252
|
+
await self._send_notification("event", serialize_event(event))
|
|
253
|
+
|
|
254
|
+
async def _request_approval(self, request: ApprovalRequest) -> ApprovalResponse:
|
|
255
|
+
self._pending_requests[request.id] = request
|
|
256
|
+
|
|
257
|
+
await self._send_request(
|
|
258
|
+
request.id,
|
|
259
|
+
"request",
|
|
260
|
+
{"type": "approval", "payload": serialize_approval_request(request)},
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
return await request.wait()
|
|
265
|
+
finally:
|
|
266
|
+
self._pending_requests.pop(request.id, None)
|
|
267
|
+
|
|
268
|
+
def _parse_approval_response(self, result: dict[str, Any]) -> ApprovalResponse:
|
|
269
|
+
value = result.get("response")
|
|
270
|
+
try:
|
|
271
|
+
if isinstance(value, ApprovalResponse):
|
|
272
|
+
return value
|
|
273
|
+
return ApprovalResponse(str(value))
|
|
274
|
+
except ValueError:
|
|
275
|
+
logger.warning("Unknown approval response: {value}", value=value)
|
|
276
|
+
return ApprovalResponse.REJECT
|
|
277
|
+
|
|
278
|
+
async def _write_loop(self) -> None:
|
|
279
|
+
assert self._writer is not None
|
|
280
|
+
|
|
281
|
+
try:
|
|
282
|
+
while True:
|
|
283
|
+
try:
|
|
284
|
+
payload = await self._send_queue.get()
|
|
285
|
+
except asyncio.QueueShutDown:
|
|
286
|
+
logger.debug("Send queue shut down, stopping Wire server write loop")
|
|
287
|
+
break
|
|
288
|
+
data = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
|
289
|
+
self._writer.write(data.encode("utf-8") + b"\n")
|
|
290
|
+
await self._writer.drain()
|
|
291
|
+
except asyncio.CancelledError:
|
|
292
|
+
raise
|
|
293
|
+
except Exception:
|
|
294
|
+
logger.exception("Wire server write loop error:")
|
|
295
|
+
raise
|
|
296
|
+
|
|
297
|
+
async def _send_notification(self, method: str, params: Any) -> None:
|
|
298
|
+
await self._enqueue_payload(
|
|
299
|
+
{"jsonrpc": JSONRPC_VERSION, "method": method, "params": params}
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
async def _send_request(self, msg_id: Any, method: str, params: Any) -> None:
|
|
303
|
+
await self._enqueue_payload(
|
|
304
|
+
{"jsonrpc": JSONRPC_VERSION, "id": msg_id, "method": method, "params": params}
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
async def _send_response(self, msg_id: Any, result: Any) -> None:
|
|
308
|
+
await self._enqueue_payload({"jsonrpc": JSONRPC_VERSION, "id": msg_id, "result": result})
|
|
309
|
+
|
|
310
|
+
async def _send_error(self, msg_id: Any, code: int, message: str) -> None:
|
|
311
|
+
await self._enqueue_payload(
|
|
312
|
+
{"jsonrpc": JSONRPC_VERSION, "id": msg_id, "error": {"code": code, "message": message}}
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
async def _enqueue_payload(self, payload: dict[str, Any]) -> None:
|
|
316
|
+
try:
|
|
317
|
+
await self._send_queue.put(payload)
|
|
318
|
+
except asyncio.QueueShutDown:
|
|
319
|
+
logger.debug("Send queue shut down; dropping payload: {payload}", payload=payload)
|
|
320
|
+
|
|
321
|
+
async def _shutdown(self) -> None:
|
|
322
|
+
await self._runner.shutdown()
|
|
323
|
+
self._send_queue.shutdown()
|
|
324
|
+
|
|
325
|
+
if self._write_task is not None:
|
|
326
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
327
|
+
await self._write_task
|
|
328
|
+
|
|
329
|
+
for request in self._pending_requests.values():
|
|
330
|
+
if not request.resolved:
|
|
331
|
+
request.resolve(ApprovalResponse.REJECT)
|
|
332
|
+
self._pending_requests.clear()
|
|
333
|
+
|
|
334
|
+
if self._writer is not None:
|
|
335
|
+
self._writer.close()
|
|
336
|
+
with contextlib.suppress(Exception):
|
|
337
|
+
await self._writer.wait_closed()
|
|
338
|
+
self._writer = None
|
|
339
|
+
|
|
340
|
+
self._reader = None
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Any, Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
|
|
4
|
+
|
|
5
|
+
JSONRPC_VERSION = "2.0"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _MessageBase(BaseModel):
|
|
9
|
+
jsonrpc: Literal["2.0"]
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(extra="forbid")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JSONRPCRequest(_MessageBase):
|
|
15
|
+
method: str
|
|
16
|
+
id: str | None = None
|
|
17
|
+
params: dict[str, Any] = Field(default_factory=dict)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _ResponseBase(_MessageBase):
|
|
21
|
+
id: str | None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class JSONRPCSuccessResponse(_ResponseBase):
|
|
25
|
+
result: dict[str, Any]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class JSONRPCErrorObject(BaseModel):
|
|
29
|
+
code: int
|
|
30
|
+
message: str
|
|
31
|
+
data: Any | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class JSONRPCErrorResponse(_ResponseBase):
|
|
35
|
+
error: JSONRPCErrorObject
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
JSONRPCMessage = JSONRPCRequest | JSONRPCSuccessResponse | JSONRPCErrorResponse
|
|
39
|
+
JSONRPC_MESSAGE_ADAPTER = TypeAdapter[JSONRPCMessage](JSONRPCMessage)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"JSONRPCRequest",
|
|
43
|
+
"JSONRPCSuccessResponse",
|
|
44
|
+
"JSONRPCErrorObject",
|
|
45
|
+
"JSONRPCErrorResponse",
|
|
46
|
+
"JSONRPCMessage",
|
|
47
|
+
"JSONRPC_MESSAGE_ADAPTER",
|
|
48
|
+
]
|
kimi_cli/wire/message.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import uuid
|
|
3
|
+
from collections.abc import Sequence
|
|
3
4
|
from enum import Enum
|
|
4
|
-
from typing import TYPE_CHECKING, NamedTuple
|
|
5
|
+
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
5
6
|
|
|
6
7
|
from kosong.base.message import ContentPart, ToolCall, ToolCallPart
|
|
7
|
-
from kosong.tooling import ToolResult
|
|
8
|
+
from kosong.tooling import ToolOk, ToolResult
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from kimi_cli.soul import StatusSnapshot
|
|
@@ -89,3 +90,89 @@ class ApprovalRequest:
|
|
|
89
90
|
|
|
90
91
|
|
|
91
92
|
type WireMessage = Event | ApprovalRequest
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def serialize_event(event: Event) -> dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
Convert an event message into a JSON-serializable dictionary.
|
|
98
|
+
"""
|
|
99
|
+
match event:
|
|
100
|
+
case StepBegin():
|
|
101
|
+
return {"type": "step_begin", "payload": {"n": event.n}}
|
|
102
|
+
case StepInterrupted():
|
|
103
|
+
return {"type": "step_interrupted"}
|
|
104
|
+
case CompactionBegin():
|
|
105
|
+
return {"type": "compaction_begin"}
|
|
106
|
+
case CompactionEnd():
|
|
107
|
+
return {"type": "compaction_end"}
|
|
108
|
+
case StatusUpdate():
|
|
109
|
+
return {
|
|
110
|
+
"type": "status_update",
|
|
111
|
+
"payload": {"context_usage": event.status.context_usage},
|
|
112
|
+
}
|
|
113
|
+
case ContentPart():
|
|
114
|
+
return {
|
|
115
|
+
"type": "content_part",
|
|
116
|
+
"payload": event.model_dump(mode="json", exclude_none=True),
|
|
117
|
+
}
|
|
118
|
+
case ToolCall():
|
|
119
|
+
return {
|
|
120
|
+
"type": "tool_call",
|
|
121
|
+
"payload": event.model_dump(mode="json", exclude_none=True),
|
|
122
|
+
}
|
|
123
|
+
case ToolCallPart():
|
|
124
|
+
return {
|
|
125
|
+
"type": "tool_call_part",
|
|
126
|
+
"payload": event.model_dump(mode="json", exclude_none=True),
|
|
127
|
+
}
|
|
128
|
+
case ToolResult():
|
|
129
|
+
return {
|
|
130
|
+
"type": "tool_result",
|
|
131
|
+
"payload": serialize_tool_result(event),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def serialize_approval_request(request: ApprovalRequest) -> dict[str, Any]:
|
|
136
|
+
"""
|
|
137
|
+
Convert an ApprovalRequest into a JSON-serializable dictionary.
|
|
138
|
+
"""
|
|
139
|
+
return {
|
|
140
|
+
"id": request.id,
|
|
141
|
+
"tool_call_id": request.tool_call_id,
|
|
142
|
+
"sender": request.sender,
|
|
143
|
+
"action": request.action,
|
|
144
|
+
"description": request.description,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def serialize_tool_result(result: ToolResult) -> dict[str, Any]:
|
|
149
|
+
if isinstance(result.result, ToolOk):
|
|
150
|
+
ok = True
|
|
151
|
+
result_data = {
|
|
152
|
+
"output": _serialize_tool_output(result.result.output),
|
|
153
|
+
"message": result.result.message,
|
|
154
|
+
"brief": result.result.brief,
|
|
155
|
+
}
|
|
156
|
+
else:
|
|
157
|
+
ok = False
|
|
158
|
+
result_data = {
|
|
159
|
+
"output": result.result.output,
|
|
160
|
+
"message": result.result.message,
|
|
161
|
+
"brief": result.result.brief,
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
"tool_call_id": result.tool_call_id,
|
|
165
|
+
"ok": ok,
|
|
166
|
+
"result": result_data,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _serialize_tool_output(
|
|
171
|
+
output: str | ContentPart | Sequence[ContentPart],
|
|
172
|
+
) -> str | list[Any] | dict[str, Any]:
|
|
173
|
+
if isinstance(output, str):
|
|
174
|
+
return output
|
|
175
|
+
elif isinstance(output, ContentPart):
|
|
176
|
+
return output.model_dump(mode="json", exclude_none=True)
|
|
177
|
+
else: # Sequence[ContentPart]
|
|
178
|
+
return [part.model_dump(mode="json", exclude_none=True) for part in output]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: kimi-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.46
|
|
4
4
|
Summary: Kimi CLI is your next CLI agent.
|
|
5
5
|
Requires-Dist: agent-client-protocol==0.6.2
|
|
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
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
kimi_cli/CHANGELOG.md,sha256=
|
|
1
|
+
kimi_cli/CHANGELOG.md,sha256=cd7f214ad9fb6cdc9af293cc90d69aeba9c24b4dc878486a99187de70ff197d2,8832
|
|
2
2
|
kimi_cli/__init__.py,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
|
|
3
3
|
kimi_cli/agents/default/agent.yaml,sha256=6e5c51987ef5cfc0c4c4e34cc20b6fc975953ee219623fccae81a19155aab7b3,709
|
|
4
4
|
kimi_cli/agents/default/sub.yaml,sha256=e0c1ea34fdb04b0d6dc635709f0f130aff25d7f9fb97e238470143c8145be251,634
|
|
5
5
|
kimi_cli/agents/default/system.md,sha256=1d8fd4956b2442215396b5e9651771c9da8f9505ccbd3b6d5e91b1ac4ff35418,5001
|
|
6
6
|
kimi_cli/agentspec.py,sha256=1148b5184ca610b2fb261ce365a63eb2fc9d09497330fe0ea4b2567fc98d5657,4307
|
|
7
|
-
kimi_cli/app.py,sha256=
|
|
8
|
-
kimi_cli/cli.py,sha256=
|
|
9
|
-
kimi_cli/config.py,sha256=
|
|
7
|
+
kimi_cli/app.py,sha256=f7a6a39300df101504882121d3930d6a8660e0f4182421aac456fe3fa684e560,7260
|
|
8
|
+
kimi_cli/cli.py,sha256=54bd72907fc99feea96bbaf9eeb808a2a21a2f0c0c6b5d11229a4263597a5920,7215
|
|
9
|
+
kimi_cli/config.py,sha256=85ce4877f0f05f72e2f390b6ebb6e8be5525a719ea4a6e3560817527c5261fb8,5025
|
|
10
10
|
kimi_cli/constant.py,sha256=78e25b9304cca7b6f70bb08bf0e1fee4b066297a05386e56dd6935ba42027cd9,110
|
|
11
11
|
kimi_cli/exception.py,sha256=a3fec07566da7d2d34be8cc454fb825f34109bbde3cddf69e1ece6ab21b4b219,259
|
|
12
|
-
kimi_cli/llm.py,sha256=
|
|
12
|
+
kimi_cli/llm.py,sha256=a1c3537a427c0c3ace75bbc0cebf3749215562750570113078a1fcc9e9c80a86,5026
|
|
13
13
|
kimi_cli/metadata.py,sha256=9e9d4bc12ff26fc34e0e09d9068be989f2ff3c8b682ef453de69e442f8f672a1,1557
|
|
14
14
|
kimi_cli/prompts/__init__.py,sha256=6dc5ed2d841f145c09550075f30853cdc51f00d2f5d9aa1097f8edea770536e7,174
|
|
15
15
|
kimi_cli/prompts/compact.md,sha256=6655bd7d8270b24d8f97b51ef7c471cf71d686c56f8ec9a5cc9e47caa3aae87c,1877
|
|
@@ -26,27 +26,27 @@ kimi_cli/soul/denwarenji.py,sha256=66b95f052a1fa844e2347972d34e1916a7be24d3e4937
|
|
|
26
26
|
kimi_cli/soul/kimisoul.py,sha256=68c65fe0f97700832a42018ae00892d38dc79f14759265042f47b707dbc0c749,11822
|
|
27
27
|
kimi_cli/soul/message.py,sha256=7a52a6d4d63ef1a3621d93d5ff86887baa7e67019bf2e9a08c374fc130b8d152,2541
|
|
28
28
|
kimi_cli/soul/runtime.py,sha256=9421a3ce6882587a95ecdf77b3d75f3b7ecab55cf68dc57e3e563b93e5a02e46,2690
|
|
29
|
-
kimi_cli/soul/toolset.py,sha256=
|
|
30
|
-
kimi_cli/tools/__init__.py,sha256=
|
|
31
|
-
kimi_cli/tools/bash/__init__.py,sha256=
|
|
29
|
+
kimi_cli/soul/toolset.py,sha256=34d3cb269cb28f2ca1a2323d984625e2ee822d124789a5fcf63651bddf2102fe,777
|
|
30
|
+
kimi_cli/tools/__init__.py,sha256=3b0c7446947219933d0b6819c45b8b57befb8e498171b3374720925697b8c11e,3430
|
|
31
|
+
kimi_cli/tools/bash/__init__.py,sha256=e7821ce9296171c538117398d77311d220a8000de625302f9b8faf62de5910a5,3304
|
|
32
32
|
kimi_cli/tools/bash/bash.md,sha256=5d9cc54b3718097951340b0a737c8e1fa308341fd2c4ebd121be94de31dd5f73,2348
|
|
33
|
-
kimi_cli/tools/dmail/__init__.py,sha256=
|
|
33
|
+
kimi_cli/tools/dmail/__init__.py,sha256=dfc9ceccb5a47211ab99b7afa4624d13bf147ef68e2a079867f93cf46dfb58d2,1302
|
|
34
34
|
kimi_cli/tools/dmail/dmail.md,sha256=0d18cae387dd52127ddc99e296253c09e68ccba5f343153c0adbe77d7586e759,1912
|
|
35
35
|
kimi_cli/tools/file/__init__.py,sha256=1516fb4c71097f9c14b605e7b9a1872af8786bdcb48323d1fa83bb1419436abb,546
|
|
36
36
|
kimi_cli/tools/file/glob.md,sha256=11fbfaf6033f57b69c6f91077ddd90505036937cd7217600d96992b9a48b7ca7,1400
|
|
37
|
-
kimi_cli/tools/file/glob.py,sha256=
|
|
37
|
+
kimi_cli/tools/file/glob.py,sha256=a95f0f502fe6458f8c21a7aa5f15e27828993b05fed83b83f8a7c0dc9c50a48b,5431
|
|
38
38
|
kimi_cli/tools/file/grep.md,sha256=12353db42cf3b5d9d91ac7c0f0b9c2a732e8b050c23a78f4e668db823cca4d50,245
|
|
39
|
-
kimi_cli/tools/file/grep.py,sha256=
|
|
39
|
+
kimi_cli/tools/file/grep.py,sha256=77c8ae701c2d827bc5784e140fb007c4153162b278803956915858064f8733e5,9830
|
|
40
40
|
kimi_cli/tools/file/patch.md,sha256=f9edbed6c6a03bf7448a51acc1f42622395fd7f1674a814e9a3b2302d3b7b92e,404
|
|
41
|
-
kimi_cli/tools/file/patch.py,sha256=
|
|
41
|
+
kimi_cli/tools/file/patch.py,sha256=6ee74656c611510605cc2a70e32b8ed291163e79c458b06fd5a310d48650359f,6632
|
|
42
42
|
kimi_cli/tools/file/read.md,sha256=4c2d83e557daadc0612fb1a613e252b2c8e4df7ae57e6db094e9e75e994cb23b,1066
|
|
43
|
-
kimi_cli/tools/file/read.py,sha256=
|
|
43
|
+
kimi_cli/tools/file/read.py,sha256=76ea260a813a987a4e34706f6cc3c2b77fc3f31a76831883caa91f9ab86df6a6,5109
|
|
44
44
|
kimi_cli/tools/file/replace.md,sha256=f429f263fa580f2f6107907a33ba8800dcdbd4fc1d9ff8dc4f858bd76ec6bbc6,267
|
|
45
|
-
kimi_cli/tools/file/replace.py,sha256=
|
|
45
|
+
kimi_cli/tools/file/replace.py,sha256=c6c83869bc1431c645de52c920713e38970668fc531ee005680317a0dfeb4095,5214
|
|
46
46
|
kimi_cli/tools/file/write.md,sha256=f37b0f4742da57797ec4dd29fbd4fdc9b6617c6be644724a3b16d651c6129cec,324
|
|
47
|
-
kimi_cli/tools/file/write.py,sha256=
|
|
48
|
-
kimi_cli/tools/mcp.py,sha256=
|
|
49
|
-
kimi_cli/tools/task/__init__.py,sha256=
|
|
47
|
+
kimi_cli/tools/file/write.py,sha256=a7b48ae20ba6e8e6577ce30bc8561c74d0e76d4cebaa107bdcafb7611eae9dfc,4388
|
|
48
|
+
kimi_cli/tools/mcp.py,sha256=4e8b71c316a5d752ad05ee14af3a3db8ba433b7cc0a2231de4e1a03d4cf9365c,3736
|
|
49
|
+
kimi_cli/tools/task/__init__.py,sha256=7bd6bf96a40d0e2456079f56e2854f71263ad8844265be628753b5eec4a34d5c,6576
|
|
50
50
|
kimi_cli/tools/task/task.md,sha256=391cc3553c7d310a323626bae180dd41cb810fb1233583713ebde105f954147a,2280
|
|
51
51
|
kimi_cli/tools/test.py,sha256=c094a91a2d1a5259e192f1147facd5eebd5e5c413787fce167db90e4b41b5119,1442
|
|
52
52
|
kimi_cli/tools/think/__init__.py,sha256=31b06088e2404cb09d42e0acec97c185e4861890bb687f28b41f39cea01b5733,603
|
|
@@ -58,7 +58,7 @@ kimi_cli/tools/web/__init__.py,sha256=e13108c598828a8a05907a7a821e7ac92f5d63572b
|
|
|
58
58
|
kimi_cli/tools/web/fetch.md,sha256=56d00bd93b4e379c4f7efe445fce963eb26b8d20f85d4c19097ba6f33bd0019a,67
|
|
59
59
|
kimi_cli/tools/web/fetch.py,sha256=66448121d27d67f75b977c32244c721c2ccff1b2e097c2fe6717e66018d8f747,3183
|
|
60
60
|
kimi_cli/tools/web/search.md,sha256=24049f9e90d37083e0fc78b8b2e3a5f6fadf09bea00f36712b235d1212a2f532,146
|
|
61
|
-
kimi_cli/tools/web/search.py,sha256=
|
|
61
|
+
kimi_cli/tools/web/search.py,sha256=5a5a3b7b160d6bd41f99419f7b7ae346f9dd29d37b8dbbe2dad912763d462554,4527
|
|
62
62
|
kimi_cli/ui/__init__.py,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
|
|
63
63
|
kimi_cli/ui/acp/__init__.py,sha256=a35e17273e943168882865f90d922180c5a1f01de0d128c899ffcfe55a9db3c3,17120
|
|
64
64
|
kimi_cli/ui/print/__init__.py,sha256=ca402bec701a253acd6de5b57d59635ac0b05d4013cebc26877c5aa4aa2c27c7,5546
|
|
@@ -73,6 +73,9 @@ kimi_cli/ui/shell/replay.py,sha256=e54f58acebc46ad944e1a2cdf54d81559262d2cf8baf5
|
|
|
73
73
|
kimi_cli/ui/shell/setup.py,sha256=8fbf2935fc5b972d2c3946e8dc9f4a7e9d2953810b57c0fb6f22172abf3e6fb5,5369
|
|
74
74
|
kimi_cli/ui/shell/update.py,sha256=56dcb0bd1da82b98c22bfdddca717a2805bd8ac3e93bf23fb3b508549c41fae8,7340
|
|
75
75
|
kimi_cli/ui/shell/visualize.py,sha256=1ca7a1e766f96f108318640d48552111d42b433e4ddb551af869d331c074d950,4112
|
|
76
|
+
kimi_cli/ui/wire/README.md,sha256=0d9beaf38d4f4f1777013e85445e615fcacfe3a169d11151c42a141dec6196bd,4849
|
|
77
|
+
kimi_cli/ui/wire/__init__.py,sha256=9252f53cd32b5a114afdbc402817a627818a48897554977908b5c5e64864801e,11932
|
|
78
|
+
kimi_cli/ui/wire/jsonrpc.py,sha256=c093a540e70026cb48eae494cf4c8ad04f1180338eaaad0a23d64798bb679944,995
|
|
76
79
|
kimi_cli/utils/aiohttp.py,sha256=f8f61e3beaf6439e949c33c3a10db3035bf88136e882b09c858ea92a4c888e00,245
|
|
77
80
|
kimi_cli/utils/changelog.py,sha256=bfcf5a5a360b13648bb7a6abc83e427270caa502646b5acc950d62148510641c,3402
|
|
78
81
|
kimi_cli/utils/logging.py,sha256=129298ac214ecd8d913c3431cc05d754f9c4c8c4042c458618bf9e8ddebdb763,399
|
|
@@ -82,8 +85,8 @@ kimi_cli/utils/pyinstaller.py,sha256=e5d709d0490ef8645bbed2d2363920c59f25bd17c04
|
|
|
82
85
|
kimi_cli/utils/signals.py,sha256=20e0d158a1043189d44815fe3624cd0bfe41e99620a18ac9e12c0eda6db5220f,1387
|
|
83
86
|
kimi_cli/utils/string.py,sha256=0d437d3633199df1051813af8b49a2f808c6525547310cc5c3d427710d2eae06,593
|
|
84
87
|
kimi_cli/wire/__init__.py,sha256=9f1d7eb58f76885edaf76f769371c363ec801b46cada03883eeb3536fa2677f7,1896
|
|
85
|
-
kimi_cli/wire/message.py,sha256=
|
|
86
|
-
kimi_cli-0.
|
|
87
|
-
kimi_cli-0.
|
|
88
|
-
kimi_cli-0.
|
|
89
|
-
kimi_cli-0.
|
|
88
|
+
kimi_cli/wire/message.py,sha256=005213250d05c3c724a5eac6f3c1c3843117439c5e5a24619e9570f59986f67e,5201
|
|
89
|
+
kimi_cli-0.46.dist-info/WHEEL,sha256=70ab3c2925fe316809860cb034f99ba13c4b49819b339959274aab755cc084a8,78
|
|
90
|
+
kimi_cli-0.46.dist-info/entry_points.txt,sha256=97e051756296e9db3167f6dce61d6c88e58d170314a2d63d18c84c73a5c1333b,44
|
|
91
|
+
kimi_cli-0.46.dist-info/METADATA,sha256=0c527326fc9fdbbb9bfd7cabeba9cd0004add9a00082ae8594e350ab90904549,5309
|
|
92
|
+
kimi_cli-0.46.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|