kimi-cli 0.35__py3-none-any.whl → 0.52__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.
- kimi_cli/CHANGELOG.md +165 -0
- kimi_cli/__init__.py +0 -374
- kimi_cli/agents/{koder → default}/agent.yaml +1 -1
- kimi_cli/agents/{koder → default}/system.md +1 -1
- kimi_cli/agentspec.py +115 -0
- kimi_cli/app.py +208 -0
- kimi_cli/cli.py +321 -0
- kimi_cli/config.py +33 -16
- kimi_cli/constant.py +4 -0
- kimi_cli/exception.py +16 -0
- kimi_cli/llm.py +144 -3
- kimi_cli/metadata.py +6 -69
- kimi_cli/prompts/__init__.py +4 -0
- kimi_cli/session.py +103 -0
- kimi_cli/soul/__init__.py +130 -9
- kimi_cli/soul/agent.py +159 -0
- kimi_cli/soul/approval.py +5 -6
- kimi_cli/soul/compaction.py +106 -0
- kimi_cli/soul/context.py +1 -1
- kimi_cli/soul/kimisoul.py +180 -80
- kimi_cli/soul/message.py +6 -6
- kimi_cli/soul/runtime.py +96 -0
- kimi_cli/soul/toolset.py +3 -2
- kimi_cli/tools/__init__.py +35 -31
- kimi_cli/tools/bash/__init__.py +25 -9
- kimi_cli/tools/bash/cmd.md +31 -0
- kimi_cli/tools/dmail/__init__.py +5 -4
- kimi_cli/tools/file/__init__.py +8 -0
- kimi_cli/tools/file/glob.md +1 -1
- kimi_cli/tools/file/glob.py +4 -4
- kimi_cli/tools/file/grep.py +36 -19
- kimi_cli/tools/file/patch.py +52 -10
- kimi_cli/tools/file/read.py +6 -5
- kimi_cli/tools/file/replace.py +16 -4
- kimi_cli/tools/file/write.py +16 -4
- kimi_cli/tools/mcp.py +7 -4
- kimi_cli/tools/task/__init__.py +60 -41
- kimi_cli/tools/task/task.md +1 -1
- kimi_cli/tools/todo/__init__.py +4 -2
- kimi_cli/tools/utils.py +1 -1
- kimi_cli/tools/web/fetch.py +2 -1
- kimi_cli/tools/web/search.py +13 -12
- kimi_cli/ui/__init__.py +0 -68
- kimi_cli/ui/acp/__init__.py +67 -38
- kimi_cli/ui/print/__init__.py +46 -69
- kimi_cli/ui/shell/__init__.py +145 -154
- kimi_cli/ui/shell/console.py +27 -1
- kimi_cli/ui/shell/debug.py +187 -0
- kimi_cli/ui/shell/keyboard.py +183 -0
- kimi_cli/ui/shell/metacmd.py +34 -81
- kimi_cli/ui/shell/prompt.py +245 -28
- kimi_cli/ui/shell/replay.py +104 -0
- kimi_cli/ui/shell/setup.py +19 -19
- kimi_cli/ui/shell/update.py +11 -5
- kimi_cli/ui/shell/visualize.py +576 -0
- 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/utils/__init__.py +0 -0
- kimi_cli/utils/aiohttp.py +10 -0
- kimi_cli/utils/changelog.py +6 -2
- kimi_cli/utils/clipboard.py +10 -0
- kimi_cli/utils/message.py +15 -1
- kimi_cli/utils/rich/__init__.py +33 -0
- kimi_cli/utils/rich/markdown.py +959 -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 +41 -0
- kimi_cli/utils/string.py +8 -0
- kimi_cli/utils/term.py +114 -0
- kimi_cli/wire/__init__.py +73 -0
- kimi_cli/wire/message.py +191 -0
- kimi_cli-0.52.dist-info/METADATA +186 -0
- kimi_cli-0.52.dist-info/RECORD +99 -0
- kimi_cli-0.52.dist-info/entry_points.txt +3 -0
- kimi_cli/agent.py +0 -261
- kimi_cli/agents/koder/README.md +0 -3
- kimi_cli/prompts/metacmds/__init__.py +0 -4
- kimi_cli/soul/wire.py +0 -101
- kimi_cli/ui/shell/liveview.py +0 -158
- kimi_cli/utils/provider.py +0 -64
- kimi_cli-0.35.dist-info/METADATA +0 -24
- kimi_cli-0.35.dist-info/RECORD +0 -76
- kimi_cli-0.35.dist-info/entry_points.txt +0 -3
- /kimi_cli/agents/{koder → default}/sub.yaml +0 -0
- /kimi_cli/prompts/{metacmds/compact.md → compact.md} +0 -0
- /kimi_cli/prompts/{metacmds/init.md → init.md} +0 -0
- {kimi_cli-0.35.dist-info → kimi_cli-0.52.dist-info}/WHEEL +0 -0
kimi_cli/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,171 @@ 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.52] - 2025-11-10
|
|
13
|
+
|
|
14
|
+
- CLI: Remove `--ui` option in favor of `--print`, `--acp`, and `--wire` flags (shell is still the default)
|
|
15
|
+
- CLI: More intuitive session continuation behavior
|
|
16
|
+
- Core: Add retry for LLM empty responses
|
|
17
|
+
- Tool: Change `Bash` tool to `CMD` tool on Windows
|
|
18
|
+
- UI: Fix completion after backspacing
|
|
19
|
+
- UI: Fix code block rendering issues on light background colors
|
|
20
|
+
|
|
21
|
+
## [0.51] - 2025-11-8
|
|
22
|
+
|
|
23
|
+
- Lib: Rename `Soul.model` to `Soul.model_name`
|
|
24
|
+
- Lib: Rename `LLMModelCapability` to `ModelCapability` and move to `kimi_cli.llm`
|
|
25
|
+
- Lib: Add `"thinking"` to `ModelCapability`
|
|
26
|
+
- Lib: Remove `LLM.supports_image_in` property
|
|
27
|
+
- Lib: Add required `Soul.model_capabilities` property
|
|
28
|
+
- Lib: Rename `KimiSoul.set_thinking_mode` to `KimiSoul.set_thinking`
|
|
29
|
+
- Lib: Add `KimiSoul.thinking` property
|
|
30
|
+
- UI: Better checks and notices for LLM model capabilities
|
|
31
|
+
- UI: Clear the screen for `/clear` meta command
|
|
32
|
+
- Tool: Support auto-downloading ripgrep on Windows
|
|
33
|
+
- CLI: Add `--thinking` option to start in thinking mode
|
|
34
|
+
- ACP: Support thinking content in ACP mode
|
|
35
|
+
|
|
36
|
+
## [0.50] - 2025-11-07
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Improve UI look and feel
|
|
41
|
+
- Improve Task tool observability
|
|
42
|
+
|
|
43
|
+
## [0.49] - 2025-11-06
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- Minor UX improvements
|
|
48
|
+
|
|
49
|
+
## [0.48] - 2025-11-06
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- Support Kimi K2 thinking mode
|
|
54
|
+
|
|
55
|
+
## [0.47] - 2025-11-05
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- Fix Ctrl-W not working in some environments
|
|
60
|
+
- Do not load SearchWeb tool when the search service is not configured
|
|
61
|
+
|
|
62
|
+
## [0.46] - 2025-11-03
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- Introduce Wire over stdio for local IPC (experimental, subject to change)
|
|
67
|
+
- Support Anthropic provider type
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- Fix binary packed by PyInstaller not working due to wrong entrypoint
|
|
72
|
+
|
|
73
|
+
## [0.45] - 2025-10-31
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- Allow `KIMI_MODEL_CAPABILITIES` environment variable to override model capabilities
|
|
78
|
+
- Add `--no-markdown` option to disable markdown rendering
|
|
79
|
+
- Support `openai_responses` LLM provider type
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
|
|
83
|
+
- Fix crash when continuing a session
|
|
84
|
+
|
|
85
|
+
## [0.44] - 2025-10-30
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- Improve startup time
|
|
90
|
+
|
|
91
|
+
### Fixed
|
|
92
|
+
|
|
93
|
+
- Fix potential invalid bytes in user input
|
|
94
|
+
|
|
95
|
+
## [0.43] - 2025-10-30
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- Basic Windows support (experimental)
|
|
100
|
+
- Display warnings when base URL or API key is overridden in environment variables
|
|
101
|
+
- Support image input if the LLM model supports it
|
|
102
|
+
- Replay recent context history when continuing a session
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- Ensure new line after executing shell commands
|
|
107
|
+
|
|
108
|
+
## [0.42] - 2025-10-28
|
|
109
|
+
|
|
110
|
+
### Added
|
|
111
|
+
|
|
112
|
+
- Support Ctrl-J or Alt-Enter to insert a new line
|
|
113
|
+
|
|
114
|
+
### Changed
|
|
115
|
+
|
|
116
|
+
- Change mode switch shortcut from Ctrl-K to Ctrl-X
|
|
117
|
+
- Improve overall robustness
|
|
118
|
+
|
|
119
|
+
### Fixed
|
|
120
|
+
|
|
121
|
+
- Fix ACP server `no attribute` error
|
|
122
|
+
|
|
123
|
+
## [0.41] - 2025-10-26
|
|
124
|
+
|
|
125
|
+
### Fixed
|
|
126
|
+
|
|
127
|
+
- Fix a bug for Glob tool when no matching files are found
|
|
128
|
+
- Ensure reading files with UTF-8 encoding
|
|
129
|
+
|
|
130
|
+
### Changed
|
|
131
|
+
|
|
132
|
+
- Disable reading command/query from stdin in shell mode
|
|
133
|
+
- Clarify the API platform selection in `/setup` meta command
|
|
134
|
+
|
|
135
|
+
## [0.40] - 2025-10-24
|
|
136
|
+
|
|
137
|
+
### Added
|
|
138
|
+
|
|
139
|
+
- Support `ESC` key to interrupt the agent loop
|
|
140
|
+
|
|
141
|
+
### Fixed
|
|
142
|
+
|
|
143
|
+
- Fix SSL certificate verification error in some rare cases
|
|
144
|
+
- Fix possible decoding error in Bash tool
|
|
145
|
+
|
|
146
|
+
## [0.39] - 2025-10-24
|
|
147
|
+
|
|
148
|
+
### Fixed
|
|
149
|
+
|
|
150
|
+
- Fix context compaction threshold check
|
|
151
|
+
- Fix panic when SOCKS proxy is set in the shell session
|
|
152
|
+
|
|
153
|
+
## [0.38] - 2025-10-24
|
|
154
|
+
|
|
155
|
+
- Minor UX improvements
|
|
156
|
+
|
|
157
|
+
## [0.37] - 2025-10-24
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
|
|
161
|
+
- Fix update checking
|
|
162
|
+
|
|
163
|
+
## [0.36] - 2025-10-24
|
|
164
|
+
|
|
165
|
+
### Added
|
|
166
|
+
|
|
167
|
+
- Add `/debug` meta command to debug the context
|
|
168
|
+
- Add auto context compaction
|
|
169
|
+
- Add approval request mechanism
|
|
170
|
+
- Add `--yolo` option to automatically approve all actions
|
|
171
|
+
- Render markdown content for better readability
|
|
172
|
+
|
|
173
|
+
### Fixed
|
|
174
|
+
|
|
175
|
+
- Fix "unknown error" message when interrupting a meta command
|
|
176
|
+
|
|
12
177
|
## [0.35] - 2025-10-22
|
|
13
178
|
|
|
14
179
|
### Changed
|
kimi_cli/__init__.py
CHANGED
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import contextlib
|
|
3
|
-
import importlib.metadata
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import subprocess
|
|
7
|
-
import sys
|
|
8
|
-
import textwrap
|
|
9
|
-
import warnings
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, Literal
|
|
13
|
-
|
|
14
|
-
import click
|
|
15
|
-
from pydantic import SecretStr
|
|
16
|
-
|
|
17
|
-
from kimi_cli.agent import (
|
|
18
|
-
DEFAULT_AGENT_FILE,
|
|
19
|
-
AgentGlobals,
|
|
20
|
-
BuiltinSystemPromptArgs,
|
|
21
|
-
load_agent_with_mcp,
|
|
22
|
-
load_agents_md,
|
|
23
|
-
)
|
|
24
|
-
from kimi_cli.config import (
|
|
25
|
-
Config,
|
|
26
|
-
ConfigError,
|
|
27
|
-
LLMModel,
|
|
28
|
-
LLMProvider,
|
|
29
|
-
load_config,
|
|
30
|
-
)
|
|
31
|
-
from kimi_cli.metadata import Session, continue_session, new_session
|
|
32
|
-
from kimi_cli.share import get_share_dir
|
|
33
|
-
from kimi_cli.soul.approval import Approval
|
|
34
|
-
from kimi_cli.soul.context import Context
|
|
35
|
-
from kimi_cli.soul.denwarenji import DenwaRenji
|
|
36
|
-
from kimi_cli.soul.kimisoul import KimiSoul
|
|
37
|
-
from kimi_cli.ui.acp import ACPServer
|
|
38
|
-
from kimi_cli.ui.print import InputFormat, OutputFormat, PrintApp
|
|
39
|
-
from kimi_cli.ui.shell import Reload, ShellApp
|
|
40
|
-
from kimi_cli.utils.logging import StreamToLogger, logger
|
|
41
|
-
from kimi_cli.utils.provider import augment_provider_with_env_vars, create_llm
|
|
42
|
-
|
|
43
|
-
__version__ = importlib.metadata.version("kimi-cli")
|
|
44
|
-
USER_AGENT = f"KimiCLI/{__version__}"
|
|
45
|
-
|
|
46
|
-
UIMode = Literal["shell", "print", "acp"]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
|
|
50
|
-
@click.version_option(__version__)
|
|
51
|
-
@click.option(
|
|
52
|
-
"--verbose",
|
|
53
|
-
is_flag=True,
|
|
54
|
-
default=False,
|
|
55
|
-
help="Print verbose information. Default: no.",
|
|
56
|
-
)
|
|
57
|
-
@click.option(
|
|
58
|
-
"--debug",
|
|
59
|
-
is_flag=True,
|
|
60
|
-
default=False,
|
|
61
|
-
help="Log debug information. Default: no.",
|
|
62
|
-
)
|
|
63
|
-
@click.option(
|
|
64
|
-
"--agent-file",
|
|
65
|
-
type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
|
|
66
|
-
default=DEFAULT_AGENT_FILE,
|
|
67
|
-
help="Custom agent specification file. Default: builtin Kimi Koder.",
|
|
68
|
-
)
|
|
69
|
-
@click.option(
|
|
70
|
-
"--model",
|
|
71
|
-
"-m",
|
|
72
|
-
"model_name",
|
|
73
|
-
type=str,
|
|
74
|
-
default=None,
|
|
75
|
-
help="LLM model to use. Default: default model set in config file.",
|
|
76
|
-
)
|
|
77
|
-
@click.option(
|
|
78
|
-
"--work-dir",
|
|
79
|
-
"-w",
|
|
80
|
-
type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
|
81
|
-
default=Path.cwd(),
|
|
82
|
-
help="Working directory for the agent. Default: current directory.",
|
|
83
|
-
)
|
|
84
|
-
@click.option(
|
|
85
|
-
"--continue",
|
|
86
|
-
"-C",
|
|
87
|
-
"continue_",
|
|
88
|
-
is_flag=True,
|
|
89
|
-
default=False,
|
|
90
|
-
help="Continue the previous session for the working directory. Default: no.",
|
|
91
|
-
)
|
|
92
|
-
@click.option(
|
|
93
|
-
"--command",
|
|
94
|
-
"-c",
|
|
95
|
-
"--query",
|
|
96
|
-
"-q",
|
|
97
|
-
"command",
|
|
98
|
-
type=str,
|
|
99
|
-
default=None,
|
|
100
|
-
help="User query to the agent. Default: prompt interactively.",
|
|
101
|
-
)
|
|
102
|
-
@click.option(
|
|
103
|
-
"--ui",
|
|
104
|
-
"ui",
|
|
105
|
-
type=click.Choice(["shell", "print", "acp"]),
|
|
106
|
-
default="shell",
|
|
107
|
-
help="UI mode to use. Default: shell.",
|
|
108
|
-
)
|
|
109
|
-
@click.option(
|
|
110
|
-
"--print",
|
|
111
|
-
"ui",
|
|
112
|
-
flag_value="print",
|
|
113
|
-
help="Run in print mode. Shortcut for `--ui print`.",
|
|
114
|
-
)
|
|
115
|
-
@click.option(
|
|
116
|
-
"--acp",
|
|
117
|
-
"ui",
|
|
118
|
-
flag_value="acp",
|
|
119
|
-
help="Start ACP server. Shortcut for `--ui acp`.",
|
|
120
|
-
)
|
|
121
|
-
@click.option(
|
|
122
|
-
"--input-format",
|
|
123
|
-
type=click.Choice(["text", "stream-json"]),
|
|
124
|
-
default=None,
|
|
125
|
-
help=(
|
|
126
|
-
"Input format to use. Must be used with `--print` "
|
|
127
|
-
"and the input must be piped in via stdin. "
|
|
128
|
-
"Default: text."
|
|
129
|
-
),
|
|
130
|
-
)
|
|
131
|
-
@click.option(
|
|
132
|
-
"--output-format",
|
|
133
|
-
type=click.Choice(["text", "stream-json"]),
|
|
134
|
-
default=None,
|
|
135
|
-
help="Output format to use. Must be used with `--print`. Default: text.",
|
|
136
|
-
)
|
|
137
|
-
@click.option(
|
|
138
|
-
"--mcp-config-file",
|
|
139
|
-
type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
|
|
140
|
-
multiple=True,
|
|
141
|
-
help=(
|
|
142
|
-
"MCP config file to load. Add this option multiple times to specify multiple MCP configs. "
|
|
143
|
-
"Default: none."
|
|
144
|
-
),
|
|
145
|
-
)
|
|
146
|
-
@click.option(
|
|
147
|
-
"--mcp-config",
|
|
148
|
-
type=str,
|
|
149
|
-
multiple=True,
|
|
150
|
-
help=(
|
|
151
|
-
"MCP config JSON to load. Add this option multiple times to specify multiple MCP configs. "
|
|
152
|
-
"Default: none."
|
|
153
|
-
),
|
|
154
|
-
)
|
|
155
|
-
def kimi(
|
|
156
|
-
verbose: bool,
|
|
157
|
-
debug: bool,
|
|
158
|
-
agent_file: Path,
|
|
159
|
-
model_name: str | None,
|
|
160
|
-
work_dir: Path,
|
|
161
|
-
continue_: bool,
|
|
162
|
-
command: str | None,
|
|
163
|
-
ui: UIMode,
|
|
164
|
-
input_format: InputFormat | None,
|
|
165
|
-
output_format: OutputFormat | None,
|
|
166
|
-
mcp_config_file: list[Path],
|
|
167
|
-
mcp_config: list[str],
|
|
168
|
-
):
|
|
169
|
-
"""Kimi, your next CLI agent."""
|
|
170
|
-
echo = click.echo if verbose else lambda *args, **kwargs: None
|
|
171
|
-
|
|
172
|
-
logger.add(
|
|
173
|
-
get_share_dir() / "logs" / "kimi.log",
|
|
174
|
-
level="DEBUG" if debug else "INFO",
|
|
175
|
-
rotation="06:00",
|
|
176
|
-
retention="10 days",
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
work_dir = work_dir.absolute()
|
|
180
|
-
|
|
181
|
-
if continue_:
|
|
182
|
-
session = continue_session(work_dir)
|
|
183
|
-
if session is None:
|
|
184
|
-
raise click.BadOptionUsage(
|
|
185
|
-
"--continue", "No previous session found for the working directory"
|
|
186
|
-
)
|
|
187
|
-
echo(f"✓ Continuing previous session: {session.id}")
|
|
188
|
-
else:
|
|
189
|
-
session = new_session(work_dir)
|
|
190
|
-
echo(f"✓ Created new session: {session.id}")
|
|
191
|
-
echo(f"✓ Session history file: {session.history_file}")
|
|
192
|
-
|
|
193
|
-
if input_format is not None and ui != "print":
|
|
194
|
-
raise click.BadOptionUsage(
|
|
195
|
-
"--input-format",
|
|
196
|
-
"Input format is only supported for print UI",
|
|
197
|
-
)
|
|
198
|
-
if output_format is not None and ui != "print":
|
|
199
|
-
raise click.BadOptionUsage(
|
|
200
|
-
"--output-format",
|
|
201
|
-
"Output format is only supported for print UI",
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
try:
|
|
205
|
-
mcp_configs = [json.loads(conf.read_text()) for conf in mcp_config_file]
|
|
206
|
-
except json.JSONDecodeError as e:
|
|
207
|
-
raise click.BadOptionUsage("--mcp-config-file", f"Invalid JSON: {e}") from e
|
|
208
|
-
|
|
209
|
-
try:
|
|
210
|
-
mcp_configs += [json.loads(conf) for conf in mcp_config]
|
|
211
|
-
except json.JSONDecodeError as e:
|
|
212
|
-
raise click.BadOptionUsage("--mcp-config", f"Invalid JSON: {e}") from e
|
|
213
|
-
|
|
214
|
-
while True:
|
|
215
|
-
try:
|
|
216
|
-
try:
|
|
217
|
-
config = load_config()
|
|
218
|
-
except ConfigError as e:
|
|
219
|
-
raise click.ClickException(f"Failed to load config: {e}") from e
|
|
220
|
-
echo(f"✓ Loaded config: {config}")
|
|
221
|
-
|
|
222
|
-
succeeded = asyncio.run(
|
|
223
|
-
kimi_run(
|
|
224
|
-
config=config,
|
|
225
|
-
model_name=model_name,
|
|
226
|
-
work_dir=work_dir,
|
|
227
|
-
session=session,
|
|
228
|
-
command=command,
|
|
229
|
-
agent_file=agent_file,
|
|
230
|
-
verbose=verbose,
|
|
231
|
-
ui=ui,
|
|
232
|
-
input_format=input_format,
|
|
233
|
-
output_format=output_format,
|
|
234
|
-
mcp_configs=mcp_configs,
|
|
235
|
-
)
|
|
236
|
-
)
|
|
237
|
-
if not succeeded:
|
|
238
|
-
sys.exit(1)
|
|
239
|
-
break
|
|
240
|
-
except Reload:
|
|
241
|
-
continue
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
async def kimi_run(
|
|
245
|
-
*,
|
|
246
|
-
config: Config,
|
|
247
|
-
model_name: str | None,
|
|
248
|
-
work_dir: Path,
|
|
249
|
-
session: Session,
|
|
250
|
-
command: str | None = None,
|
|
251
|
-
agent_file: Path = DEFAULT_AGENT_FILE,
|
|
252
|
-
verbose: bool = True,
|
|
253
|
-
ui: UIMode = "shell",
|
|
254
|
-
input_format: InputFormat | None = None,
|
|
255
|
-
output_format: OutputFormat | None = None,
|
|
256
|
-
mcp_configs: list[dict[str, Any]] | None = None,
|
|
257
|
-
) -> bool:
|
|
258
|
-
"""Run Kimi CLI."""
|
|
259
|
-
echo = click.echo if verbose else lambda *args, **kwargs: None
|
|
260
|
-
|
|
261
|
-
model: LLMModel | None = None
|
|
262
|
-
provider: LLMProvider | None = None
|
|
263
|
-
|
|
264
|
-
# try to use config file
|
|
265
|
-
if not model_name and config.default_model:
|
|
266
|
-
# no --model specified && default model is set in config
|
|
267
|
-
model = config.models[config.default_model]
|
|
268
|
-
provider = config.providers[model.provider]
|
|
269
|
-
if model_name and model_name in config.models:
|
|
270
|
-
# --model specified && model is set in config
|
|
271
|
-
model = config.models[model_name]
|
|
272
|
-
provider = config.providers[model.provider]
|
|
273
|
-
|
|
274
|
-
if not model:
|
|
275
|
-
model = LLMModel(provider="", model="", max_context_size=100_000)
|
|
276
|
-
provider = LLMProvider(type="kimi", base_url="", api_key=SecretStr(""))
|
|
277
|
-
|
|
278
|
-
# try overwrite with environment variables
|
|
279
|
-
assert provider is not None
|
|
280
|
-
assert model is not None
|
|
281
|
-
augment_provider_with_env_vars(provider, model)
|
|
282
|
-
|
|
283
|
-
if not provider.base_url or not model.model:
|
|
284
|
-
llm = None
|
|
285
|
-
else:
|
|
286
|
-
echo(f"✓ Using LLM provider: {provider}")
|
|
287
|
-
echo(f"✓ Using LLM model: {model}")
|
|
288
|
-
stream = ui != "print" # use non-streaming mode only for print UI
|
|
289
|
-
llm = create_llm(provider, model, stream=stream)
|
|
290
|
-
|
|
291
|
-
# TODO: support Windows
|
|
292
|
-
ls = subprocess.run(["ls", "-la"], capture_output=True, text=True)
|
|
293
|
-
agents_md = load_agents_md(work_dir) or ""
|
|
294
|
-
if agents_md:
|
|
295
|
-
echo(f"✓ Loaded agents.md: {textwrap.shorten(agents_md, width=100)}")
|
|
296
|
-
|
|
297
|
-
agent_globals = AgentGlobals(
|
|
298
|
-
config=config,
|
|
299
|
-
llm=llm,
|
|
300
|
-
builtin_args=BuiltinSystemPromptArgs(
|
|
301
|
-
KIMI_NOW=datetime.now().astimezone().isoformat(),
|
|
302
|
-
KIMI_WORK_DIR=work_dir,
|
|
303
|
-
KIMI_WORK_DIR_LS=ls.stdout,
|
|
304
|
-
KIMI_AGENTS_MD=agents_md,
|
|
305
|
-
),
|
|
306
|
-
denwa_renji=DenwaRenji(),
|
|
307
|
-
session=session,
|
|
308
|
-
approval=Approval(),
|
|
309
|
-
)
|
|
310
|
-
try:
|
|
311
|
-
agent = await load_agent_with_mcp(agent_file, agent_globals, mcp_configs or [])
|
|
312
|
-
except ValueError as e:
|
|
313
|
-
raise click.BadParameter(f"Failed to load agent: {e}") from e
|
|
314
|
-
echo(f"✓ Loaded agent: {agent.name}")
|
|
315
|
-
echo(f"✓ Loaded system prompt: {textwrap.shorten(agent.system_prompt, width=100)}")
|
|
316
|
-
echo(f"✓ Loaded tools: {[tool.name for tool in agent.toolset.tools]}")
|
|
317
|
-
|
|
318
|
-
if command is not None:
|
|
319
|
-
command = command.strip()
|
|
320
|
-
if not command:
|
|
321
|
-
raise click.BadParameter("Command cannot be empty")
|
|
322
|
-
|
|
323
|
-
context = Context(session.history_file)
|
|
324
|
-
restored = await context.restore()
|
|
325
|
-
if restored:
|
|
326
|
-
echo(f"✓ Restored history from {session.history_file}")
|
|
327
|
-
|
|
328
|
-
soul = KimiSoul(
|
|
329
|
-
agent,
|
|
330
|
-
agent_globals,
|
|
331
|
-
context=context,
|
|
332
|
-
loop_control=config.loop_control,
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
original_cwd = Path.cwd()
|
|
336
|
-
os.chdir(work_dir)
|
|
337
|
-
|
|
338
|
-
try:
|
|
339
|
-
if ui == "shell":
|
|
340
|
-
if command is None and not sys.stdin.isatty():
|
|
341
|
-
command = sys.stdin.read().strip()
|
|
342
|
-
echo(f"✓ Read command from stdin: {command}")
|
|
343
|
-
|
|
344
|
-
app = ShellApp(
|
|
345
|
-
soul,
|
|
346
|
-
welcome_info={
|
|
347
|
-
"Directory": str(work_dir),
|
|
348
|
-
"Session": session.id,
|
|
349
|
-
},
|
|
350
|
-
)
|
|
351
|
-
# to ignore possible warnings from dateparser
|
|
352
|
-
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
353
|
-
with contextlib.redirect_stderr(StreamToLogger()):
|
|
354
|
-
return await app.run(command)
|
|
355
|
-
elif ui == "print":
|
|
356
|
-
app = PrintApp(soul, input_format or "text", output_format or "text")
|
|
357
|
-
return await app.run(command)
|
|
358
|
-
elif ui == "acp":
|
|
359
|
-
if command is not None:
|
|
360
|
-
logger.warning("ACP server ignores command argument")
|
|
361
|
-
app = ACPServer(soul)
|
|
362
|
-
return await app.run()
|
|
363
|
-
else:
|
|
364
|
-
raise click.BadParameter(f"Invalid UI mode: {ui}")
|
|
365
|
-
finally:
|
|
366
|
-
os.chdir(original_cwd)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
def main():
|
|
370
|
-
kimi()
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if __name__ == "__main__":
|
|
374
|
-
main()
|
|
@@ -47,7 +47,7 @@ The operating environment is not in a sandbox. Any action especially mutation yo
|
|
|
47
47
|
|
|
48
48
|
The current working directory is `${KIMI_WORK_DIR}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, if so, you should strictly follow the requirements.
|
|
49
49
|
|
|
50
|
-
The
|
|
50
|
+
The directory listing of current working directory is:
|
|
51
51
|
|
|
52
52
|
```
|
|
53
53
|
${KIMI_WORK_DIR_LS}
|
kimi_cli/agentspec.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, NamedTuple
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from kimi_cli.exception import AgentSpecError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_agents_dir() -> Path:
|
|
11
|
+
return Path(__file__).parent / "agents"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DEFAULT_AGENT_FILE = get_agents_dir() / "default" / "agent.yaml"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentSpec(BaseModel):
|
|
18
|
+
"""Agent specification."""
|
|
19
|
+
|
|
20
|
+
extend: str | None = Field(default=None, description="Agent file to extend")
|
|
21
|
+
name: str | None = Field(default=None, description="Agent name") # required
|
|
22
|
+
system_prompt_path: Path | None = Field(
|
|
23
|
+
default=None, description="System prompt path"
|
|
24
|
+
) # required
|
|
25
|
+
system_prompt_args: dict[str, str] = Field(
|
|
26
|
+
default_factory=dict, description="System prompt arguments"
|
|
27
|
+
)
|
|
28
|
+
tools: list[str] | None = Field(default=None, description="Tools") # required
|
|
29
|
+
exclude_tools: list[str] | None = Field(default=None, description="Tools to exclude")
|
|
30
|
+
subagents: dict[str, "SubagentSpec"] | None = Field(default=None, description="Subagents")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SubagentSpec(BaseModel):
|
|
34
|
+
"""Subagent specification."""
|
|
35
|
+
|
|
36
|
+
path: Path = Field(description="Subagent file path")
|
|
37
|
+
description: str = Field(description="Subagent description")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ResolvedAgentSpec(NamedTuple):
|
|
41
|
+
"""Resolved agent specification."""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
system_prompt_path: Path
|
|
45
|
+
system_prompt_args: dict[str, str]
|
|
46
|
+
tools: list[str]
|
|
47
|
+
exclude_tools: list[str]
|
|
48
|
+
subagents: dict[str, "SubagentSpec"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
|
|
52
|
+
"""
|
|
53
|
+
Load agent specification from file.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
FileNotFoundError: If the agent spec file is not found.
|
|
57
|
+
AgentSpecError: If the agent spec is not valid.
|
|
58
|
+
"""
|
|
59
|
+
agent_spec = _load_agent_spec(agent_file)
|
|
60
|
+
assert agent_spec.extend is None, "agent extension should be recursively resolved"
|
|
61
|
+
if agent_spec.name is None:
|
|
62
|
+
raise AgentSpecError("Agent name is required")
|
|
63
|
+
if agent_spec.system_prompt_path is None:
|
|
64
|
+
raise AgentSpecError("System prompt path is required")
|
|
65
|
+
if agent_spec.tools is None:
|
|
66
|
+
raise AgentSpecError("Tools are required")
|
|
67
|
+
return ResolvedAgentSpec(
|
|
68
|
+
name=agent_spec.name,
|
|
69
|
+
system_prompt_path=agent_spec.system_prompt_path,
|
|
70
|
+
system_prompt_args=agent_spec.system_prompt_args,
|
|
71
|
+
tools=agent_spec.tools,
|
|
72
|
+
exclude_tools=agent_spec.exclude_tools or [],
|
|
73
|
+
subagents=agent_spec.subagents or {},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _load_agent_spec(agent_file: Path) -> AgentSpec:
|
|
78
|
+
assert agent_file.is_file(), "expect agent file to exist"
|
|
79
|
+
try:
|
|
80
|
+
with open(agent_file, encoding="utf-8") as f:
|
|
81
|
+
data: dict[str, Any] = yaml.safe_load(f)
|
|
82
|
+
except yaml.YAMLError as e:
|
|
83
|
+
raise AgentSpecError(f"Invalid YAML in agent spec file: {e}") from e
|
|
84
|
+
|
|
85
|
+
version = data.get("version", 1)
|
|
86
|
+
if version != 1:
|
|
87
|
+
raise AgentSpecError(f"Unsupported agent spec version: {version}")
|
|
88
|
+
|
|
89
|
+
agent_spec = AgentSpec(**data.get("agent", {}))
|
|
90
|
+
if agent_spec.system_prompt_path is not None:
|
|
91
|
+
agent_spec.system_prompt_path = agent_file.parent / agent_spec.system_prompt_path
|
|
92
|
+
if agent_spec.subagents is not None:
|
|
93
|
+
for v in agent_spec.subagents.values():
|
|
94
|
+
v.path = agent_file.parent / v.path
|
|
95
|
+
if agent_spec.extend:
|
|
96
|
+
if agent_spec.extend == "default":
|
|
97
|
+
base_agent_file = DEFAULT_AGENT_FILE
|
|
98
|
+
else:
|
|
99
|
+
base_agent_file = agent_file.parent / agent_spec.extend
|
|
100
|
+
base_agent_spec = _load_agent_spec(base_agent_file)
|
|
101
|
+
if agent_spec.name is not None:
|
|
102
|
+
base_agent_spec.name = agent_spec.name
|
|
103
|
+
if agent_spec.system_prompt_path is not None:
|
|
104
|
+
base_agent_spec.system_prompt_path = agent_spec.system_prompt_path
|
|
105
|
+
for k, v in agent_spec.system_prompt_args.items():
|
|
106
|
+
# system prompt args should be merged instead of overwritten
|
|
107
|
+
base_agent_spec.system_prompt_args[k] = v
|
|
108
|
+
if agent_spec.tools is not None:
|
|
109
|
+
base_agent_spec.tools = agent_spec.tools
|
|
110
|
+
if agent_spec.exclude_tools is not None:
|
|
111
|
+
base_agent_spec.exclude_tools = agent_spec.exclude_tools
|
|
112
|
+
if agent_spec.subagents is not None:
|
|
113
|
+
base_agent_spec.subagents = agent_spec.subagents
|
|
114
|
+
agent_spec = base_agent_spec
|
|
115
|
+
return agent_spec
|