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
|
@@ -1,34 +1,33 @@
|
|
|
1
|
-
You are Kimi CLI
|
|
1
|
+
You are Kimi CLI, an interactive general AI agent running on a user's computer.
|
|
2
|
+
|
|
3
|
+
Your primary goal is to answer questions and/or finish tasks safely and efficiently, adhering strictly to the following system instructions and the user's requirements, leveraging the available tools flexibly.
|
|
2
4
|
|
|
3
5
|
${ROLE_ADDITIONAL}
|
|
4
6
|
|
|
5
7
|
# Prompt and Tool Use
|
|
6
8
|
|
|
7
|
-
The user's
|
|
9
|
+
The user's messages may contain questions and/or task descriptions in natural language, code snippets, logs, file paths, or other forms of information. Read them, understand them and do what the user requested. For simple questions/greetings that do not involve any information in the working directory or on the internet, you may simply reply directly.
|
|
8
10
|
|
|
9
|
-
When handling the user's request, you
|
|
11
|
+
When handling the user's request, you may call available tools to accomplish the task. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.
|
|
10
12
|
|
|
11
13
|
You have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance.
|
|
12
14
|
|
|
13
|
-
The results of the tool calls will be returned to you in a
|
|
15
|
+
The results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.
|
|
14
16
|
|
|
15
|
-
The system may, where appropriate, insert hints or information wrapped in `<system>` and `</system>` tags within
|
|
17
|
+
The system may, where appropriate, insert hints or information wrapped in `<system>` and `</system>` tags within user or tool messages. This information is relevant to the current task or tool calls, may or may not be important to you. Take this info into consideration when determining your next action.
|
|
16
18
|
|
|
17
19
|
When responding to the user, you MUST use the SAME language as the user, unless explicitly instructed to do otherwise.
|
|
18
20
|
|
|
19
|
-
# General Coding
|
|
20
|
-
|
|
21
|
-
Always think carefully. Be patient and thorough. Do not give up too early.
|
|
22
|
-
|
|
23
|
-
ALWAYS, keep it stupidly simple. Do not overcomplicate things.
|
|
21
|
+
# General Guidelines for Coding
|
|
24
22
|
|
|
25
23
|
When building something from scratch, you should:
|
|
26
24
|
|
|
27
25
|
- Understand the user's requirements.
|
|
26
|
+
- Clarify with the user if there is anything unclear.
|
|
28
27
|
- Design the architecture and make a plan for the implementation.
|
|
29
28
|
- Write the code in a modular and maintainable way.
|
|
30
29
|
|
|
31
|
-
When working on existing codebase, you should:
|
|
30
|
+
When working on an existing codebase, you should:
|
|
32
31
|
|
|
33
32
|
- Understand the codebase and the user's requirements. Identify the ultimate goal and the most important criteria to achieve the goal.
|
|
34
33
|
- For a bug fix, you typically need to check error logs or failed tests, scan over the codebase to find the root cause, and figure out a fix. If user mentioned any failed tests, you should make sure they pass after the changes.
|
|
@@ -37,15 +36,32 @@ When working on existing codebase, you should:
|
|
|
37
36
|
- Make MINIMAL changes to achieve the goal. This is very important to your performance.
|
|
38
37
|
- Follow the coding style of existing code in the project.
|
|
39
38
|
|
|
39
|
+
DO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if the user has confirmed in earlier conversations.
|
|
40
|
+
|
|
41
|
+
# General Guidelines for Research and Data Processing
|
|
42
|
+
|
|
43
|
+
The user may ask you to research on certain topics, or process certain multimedia files or folders. You must understand the user's requirements thoroughly, ask for clarification before you start if needed.
|
|
44
|
+
|
|
45
|
+
Make plans before you do deep or wide research tasks, to ensure you are always on track. Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy.
|
|
46
|
+
|
|
47
|
+
Operate on the user's computer carefully:
|
|
48
|
+
|
|
49
|
+
- When working on images, videos, PDFs, docs, spreadsheets, presentations, or other multimedia files, you may need to use proper shell commands or Python tools to process them. Detect if there are already such tools in the environment. If you have to install them, you MUST ensure that any third-party packages are installed in a virtual environment.
|
|
50
|
+
- Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation.
|
|
51
|
+
|
|
40
52
|
# Working Environment
|
|
41
53
|
|
|
42
54
|
## Operating System
|
|
43
55
|
|
|
44
|
-
The operating environment is not in a sandbox. Any
|
|
56
|
+
The operating environment is not in a sandbox. Any actions you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.
|
|
57
|
+
|
|
58
|
+
## Date and Time
|
|
59
|
+
|
|
60
|
+
The current date and time in ISO format is `${KIMI_NOW}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Shell tool with proper command.
|
|
45
61
|
|
|
46
62
|
## Working Directory
|
|
47
63
|
|
|
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,
|
|
64
|
+
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 MUST use absolute paths for these parameters.
|
|
49
65
|
|
|
50
66
|
The directory listing of current working directory is:
|
|
51
67
|
|
|
@@ -55,18 +71,60 @@ ${KIMI_WORK_DIR_LS}
|
|
|
55
71
|
|
|
56
72
|
Use this as your basic understanding of the project structure.
|
|
57
73
|
|
|
58
|
-
## Date and Time
|
|
59
|
-
|
|
60
|
-
The current date and time in ISO format is `${KIMI_NOW}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.
|
|
61
|
-
|
|
62
74
|
# Project Information
|
|
63
75
|
|
|
64
|
-
Markdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should use this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project, but typically there is one in the project root.
|
|
76
|
+
Markdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should use this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project, but typically there is one in the project root.
|
|
65
77
|
|
|
66
|
-
|
|
78
|
+
> Why `AGENTS.md`?
|
|
79
|
+
>
|
|
80
|
+
> `README.md` files are for humans: quick starts, project descriptions, and contribution guidelines. `AGENTS.md` complements this by containing the extra, sometimes detailed context coding agents need: build steps, tests, and conventions that might clutter a README or aren’t relevant to human contributors.
|
|
81
|
+
>
|
|
82
|
+
> We intentionally kept it separate to:
|
|
83
|
+
>
|
|
84
|
+
> - Give agents a clear, predictable place for instructions.
|
|
85
|
+
> - Keep `README`s concise and focused on human contributors.
|
|
86
|
+
> - Provide precise, agent-focused guidance that complements existing `README` and docs.
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
The project level `${KIMI_WORK_DIR}/AGENTS.md`:
|
|
69
89
|
|
|
90
|
+
`````````
|
|
70
91
|
${KIMI_AGENTS_MD}
|
|
92
|
+
`````````
|
|
93
|
+
|
|
94
|
+
If the above `AGENTS.md` is empty or insufficient, you may check `README`/`README.md` files or `AGENTS.md` files in subdirectories for more information about specific parts of the project.
|
|
95
|
+
|
|
96
|
+
If you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date.
|
|
97
|
+
|
|
98
|
+
# Skills
|
|
99
|
+
|
|
100
|
+
Skills are reusable, composable capabilities that enhance your abilities. Each skill is a self-contained directory with a `SKILL.md` file that contains instructions, examples, and/or reference material.
|
|
101
|
+
|
|
102
|
+
## What are skills?
|
|
103
|
+
|
|
104
|
+
Skills are modular extensions that provide:
|
|
105
|
+
|
|
106
|
+
- Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis)
|
|
107
|
+
- Workflow patterns: Best practices for common tasks
|
|
108
|
+
- Tool integrations: Pre-configured tool chains for specific operations
|
|
109
|
+
- Reference material: Documentation, templates, and examples
|
|
110
|
+
|
|
111
|
+
## Available skills
|
|
112
|
+
|
|
113
|
+
${KIMI_SKILLS}
|
|
114
|
+
|
|
115
|
+
## How to use skills
|
|
116
|
+
|
|
117
|
+
Identify the skills that are likely to be useful for the tasks you are currently working on, read the `SKILL.md` file for detailed instructions, guidelines, scripts and more.
|
|
118
|
+
|
|
119
|
+
Only read skill details when needed to conserve the context window.
|
|
120
|
+
|
|
121
|
+
# Ultimate Reminders
|
|
122
|
+
|
|
123
|
+
At any time, you should be HELPFUL and POLITE, CONCISE and ACCURATE, PATIENT and THOROUGH.
|
|
71
124
|
|
|
72
|
-
|
|
125
|
+
- Never diverge from the requirements and the goals of the task you work on. Stay on track.
|
|
126
|
+
- Never give the user more than what they want.
|
|
127
|
+
- Try your best to avoid any hallucination. Do fact checking before providing any factual information.
|
|
128
|
+
- Think twice before you act.
|
|
129
|
+
- Do not give up too early.
|
|
130
|
+
- ALWAYS, keep it stupidly simple. Do not overcomplicate things.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
agent:
|
|
3
|
+
extend: default
|
|
4
|
+
tools:
|
|
5
|
+
- "kimi_cli.tools.multiagent:Task"
|
|
6
|
+
# - "kimi_cli.tools.multiagent:CreateSubagent"
|
|
7
|
+
- "kimi_cli.tools.dmail:SendDMail"
|
|
8
|
+
# - "kimi_cli.tools.think:Think"
|
|
9
|
+
- "kimi_cli.tools.todo:SetTodoList"
|
|
10
|
+
- "kimi_cli.tools.shell:Shell"
|
|
11
|
+
- "kimi_cli.tools.file:ReadFile"
|
|
12
|
+
- "kimi_cli.tools.file:Glob"
|
|
13
|
+
- "kimi_cli.tools.file:Grep"
|
|
14
|
+
- "kimi_cli.tools.file:WriteFile"
|
|
15
|
+
- "kimi_cli.tools.file:StrReplaceFile"
|
|
16
|
+
- "kimi_cli.tools.web:SearchWeb"
|
|
17
|
+
- "kimi_cli.tools.web:FetchURL"
|
kimi_cli/agentspec.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
1
4
|
from pathlib import Path
|
|
2
5
|
from typing import Any, NamedTuple
|
|
3
6
|
|
|
@@ -6,28 +9,43 @@ from pydantic import BaseModel, Field
|
|
|
6
9
|
|
|
7
10
|
from kimi_cli.exception import AgentSpecError
|
|
8
11
|
|
|
12
|
+
DEFAULT_AGENT_SPEC_VERSION = "1"
|
|
13
|
+
SUPPORTED_AGENT_SPEC_VERSIONS = (DEFAULT_AGENT_SPEC_VERSION,)
|
|
14
|
+
|
|
9
15
|
|
|
10
16
|
def get_agents_dir() -> Path:
|
|
11
17
|
return Path(__file__).parent / "agents"
|
|
12
18
|
|
|
13
19
|
|
|
14
20
|
DEFAULT_AGENT_FILE = get_agents_dir() / "default" / "agent.yaml"
|
|
21
|
+
OKABE_AGENT_FILE = get_agents_dir() / "okabe" / "agent.yaml"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Inherit(NamedTuple):
|
|
25
|
+
"""Marker class for inheritance in agent spec."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
inherit = Inherit()
|
|
15
29
|
|
|
16
30
|
|
|
17
31
|
class AgentSpec(BaseModel):
|
|
18
32
|
"""Agent specification."""
|
|
19
33
|
|
|
20
34
|
extend: str | None = Field(default=None, description="Agent file to extend")
|
|
21
|
-
name: str |
|
|
22
|
-
system_prompt_path: Path |
|
|
23
|
-
default=
|
|
35
|
+
name: str | Inherit = Field(default=inherit, description="Agent name") # required
|
|
36
|
+
system_prompt_path: Path | Inherit = Field(
|
|
37
|
+
default=inherit, description="System prompt path"
|
|
24
38
|
) # required
|
|
25
39
|
system_prompt_args: dict[str, str] = Field(
|
|
26
40
|
default_factory=dict, description="System prompt arguments"
|
|
27
41
|
)
|
|
28
|
-
tools: list[str] | None = Field(default=
|
|
29
|
-
exclude_tools: list[str] | None = Field(
|
|
30
|
-
|
|
42
|
+
tools: list[str] | None | Inherit = Field(default=inherit, description="Tools") # required
|
|
43
|
+
exclude_tools: list[str] | None | Inherit = Field(
|
|
44
|
+
default=inherit, description="Tools to exclude"
|
|
45
|
+
)
|
|
46
|
+
subagents: dict[str, SubagentSpec] | None | Inherit = Field(
|
|
47
|
+
default=inherit, description="Subagents"
|
|
48
|
+
)
|
|
31
49
|
|
|
32
50
|
|
|
33
51
|
class SubagentSpec(BaseModel):
|
|
@@ -37,7 +55,8 @@ class SubagentSpec(BaseModel):
|
|
|
37
55
|
description: str = Field(description="Subagent description")
|
|
38
56
|
|
|
39
57
|
|
|
40
|
-
|
|
58
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
59
|
+
class ResolvedAgentSpec:
|
|
41
60
|
"""Resolved agent specification."""
|
|
42
61
|
|
|
43
62
|
name: str
|
|
@@ -45,7 +64,7 @@ class ResolvedAgentSpec(NamedTuple):
|
|
|
45
64
|
system_prompt_args: dict[str, str]
|
|
46
65
|
tools: list[str]
|
|
47
66
|
exclude_tools: list[str]
|
|
48
|
-
subagents: dict[str,
|
|
67
|
+
subagents: dict[str, SubagentSpec]
|
|
49
68
|
|
|
50
69
|
|
|
51
70
|
def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
|
|
@@ -58,58 +77,67 @@ def load_agent_spec(agent_file: Path) -> ResolvedAgentSpec:
|
|
|
58
77
|
"""
|
|
59
78
|
agent_spec = _load_agent_spec(agent_file)
|
|
60
79
|
assert agent_spec.extend is None, "agent extension should be recursively resolved"
|
|
61
|
-
if agent_spec.name
|
|
80
|
+
if isinstance(agent_spec.name, Inherit):
|
|
62
81
|
raise AgentSpecError("Agent name is required")
|
|
63
|
-
if agent_spec.system_prompt_path
|
|
82
|
+
if isinstance(agent_spec.system_prompt_path, Inherit):
|
|
64
83
|
raise AgentSpecError("System prompt path is required")
|
|
65
|
-
if agent_spec.tools
|
|
84
|
+
if isinstance(agent_spec.tools, Inherit):
|
|
66
85
|
raise AgentSpecError("Tools are required")
|
|
86
|
+
if isinstance(agent_spec.exclude_tools, Inherit):
|
|
87
|
+
agent_spec.exclude_tools = []
|
|
88
|
+
if isinstance(agent_spec.subagents, Inherit):
|
|
89
|
+
agent_spec.subagents = {}
|
|
67
90
|
return ResolvedAgentSpec(
|
|
68
91
|
name=agent_spec.name,
|
|
69
92
|
system_prompt_path=agent_spec.system_prompt_path,
|
|
70
93
|
system_prompt_args=agent_spec.system_prompt_args,
|
|
71
|
-
tools=agent_spec.tools,
|
|
94
|
+
tools=agent_spec.tools or [],
|
|
72
95
|
exclude_tools=agent_spec.exclude_tools or [],
|
|
73
96
|
subagents=agent_spec.subagents or {},
|
|
74
97
|
)
|
|
75
98
|
|
|
76
99
|
|
|
77
100
|
def _load_agent_spec(agent_file: Path) -> AgentSpec:
|
|
78
|
-
|
|
101
|
+
if not agent_file.exists():
|
|
102
|
+
raise AgentSpecError(f"Agent spec file not found: {agent_file}")
|
|
103
|
+
if not agent_file.is_file():
|
|
104
|
+
raise AgentSpecError(f"Agent spec path is not a file: {agent_file}")
|
|
79
105
|
try:
|
|
80
106
|
with open(agent_file, encoding="utf-8") as f:
|
|
81
107
|
data: dict[str, Any] = yaml.safe_load(f)
|
|
82
108
|
except yaml.YAMLError as e:
|
|
83
109
|
raise AgentSpecError(f"Invalid YAML in agent spec file: {e}") from e
|
|
84
110
|
|
|
85
|
-
version = data.get("version",
|
|
86
|
-
if version
|
|
111
|
+
version = str(data.get("version", DEFAULT_AGENT_SPEC_VERSION))
|
|
112
|
+
if version not in SUPPORTED_AGENT_SPEC_VERSIONS:
|
|
87
113
|
raise AgentSpecError(f"Unsupported agent spec version: {version}")
|
|
88
114
|
|
|
89
115
|
agent_spec = AgentSpec(**data.get("agent", {}))
|
|
90
|
-
if agent_spec.system_prompt_path
|
|
91
|
-
agent_spec.system_prompt_path =
|
|
92
|
-
|
|
116
|
+
if isinstance(agent_spec.system_prompt_path, Path):
|
|
117
|
+
agent_spec.system_prompt_path = (
|
|
118
|
+
agent_file.parent / agent_spec.system_prompt_path
|
|
119
|
+
).absolute()
|
|
120
|
+
if isinstance(agent_spec.subagents, dict):
|
|
93
121
|
for v in agent_spec.subagents.values():
|
|
94
|
-
v.path = agent_file.parent / v.path
|
|
122
|
+
v.path = (agent_file.parent / v.path).absolute()
|
|
95
123
|
if agent_spec.extend:
|
|
96
124
|
if agent_spec.extend == "default":
|
|
97
125
|
base_agent_file = DEFAULT_AGENT_FILE
|
|
98
126
|
else:
|
|
99
|
-
base_agent_file = agent_file.parent / agent_spec.extend
|
|
127
|
+
base_agent_file = (agent_file.parent / agent_spec.extend).absolute()
|
|
100
128
|
base_agent_spec = _load_agent_spec(base_agent_file)
|
|
101
|
-
if agent_spec.name
|
|
129
|
+
if not isinstance(agent_spec.name, Inherit):
|
|
102
130
|
base_agent_spec.name = agent_spec.name
|
|
103
|
-
if agent_spec.system_prompt_path
|
|
131
|
+
if not isinstance(agent_spec.system_prompt_path, Inherit):
|
|
104
132
|
base_agent_spec.system_prompt_path = agent_spec.system_prompt_path
|
|
105
133
|
for k, v in agent_spec.system_prompt_args.items():
|
|
106
134
|
# system prompt args should be merged instead of overwritten
|
|
107
135
|
base_agent_spec.system_prompt_args[k] = v
|
|
108
|
-
if agent_spec.tools
|
|
136
|
+
if not isinstance(agent_spec.tools, Inherit):
|
|
109
137
|
base_agent_spec.tools = agent_spec.tools
|
|
110
|
-
if agent_spec.exclude_tools
|
|
138
|
+
if not isinstance(agent_spec.exclude_tools, Inherit):
|
|
111
139
|
base_agent_spec.exclude_tools = agent_spec.exclude_tools
|
|
112
|
-
if agent_spec.subagents
|
|
140
|
+
if not isinstance(agent_spec.subagents, Inherit):
|
|
113
141
|
base_agent_spec.subagents = agent_spec.subagents
|
|
114
142
|
agent_spec = base_agent_spec
|
|
115
143
|
return agent_spec
|
kimi_cli/app.py
CHANGED
|
@@ -1,22 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
1
4
|
import contextlib
|
|
2
|
-
import os
|
|
3
5
|
import warnings
|
|
4
|
-
from collections.abc import
|
|
6
|
+
from collections.abc import AsyncGenerator
|
|
5
7
|
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
7
9
|
|
|
10
|
+
import kaos
|
|
11
|
+
from kaos.path import KaosPath
|
|
8
12
|
from pydantic import SecretStr
|
|
9
13
|
|
|
10
14
|
from kimi_cli.agentspec import DEFAULT_AGENT_FILE
|
|
11
15
|
from kimi_cli.cli import InputFormat, OutputFormat
|
|
12
|
-
from kimi_cli.config import LLMModel, LLMProvider, load_config
|
|
16
|
+
from kimi_cli.config import Config, LLMModel, LLMProvider, load_config
|
|
17
|
+
from kimi_cli.flow import PromptFlow
|
|
13
18
|
from kimi_cli.llm import augment_provider_with_env_vars, create_llm
|
|
14
19
|
from kimi_cli.session import Session
|
|
15
|
-
from kimi_cli.
|
|
20
|
+
from kimi_cli.share import get_share_dir
|
|
21
|
+
from kimi_cli.soul import run_soul
|
|
22
|
+
from kimi_cli.soul.agent import Runtime, load_agent
|
|
16
23
|
from kimi_cli.soul.context import Context
|
|
17
24
|
from kimi_cli.soul.kimisoul import KimiSoul
|
|
18
|
-
from kimi_cli.
|
|
25
|
+
from kimi_cli.utils.aioqueue import QueueShutDown
|
|
19
26
|
from kimi_cli.utils.logging import StreamToLogger, logger
|
|
27
|
+
from kimi_cli.utils.path import shorten_home
|
|
28
|
+
from kimi_cli.wire import Wire, WireUISide
|
|
29
|
+
from kimi_cli.wire.types import ContentPart, WireMessage
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from fastmcp.mcp_config import MCPConfig
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def enable_logging(debug: bool = False) -> None:
|
|
36
|
+
logger.remove() # Remove default stderr handler
|
|
37
|
+
logger.enable("kimi_cli")
|
|
38
|
+
if debug:
|
|
39
|
+
logger.enable("kosong")
|
|
40
|
+
logger.add(
|
|
41
|
+
get_share_dir() / "logs" / "kimi.log",
|
|
42
|
+
# FIXME: configure level for different modules
|
|
43
|
+
level="TRACE" if debug else "INFO",
|
|
44
|
+
rotation="06:00",
|
|
45
|
+
retention="10 days",
|
|
46
|
+
)
|
|
20
47
|
|
|
21
48
|
|
|
22
49
|
class KimiCLI:
|
|
@@ -24,30 +51,60 @@ class KimiCLI:
|
|
|
24
51
|
async def create(
|
|
25
52
|
session: Session,
|
|
26
53
|
*,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
mcp_configs: list[dict[str, Any]] | None = None,
|
|
30
|
-
config_file: Path | None = None,
|
|
54
|
+
# Basic configuration
|
|
55
|
+
config: Config | Path | None = None,
|
|
31
56
|
model_name: str | None = None,
|
|
57
|
+
thinking: bool | None = None,
|
|
58
|
+
# Run mode
|
|
59
|
+
yolo: bool = False,
|
|
60
|
+
# Extensions
|
|
32
61
|
agent_file: Path | None = None,
|
|
33
|
-
|
|
62
|
+
mcp_configs: list[MCPConfig] | list[dict[str, Any]] | None = None,
|
|
63
|
+
skills_dir: Path | None = None,
|
|
64
|
+
# Loop control
|
|
65
|
+
max_steps_per_turn: int | None = None,
|
|
66
|
+
max_retries_per_step: int | None = None,
|
|
67
|
+
max_ralph_iterations: int | None = None,
|
|
68
|
+
flow: PromptFlow | None = None,
|
|
69
|
+
) -> KimiCLI:
|
|
34
70
|
"""
|
|
35
71
|
Create a KimiCLI instance.
|
|
36
72
|
|
|
37
73
|
Args:
|
|
38
74
|
session (Session): A session created by `Session.create` or `Session.continue_`.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
config_file (Path | None, optional): Path to the configuration file. Defaults to None.
|
|
75
|
+
config (Config | Path | None, optional): Configuration to use, or path to config file.
|
|
76
|
+
Defaults to None.
|
|
42
77
|
model_name (str | None, optional): Name of the model to use. Defaults to None.
|
|
78
|
+
thinking (bool | None, optional): Whether to enable thinking mode. Defaults to None.
|
|
79
|
+
yolo (bool, optional): Approve all actions without confirmation. Defaults to False.
|
|
43
80
|
agent_file (Path | None, optional): Path to the agent file. Defaults to None.
|
|
81
|
+
mcp_configs (list[MCPConfig | dict[str, Any]] | None, optional): MCP configs to load
|
|
82
|
+
MCP tools from. Defaults to None.
|
|
83
|
+
skills_dir (Path | None, optional): Path to the skills directory. Defaults to None.
|
|
84
|
+
max_steps_per_turn (int | None, optional): Maximum number of steps in one turn.
|
|
85
|
+
Defaults to None.
|
|
86
|
+
max_retries_per_step (int | None, optional): Maximum number of retries in one step.
|
|
87
|
+
Defaults to None.
|
|
88
|
+
max_ralph_iterations (int | None, optional): Extra iterations after the first turn in
|
|
89
|
+
Ralph mode. Defaults to None.
|
|
90
|
+
flow (PromptFlow | None, optional): Prompt flow to execute. Defaults to None.
|
|
44
91
|
|
|
45
92
|
Raises:
|
|
46
93
|
FileNotFoundError: When the agent file is not found.
|
|
47
|
-
ConfigError(KimiCLIException): When the configuration is invalid.
|
|
48
|
-
AgentSpecError(KimiCLIException): When the agent specification is invalid.
|
|
94
|
+
ConfigError(KimiCLIException, ValueError): When the configuration is invalid.
|
|
95
|
+
AgentSpecError(KimiCLIException, ValueError): When the agent specification is invalid.
|
|
96
|
+
InvalidToolError(KimiCLIException, ValueError): When any tool cannot be loaded.
|
|
97
|
+
MCPConfigError(KimiCLIException, ValueError): When any MCP configuration is invalid.
|
|
98
|
+
MCPRuntimeError(KimiCLIException, RuntimeError): When any MCP server cannot be
|
|
99
|
+
connected.
|
|
49
100
|
"""
|
|
50
|
-
config = load_config(
|
|
101
|
+
config = config if isinstance(config, Config) else load_config(config)
|
|
102
|
+
if max_steps_per_turn is not None:
|
|
103
|
+
config.loop_control.max_steps_per_turn = max_steps_per_turn
|
|
104
|
+
if max_retries_per_step is not None:
|
|
105
|
+
config.loop_control.max_retries_per_step = max_retries_per_step
|
|
106
|
+
if max_ralph_iterations is not None:
|
|
107
|
+
config.loop_control.max_ralph_iterations = max_ralph_iterations
|
|
51
108
|
logger.info("Loaded config: {config}", config=config)
|
|
52
109
|
|
|
53
110
|
model: LLMModel | None = None
|
|
@@ -72,27 +129,25 @@ class KimiCLI:
|
|
|
72
129
|
assert model is not None
|
|
73
130
|
env_overrides = augment_provider_with_env_vars(provider, model)
|
|
74
131
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
132
|
+
# determine thinking mode
|
|
133
|
+
thinking = config.default_thinking if thinking is None else thinking
|
|
134
|
+
|
|
135
|
+
llm = create_llm(provider, model, thinking=thinking, session_id=session.id)
|
|
136
|
+
if llm is not None:
|
|
78
137
|
logger.info("Using LLM provider: {provider}", provider=provider)
|
|
79
138
|
logger.info("Using LLM model: {model}", model=model)
|
|
80
|
-
|
|
139
|
+
logger.info("Thinking mode: {thinking}", thinking=thinking)
|
|
81
140
|
|
|
82
|
-
runtime = await Runtime.create(config, llm, session, yolo)
|
|
141
|
+
runtime = await Runtime.create(config, llm, session, yolo, skills_dir)
|
|
83
142
|
|
|
84
143
|
if agent_file is None:
|
|
85
144
|
agent_file = DEFAULT_AGENT_FILE
|
|
86
145
|
agent = await load_agent(agent_file, runtime, mcp_configs=mcp_configs or [])
|
|
87
146
|
|
|
88
|
-
context = Context(session.
|
|
147
|
+
context = Context(session.context_file)
|
|
89
148
|
await context.restore()
|
|
90
149
|
|
|
91
|
-
soul = KimiSoul(
|
|
92
|
-
agent,
|
|
93
|
-
runtime,
|
|
94
|
-
context=context,
|
|
95
|
-
)
|
|
150
|
+
soul = KimiSoul(agent, context=context, flow=flow)
|
|
96
151
|
return KimiCLI(soul, runtime, env_overrides)
|
|
97
152
|
|
|
98
153
|
def __init__(
|
|
@@ -115,23 +170,75 @@ class KimiCLI:
|
|
|
115
170
|
"""Get the Session instance."""
|
|
116
171
|
return self._runtime.session
|
|
117
172
|
|
|
118
|
-
@contextlib.
|
|
119
|
-
def
|
|
120
|
-
original_cwd =
|
|
121
|
-
|
|
173
|
+
@contextlib.asynccontextmanager
|
|
174
|
+
async def _env(self) -> AsyncGenerator[None]:
|
|
175
|
+
original_cwd = KaosPath.cwd()
|
|
176
|
+
await kaos.chdir(self._runtime.session.work_dir)
|
|
122
177
|
try:
|
|
123
178
|
# to ignore possible warnings from dateparser
|
|
124
179
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
125
180
|
with contextlib.redirect_stderr(StreamToLogger()):
|
|
126
181
|
yield
|
|
127
182
|
finally:
|
|
128
|
-
|
|
183
|
+
await kaos.chdir(original_cwd)
|
|
184
|
+
|
|
185
|
+
async def run(
|
|
186
|
+
self,
|
|
187
|
+
user_input: str | list[ContentPart],
|
|
188
|
+
cancel_event: asyncio.Event,
|
|
189
|
+
merge_wire_messages: bool = False,
|
|
190
|
+
) -> AsyncGenerator[WireMessage]:
|
|
191
|
+
"""
|
|
192
|
+
Run the Kimi CLI instance without any UI and yield Wire messages directly.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
user_input (str | list[ContentPart]): The user input to the agent.
|
|
196
|
+
cancel_event (asyncio.Event): An event to cancel the run.
|
|
197
|
+
merge_wire_messages (bool): Whether to merge Wire messages as much as possible.
|
|
129
198
|
|
|
130
|
-
|
|
131
|
-
|
|
199
|
+
Yields:
|
|
200
|
+
WireMessage: The Wire messages from the `KimiSoul`.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
LLMNotSet: When the LLM is not set.
|
|
204
|
+
LLMNotSupported: When the LLM does not have required capabilities.
|
|
205
|
+
ChatProviderError: When the LLM provider returns an error.
|
|
206
|
+
MaxStepsReached: When the maximum number of steps is reached.
|
|
207
|
+
RunCancelled: When the run is cancelled by the cancel event.
|
|
208
|
+
"""
|
|
209
|
+
async with self._env():
|
|
210
|
+
wire_future = asyncio.Future[WireUISide]()
|
|
211
|
+
stop_ui_loop = asyncio.Event()
|
|
212
|
+
|
|
213
|
+
async def _ui_loop_fn(wire: Wire) -> None:
|
|
214
|
+
wire_future.set_result(wire.ui_side(merge=merge_wire_messages))
|
|
215
|
+
await stop_ui_loop.wait()
|
|
216
|
+
|
|
217
|
+
soul_task = asyncio.create_task(
|
|
218
|
+
run_soul(self.soul, user_input, _ui_loop_fn, cancel_event)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
wire_ui = await wire_future
|
|
223
|
+
while True:
|
|
224
|
+
msg = await wire_ui.receive()
|
|
225
|
+
yield msg
|
|
226
|
+
except QueueShutDown:
|
|
227
|
+
pass
|
|
228
|
+
finally:
|
|
229
|
+
# stop consuming Wire messages
|
|
230
|
+
stop_ui_loop.set()
|
|
231
|
+
# wait for the soul task to finish, or raise
|
|
232
|
+
await soul_task
|
|
233
|
+
|
|
234
|
+
async def run_shell(self, command: str | None = None) -> bool:
|
|
235
|
+
"""Run the Kimi CLI instance with shell UI."""
|
|
236
|
+
from kimi_cli.ui.shell import Shell, WelcomeInfoItem
|
|
132
237
|
|
|
133
238
|
welcome_info = [
|
|
134
|
-
WelcomeInfoItem(
|
|
239
|
+
WelcomeInfoItem(
|
|
240
|
+
name="Directory", value=str(shorten_home(self._runtime.session.work_dir))
|
|
241
|
+
),
|
|
135
242
|
WelcomeInfoItem(name="Session", value=self._runtime.session.id),
|
|
136
243
|
]
|
|
137
244
|
if base_url := self._env_overrides.get("KIMI_BASE_URL"):
|
|
@@ -142,6 +249,14 @@ class KimiCLI:
|
|
|
142
249
|
level=WelcomeInfoItem.Level.WARN,
|
|
143
250
|
)
|
|
144
251
|
)
|
|
252
|
+
if self._env_overrides.get("KIMI_API_KEY"):
|
|
253
|
+
welcome_info.append(
|
|
254
|
+
WelcomeInfoItem(
|
|
255
|
+
name="API Key",
|
|
256
|
+
value="****** (from KIMI_API_KEY)",
|
|
257
|
+
level=WelcomeInfoItem.Level.WARN,
|
|
258
|
+
)
|
|
259
|
+
)
|
|
145
260
|
if not self._runtime.llm:
|
|
146
261
|
welcome_info.append(
|
|
147
262
|
WelcomeInfoItem(
|
|
@@ -154,7 +269,7 @@ class KimiCLI:
|
|
|
154
269
|
welcome_info.append(
|
|
155
270
|
WelcomeInfoItem(
|
|
156
271
|
name="Model",
|
|
157
|
-
value=f"{self._soul.
|
|
272
|
+
value=f"{self._soul.model_name} (from KIMI_MODEL_NAME)",
|
|
158
273
|
level=WelcomeInfoItem.Level.WARN,
|
|
159
274
|
)
|
|
160
275
|
)
|
|
@@ -162,34 +277,47 @@ class KimiCLI:
|
|
|
162
277
|
welcome_info.append(
|
|
163
278
|
WelcomeInfoItem(
|
|
164
279
|
name="Model",
|
|
165
|
-
value=self._soul.
|
|
280
|
+
value=self._soul.model_name,
|
|
166
281
|
level=WelcomeInfoItem.Level.INFO,
|
|
167
282
|
)
|
|
168
283
|
)
|
|
169
|
-
with self.
|
|
170
|
-
|
|
171
|
-
return await
|
|
284
|
+
async with self._env():
|
|
285
|
+
shell = Shell(self._soul, welcome_info=welcome_info)
|
|
286
|
+
return await shell.run(command)
|
|
172
287
|
|
|
173
|
-
async def
|
|
288
|
+
async def run_print(
|
|
174
289
|
self,
|
|
175
290
|
input_format: InputFormat,
|
|
176
291
|
output_format: OutputFormat,
|
|
177
292
|
command: str | None = None,
|
|
293
|
+
*,
|
|
294
|
+
final_only: bool = False,
|
|
178
295
|
) -> bool:
|
|
179
|
-
|
|
296
|
+
"""Run the Kimi CLI instance with print UI."""
|
|
297
|
+
from kimi_cli.ui.print import Print
|
|
180
298
|
|
|
181
|
-
with self.
|
|
182
|
-
|
|
299
|
+
async with self._env():
|
|
300
|
+
print_ = Print(
|
|
183
301
|
self._soul,
|
|
184
302
|
input_format,
|
|
185
303
|
output_format,
|
|
186
|
-
self._runtime.session.
|
|
304
|
+
self._runtime.session.context_file,
|
|
305
|
+
final_only=final_only,
|
|
187
306
|
)
|
|
188
|
-
return await
|
|
307
|
+
return await print_.run(command)
|
|
308
|
+
|
|
309
|
+
async def run_acp(self) -> None:
|
|
310
|
+
"""Run the Kimi CLI instance as ACP server."""
|
|
311
|
+
from kimi_cli.ui.acp import ACP
|
|
312
|
+
|
|
313
|
+
async with self._env():
|
|
314
|
+
acp = ACP(self._soul)
|
|
315
|
+
await acp.run()
|
|
189
316
|
|
|
190
|
-
async def
|
|
191
|
-
|
|
317
|
+
async def run_wire_stdio(self) -> None:
|
|
318
|
+
"""Run the Kimi CLI instance as Wire server over stdio."""
|
|
319
|
+
from kimi_cli.ui.wire import WireOverStdio
|
|
192
320
|
|
|
193
|
-
with self.
|
|
194
|
-
|
|
195
|
-
|
|
321
|
+
async with self._env():
|
|
322
|
+
server = WireOverStdio(self._soul)
|
|
323
|
+
await server.serve()
|