kimi-cli 0.41__py3-none-any.whl → 0.43__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.

Files changed (42) hide show
  1. kimi_cli/CHANGELOG.md +28 -0
  2. kimi_cli/__init__.py +169 -102
  3. kimi_cli/agents/{koder → default}/agent.yaml +1 -1
  4. kimi_cli/agentspec.py +19 -8
  5. kimi_cli/cli.py +51 -37
  6. kimi_cli/config.py +33 -14
  7. kimi_cli/exception.py +16 -0
  8. kimi_cli/llm.py +31 -3
  9. kimi_cli/metadata.py +5 -68
  10. kimi_cli/session.py +81 -0
  11. kimi_cli/soul/__init__.py +22 -4
  12. kimi_cli/soul/agent.py +18 -23
  13. kimi_cli/soul/context.py +0 -5
  14. kimi_cli/soul/kimisoul.py +40 -25
  15. kimi_cli/soul/message.py +1 -1
  16. kimi_cli/soul/{globals.py → runtime.py} +13 -11
  17. kimi_cli/tools/file/glob.py +1 -1
  18. kimi_cli/tools/file/patch.py +1 -1
  19. kimi_cli/tools/file/read.py +1 -1
  20. kimi_cli/tools/file/replace.py +1 -1
  21. kimi_cli/tools/file/write.py +1 -1
  22. kimi_cli/tools/task/__init__.py +29 -21
  23. kimi_cli/tools/web/search.py +3 -0
  24. kimi_cli/ui/acp/__init__.py +24 -28
  25. kimi_cli/ui/print/__init__.py +27 -30
  26. kimi_cli/ui/shell/__init__.py +58 -42
  27. kimi_cli/ui/shell/keyboard.py +82 -14
  28. kimi_cli/ui/shell/metacmd.py +3 -8
  29. kimi_cli/ui/shell/prompt.py +208 -6
  30. kimi_cli/ui/shell/replay.py +104 -0
  31. kimi_cli/ui/shell/visualize.py +54 -57
  32. kimi_cli/utils/message.py +14 -0
  33. kimi_cli/utils/signals.py +41 -0
  34. kimi_cli/utils/string.py +8 -0
  35. kimi_cli/wire/__init__.py +13 -0
  36. {kimi_cli-0.41.dist-info → kimi_cli-0.43.dist-info}/METADATA +21 -20
  37. {kimi_cli-0.41.dist-info → kimi_cli-0.43.dist-info}/RECORD +41 -38
  38. kimi_cli/agents/koder/README.md +0 -3
  39. /kimi_cli/agents/{koder → default}/sub.yaml +0 -0
  40. /kimi_cli/agents/{koder → default}/system.md +0 -0
  41. {kimi_cli-0.41.dist-info → kimi_cli-0.43.dist-info}/WHEEL +0 -0
  42. {kimi_cli-0.41.dist-info → kimi_cli-0.43.dist-info}/entry_points.txt +0 -0
kimi_cli/config.py CHANGED
@@ -4,6 +4,7 @@ from typing import Literal, Self
4
4
 
5
5
  from pydantic import BaseModel, Field, SecretStr, ValidationError, field_serializer, model_validator
6
6
 
7
+ from kimi_cli.exception import ConfigError
7
8
  from kimi_cli.share import get_share_dir
8
9
  from kimi_cli.utils.logging import logger
9
10
 
@@ -17,12 +18,17 @@ class LLMProvider(BaseModel):
17
18
  """API base URL"""
18
19
  api_key: SecretStr
19
20
  """API key"""
21
+ custom_headers: dict[str, str] | None = None
22
+ """Custom headers to include in API requests"""
20
23
 
21
24
  @field_serializer("api_key", when_used="json")
22
25
  def dump_secret(self, v: SecretStr):
23
26
  return v.get_secret_value()
24
27
 
25
28
 
29
+ LLMModelCapability = Literal["image_in"]
30
+
31
+
26
32
  class LLMModel(BaseModel):
27
33
  """LLM model configuration."""
28
34
 
@@ -32,6 +38,8 @@ class LLMModel(BaseModel):
32
38
  """Model name"""
33
39
  max_context_size: int
34
40
  """Maximum context size (unit: tokens)"""
41
+ capabilities: set[LLMModelCapability] | None = None
42
+ """Model capabilities"""
35
43
 
36
44
 
37
45
  class LoopControl(BaseModel):
@@ -50,6 +58,8 @@ class MoonshotSearchConfig(BaseModel):
50
58
  """Base URL for Moonshot Search service."""
51
59
  api_key: SecretStr
52
60
  """API key for Moonshot Search service."""
61
+ custom_headers: dict[str, str] | None = None
62
+ """Custom headers to include in API requests."""
53
63
 
54
64
  @field_serializer("api_key", when_used="json")
55
65
  def dump_secret(self, v: SecretStr):
@@ -99,13 +109,21 @@ def get_default_config() -> Config:
99
109
  )
100
110
 
101
111
 
102
- def load_config() -> Config:
103
- """Load configuration from config file.
112
+ def load_config(config_file: Path | None = None) -> Config:
113
+ """
114
+ Load configuration from config file.
115
+ If the config file does not exist, create it with default configuration.
116
+
117
+ Args:
118
+ config_file (Path | None): Path to the configuration file. If None, use default path.
104
119
 
105
120
  Returns:
106
121
  Validated Config object.
122
+
123
+ Raises:
124
+ ConfigError: If the configuration file is invalid.
107
125
  """
108
- config_file = get_config_file()
126
+ config_file = config_file or get_config_file()
109
127
  logger.debug("Loading config from file: {file}", file=config_file)
110
128
 
111
129
  if not config_file.exists():
@@ -119,20 +137,21 @@ def load_config() -> Config:
119
137
  with open(config_file, encoding="utf-8") as f:
120
138
  data = json.load(f)
121
139
  return Config(**data)
122
- except (json.JSONDecodeError, ValidationError) as e:
123
- raise ConfigError(f"Invalid configuration file: {config_file}") from e
140
+ except json.JSONDecodeError as e:
141
+ raise ConfigError(f"Invalid JSON in configuration file: {e}") from e
142
+ except ValidationError as e:
143
+ raise ConfigError(f"Invalid configuration file: {e}") from e
124
144
 
125
145
 
126
- class ConfigError(Exception):
127
- """Configuration error."""
128
-
129
- def __init__(self, message: str):
130
- super().__init__(message)
131
-
146
+ def save_config(config: Config, config_file: Path | None = None):
147
+ """
148
+ Save configuration to config file.
132
149
 
133
- def save_config(config: Config):
134
- """Save configuration to config file."""
135
- config_file = get_config_file()
150
+ Args:
151
+ config (Config): Config object to save.
152
+ config_file (Path | None): Path to the configuration file. If None, use default path.
153
+ """
154
+ config_file = config_file or get_config_file()
136
155
  logger.debug("Saving config to file: {file}", file=config_file)
137
156
  with open(config_file, "w", encoding="utf-8") as f:
138
157
  f.write(config.model_dump_json(indent=2, exclude_none=True))
kimi_cli/exception.py ADDED
@@ -0,0 +1,16 @@
1
+ class KimiCLIException(Exception):
2
+ """Base exception class for Kimi CLI."""
3
+
4
+ pass
5
+
6
+
7
+ class ConfigError(KimiCLIException):
8
+ """Configuration error."""
9
+
10
+ pass
11
+
12
+
13
+ class AgentSpecError(KimiCLIException):
14
+ """Agent specification error."""
15
+
16
+ pass
kimi_cli/llm.py CHANGED
@@ -7,26 +7,47 @@ from kosong.chat_provider.kimi import Kimi
7
7
  from kosong.chat_provider.openai_legacy import OpenAILegacy
8
8
  from pydantic import SecretStr
9
9
 
10
- from kimi_cli.config import LLMModel, LLMProvider
10
+ from kimi_cli.config import LLMModel, LLMModelCapability, LLMProvider
11
11
  from kimi_cli.constant import USER_AGENT
12
12
 
13
13
 
14
14
  class LLM(NamedTuple):
15
15
  chat_provider: ChatProvider
16
16
  max_context_size: int
17
+ capabilities: set[LLMModelCapability]
18
+ # TODO: these additional fields should be moved to ChatProvider
17
19
 
20
+ @property
21
+ def model_name(self) -> str:
22
+ return self.chat_provider.model_name
23
+
24
+ @property
25
+ def supports_image_in(self) -> bool:
26
+ return "image_in" in self.capabilities
27
+
28
+
29
+ def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel) -> dict[str, str]:
30
+ """Override provider/model settings from environment variables.
31
+
32
+ Returns:
33
+ Mapping of environment variables that were applied.
34
+ """
35
+ applied: dict[str, str] = {}
18
36
 
19
- def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel):
20
37
  match provider.type:
21
38
  case "kimi":
22
39
  if base_url := os.getenv("KIMI_BASE_URL"):
23
40
  provider.base_url = base_url
41
+ applied["KIMI_BASE_URL"] = base_url
24
42
  if api_key := os.getenv("KIMI_API_KEY"):
25
43
  provider.api_key = SecretStr(api_key)
44
+ applied["KIMI_API_KEY"] = "******"
26
45
  if model_name := os.getenv("KIMI_MODEL_NAME"):
27
46
  model.model = model_name
47
+ applied["KIMI_MODEL_NAME"] = model.model
28
48
  if max_context_size := os.getenv("KIMI_MODEL_MAX_CONTEXT_SIZE"):
29
49
  model.max_context_size = int(max_context_size)
50
+ applied["KIMI_MODEL_MAX_CONTEXT_SIZE"] = str(model.max_context_size)
30
51
  case "openai_legacy":
31
52
  if base_url := os.getenv("OPENAI_BASE_URL"):
32
53
  provider.base_url = base_url
@@ -35,6 +56,8 @@ def augment_provider_with_env_vars(provider: LLMProvider, model: LLMModel):
35
56
  case _:
36
57
  pass
37
58
 
59
+ return applied
60
+
38
61
 
39
62
  def create_llm(
40
63
  provider: LLMProvider,
@@ -52,6 +75,7 @@ def create_llm(
52
75
  stream=stream,
53
76
  default_headers={
54
77
  "User-Agent": USER_AGENT,
78
+ **(provider.custom_headers or {}),
55
79
  },
56
80
  )
57
81
  if session_id:
@@ -74,4 +98,8 @@ def create_llm(
74
98
  ),
75
99
  )
76
100
 
77
- return LLM(chat_provider=chat_provider, max_context_size=model.max_context_size)
101
+ return LLM(
102
+ chat_provider=chat_provider,
103
+ max_context_size=model.max_context_size,
104
+ capabilities=model.capabilities or set(),
105
+ )
kimi_cli/metadata.py CHANGED
@@ -1,8 +1,6 @@
1
1
  import json
2
- import uuid
3
2
  from hashlib import md5
4
3
  from pathlib import Path
5
- from typing import NamedTuple
6
4
 
7
5
  from pydantic import BaseModel, Field
8
6
 
@@ -33,10 +31,12 @@ class WorkDirMeta(BaseModel):
33
31
  class Metadata(BaseModel):
34
32
  """Kimi metadata structure."""
35
33
 
36
- work_dirs: list[WorkDirMeta] = Field(default_factory=list, description="Work directory list")
34
+ work_dirs: list[WorkDirMeta] = Field(
35
+ default_factory=list[WorkDirMeta], description="Work directory list"
36
+ )
37
37
 
38
38
 
39
- def _load_metadata() -> Metadata:
39
+ def load_metadata() -> Metadata:
40
40
  metadata_file = get_metadata_file()
41
41
  logger.debug("Loading metadata from file: {file}", file=metadata_file)
42
42
  if not metadata_file.exists():
@@ -47,71 +47,8 @@ def _load_metadata() -> Metadata:
47
47
  return Metadata(**data)
48
48
 
49
49
 
50
- def _save_metadata(metadata: Metadata):
50
+ def save_metadata(metadata: Metadata):
51
51
  metadata_file = get_metadata_file()
52
52
  logger.debug("Saving metadata to file: {file}", file=metadata_file)
53
53
  with open(metadata_file, "w", encoding="utf-8") as f:
54
54
  json.dump(metadata.model_dump(), f, indent=2, ensure_ascii=False)
55
-
56
-
57
- class Session(NamedTuple):
58
- """A session of a work directory."""
59
-
60
- id: str
61
- work_dir: WorkDirMeta
62
- history_file: Path
63
-
64
-
65
- def new_session(work_dir: Path, _history_file: Path | None = None) -> Session:
66
- """Create a new session for a work directory."""
67
- logger.debug("Creating new session for work directory: {work_dir}", work_dir=work_dir)
68
-
69
- metadata = _load_metadata()
70
- work_dir_meta = next((wd for wd in metadata.work_dirs if wd.path == str(work_dir)), None)
71
- if work_dir_meta is None:
72
- work_dir_meta = WorkDirMeta(path=str(work_dir))
73
- metadata.work_dirs.append(work_dir_meta)
74
-
75
- session_id = str(uuid.uuid4())
76
- if _history_file is None:
77
- history_file = work_dir_meta.sessions_dir / f"{session_id}.jsonl"
78
- work_dir_meta.last_session_id = session_id
79
- else:
80
- logger.warning("Using provided history file: {history_file}", history_file=_history_file)
81
- _history_file.parent.mkdir(parents=True, exist_ok=True)
82
- if _history_file.exists():
83
- assert _history_file.is_file()
84
- history_file = _history_file
85
-
86
- if history_file.exists():
87
- # truncate if exists
88
- logger.warning(
89
- "History file already exists, truncating: {history_file}", history_file=history_file
90
- )
91
- history_file.unlink()
92
- history_file.touch()
93
-
94
- _save_metadata(metadata)
95
- return Session(id=session_id, work_dir=work_dir_meta, history_file=history_file)
96
-
97
-
98
- def continue_session(work_dir: Path) -> Session | None:
99
- """Get the last session for a work directory."""
100
- logger.debug("Continuing session for work directory: {work_dir}", work_dir=work_dir)
101
-
102
- metadata = _load_metadata()
103
- work_dir_meta = next((wd for wd in metadata.work_dirs if wd.path == str(work_dir)), None)
104
- if work_dir_meta is None:
105
- logger.debug("Work directory never been used")
106
- return None
107
- if work_dir_meta.last_session_id is None:
108
- logger.debug("Work directory never had a session")
109
- return None
110
-
111
- logger.debug(
112
- "Found last session for work directory: {session_id}",
113
- session_id=work_dir_meta.last_session_id,
114
- )
115
- session_id = work_dir_meta.last_session_id
116
- history_file = work_dir_meta.sessions_dir / f"{session_id}.jsonl"
117
- return Session(id=session_id, work_dir=work_dir_meta, history_file=history_file)
kimi_cli/session.py ADDED
@@ -0,0 +1,81 @@
1
+ import uuid
2
+ from pathlib import Path
3
+ from typing import NamedTuple
4
+
5
+ from kimi_cli.metadata import WorkDirMeta, load_metadata, save_metadata
6
+ from kimi_cli.utils.logging import logger
7
+
8
+
9
+ class Session(NamedTuple):
10
+ """A session of a work directory."""
11
+
12
+ id: str
13
+ work_dir: Path
14
+ history_file: Path
15
+
16
+ @staticmethod
17
+ def create(work_dir: Path, _history_file: Path | None = None) -> "Session":
18
+ """Create a new session for a work directory."""
19
+ logger.debug("Creating new session for work directory: {work_dir}", work_dir=work_dir)
20
+
21
+ metadata = load_metadata()
22
+ work_dir_meta = next((wd for wd in metadata.work_dirs if wd.path == str(work_dir)), None)
23
+ if work_dir_meta is None:
24
+ work_dir_meta = WorkDirMeta(path=str(work_dir))
25
+ metadata.work_dirs.append(work_dir_meta)
26
+
27
+ session_id = str(uuid.uuid4())
28
+ if _history_file is None:
29
+ history_file = work_dir_meta.sessions_dir / f"{session_id}.jsonl"
30
+ work_dir_meta.last_session_id = session_id
31
+ else:
32
+ logger.warning(
33
+ "Using provided history file: {history_file}", history_file=_history_file
34
+ )
35
+ _history_file.parent.mkdir(parents=True, exist_ok=True)
36
+ if _history_file.exists():
37
+ assert _history_file.is_file()
38
+ history_file = _history_file
39
+
40
+ if history_file.exists():
41
+ # truncate if exists
42
+ logger.warning(
43
+ "History file already exists, truncating: {history_file}", history_file=history_file
44
+ )
45
+ history_file.unlink()
46
+ history_file.touch()
47
+
48
+ save_metadata(metadata)
49
+
50
+ return Session(
51
+ id=session_id,
52
+ work_dir=work_dir,
53
+ history_file=history_file,
54
+ )
55
+
56
+ @staticmethod
57
+ def continue_(work_dir: Path) -> "Session | None":
58
+ """Get the last session for a work directory."""
59
+ logger.debug("Continuing session for work directory: {work_dir}", work_dir=work_dir)
60
+
61
+ metadata = load_metadata()
62
+ work_dir_meta = next((wd for wd in metadata.work_dirs if wd.path == str(work_dir)), None)
63
+ if work_dir_meta is None:
64
+ logger.debug("Work directory never been used")
65
+ return None
66
+ if work_dir_meta.last_session_id is None:
67
+ logger.debug("Work directory never had a session")
68
+ return None
69
+
70
+ logger.debug(
71
+ "Found last session for work directory: {session_id}",
72
+ session_id=work_dir_meta.last_session_id,
73
+ )
74
+ session_id = work_dir_meta.last_session_id
75
+ history_file = work_dir_meta.sessions_dir / f"{session_id}.jsonl"
76
+
77
+ return Session(
78
+ id=session_id,
79
+ work_dir=work_dir,
80
+ history_file=history_file,
81
+ )
kimi_cli/soul/__init__.py CHANGED
@@ -4,6 +4,9 @@ from collections.abc import Callable, Coroutine
4
4
  from contextvars import ContextVar
5
5
  from typing import Any, NamedTuple, Protocol, runtime_checkable
6
6
 
7
+ from kosong.base.message import ContentPart
8
+
9
+ from kimi_cli.llm import LLM
7
10
  from kimi_cli.utils.logging import logger
8
11
  from kimi_cli.wire import Wire, WireUISide
9
12
  from kimi_cli.wire.message import WireMessage
@@ -15,6 +18,19 @@ class LLMNotSet(Exception):
15
18
  pass
16
19
 
17
20
 
21
+ class LLMNotSupported(Exception):
22
+ """Raised when the LLM does not have required capabilities."""
23
+
24
+ def __init__(self, llm: LLM, capabilities: list[str]):
25
+ self.llm = llm
26
+ self.capabilities = capabilities
27
+ capabilities_str = "capability" if len(capabilities) == 1 else "capabilities"
28
+ super().__init__(
29
+ f"The LLM model '{llm.model_name}' does not support required {capabilities_str}: "
30
+ f"{', '.join(capabilities)}."
31
+ )
32
+
33
+
18
34
  class MaxStepsReached(Exception):
19
35
  """Raised when the maximum number of steps is reached."""
20
36
 
@@ -47,15 +63,16 @@ class Soul(Protocol):
47
63
  """The current status of the soul. The returned value is immutable."""
48
64
  ...
49
65
 
50
- async def run(self, user_input: str):
66
+ async def run(self, user_input: str | list[ContentPart]):
51
67
  """
52
68
  Run the agent with the given user input until the max steps or no more tool calls.
53
69
 
54
70
  Args:
55
- user_input (str): The user input to the agent.
71
+ user_input (str | list[ContentPart]): The user input to the agent.
56
72
 
57
73
  Raises:
58
74
  LLMNotSet: When the LLM is not set.
75
+ LLMNotSupported: When the LLM does not have required capabilities.
59
76
  ChatProviderError: When the LLM provider returns an error.
60
77
  MaxStepsReached: When the maximum number of steps is reached.
61
78
  asyncio.CancelledError: When the run is cancelled by user.
@@ -73,7 +90,7 @@ class RunCancelled(Exception):
73
90
 
74
91
  async def run_soul(
75
92
  soul: "Soul",
76
- user_input: str,
93
+ user_input: str | list[ContentPart],
77
94
  ui_loop_fn: UILoopFn,
78
95
  cancel_event: asyncio.Event,
79
96
  ) -> None:
@@ -85,6 +102,7 @@ async def run_soul(
85
102
 
86
103
  Raises:
87
104
  LLMNotSet: When the LLM is not set.
105
+ LLMNotSupported: When the LLM does not have required capabilities.
88
106
  ChatProviderError: When the LLM provider returns an error.
89
107
  MaxStepsReached: When the maximum number of steps is reached.
90
108
  RunCancelled: When the run is cancelled by the cancel event.
@@ -125,7 +143,7 @@ async def run_soul(
125
143
  try:
126
144
  await asyncio.wait_for(ui_task, timeout=0.5)
127
145
  except asyncio.QueueShutDown:
128
- # expected
146
+ logger.debug("UI loop shut down")
129
147
  pass
130
148
  except TimeoutError:
131
149
  logger.warning("UI loop timed out")
kimi_cli/soul/agent.py CHANGED
@@ -9,10 +9,10 @@ from kosong.tooling import CallableTool, CallableTool2, Toolset
9
9
 
10
10
  from kimi_cli.agentspec import ResolvedAgentSpec, load_agent_spec
11
11
  from kimi_cli.config import Config
12
- from kimi_cli.metadata import Session
12
+ from kimi_cli.session import Session
13
13
  from kimi_cli.soul.approval import Approval
14
14
  from kimi_cli.soul.denwarenji import DenwaRenji
15
- from kimi_cli.soul.globals import AgentGlobals, BuiltinSystemPromptArgs
15
+ from kimi_cli.soul.runtime import BuiltinSystemPromptArgs, Runtime
16
16
  from kimi_cli.soul.toolset import CustomToolset
17
17
  from kimi_cli.tools.mcp import MCPTool
18
18
  from kimi_cli.utils.logging import logger
@@ -26,27 +26,18 @@ class Agent(NamedTuple):
26
26
  toolset: Toolset
27
27
 
28
28
 
29
- async def load_agent_with_mcp(
29
+ async def load_agent(
30
30
  agent_file: Path,
31
- globals_: AgentGlobals,
31
+ runtime: Runtime,
32
+ *,
32
33
  mcp_configs: list[dict[str, Any]],
33
- ) -> Agent:
34
- agent = load_agent(agent_file, globals_)
35
- assert isinstance(agent.toolset, CustomToolset)
36
- if mcp_configs:
37
- await _load_mcp_tools(agent.toolset, mcp_configs)
38
- return agent
39
-
40
-
41
- def load_agent(
42
- agent_file: Path,
43
- globals_: AgentGlobals,
44
34
  ) -> Agent:
45
35
  """
46
36
  Load agent from specification file.
47
37
 
48
38
  Raises:
49
- ValueError: If the agent spec is not valid.
39
+ FileNotFoundError: If the agent spec file does not exist.
40
+ AgentSpecError: If the agent spec is not valid.
50
41
  """
51
42
  logger.info("Loading agent: {agent_file}", agent_file=agent_file)
52
43
  agent_spec = load_agent_spec(agent_file)
@@ -54,17 +45,17 @@ def load_agent(
54
45
  system_prompt = _load_system_prompt(
55
46
  agent_spec.system_prompt_path,
56
47
  agent_spec.system_prompt_args,
57
- globals_.builtin_args,
48
+ runtime.builtin_args,
58
49
  )
59
50
 
60
51
  tool_deps = {
61
52
  ResolvedAgentSpec: agent_spec,
62
- AgentGlobals: globals_,
63
- Config: globals_.config,
64
- BuiltinSystemPromptArgs: globals_.builtin_args,
65
- Session: globals_.session,
66
- DenwaRenji: globals_.denwa_renji,
67
- Approval: globals_.approval,
53
+ Runtime: runtime,
54
+ Config: runtime.config,
55
+ BuiltinSystemPromptArgs: runtime.builtin_args,
56
+ Session: runtime.session,
57
+ DenwaRenji: runtime.denwa_renji,
58
+ Approval: runtime.approval,
68
59
  }
69
60
  tools = agent_spec.tools
70
61
  if agent_spec.exclude_tools:
@@ -75,6 +66,10 @@ def load_agent(
75
66
  if bad_tools:
76
67
  raise ValueError(f"Invalid tools: {bad_tools}")
77
68
 
69
+ assert isinstance(toolset, CustomToolset)
70
+ if mcp_configs:
71
+ await _load_mcp_tools(toolset, mcp_configs)
72
+
78
73
  return Agent(
79
74
  name=agent_spec.name,
80
75
  system_prompt=system_prompt,
kimi_cli/soul/context.py CHANGED
@@ -19,11 +19,6 @@ class Context:
19
19
  self._next_checkpoint_id: int = 0
20
20
  """The ID of the next checkpoint, starting from 0, incremented after each checkpoint."""
21
21
 
22
- @property
23
- def file_backend(self) -> Path:
24
- """The JSONL file backend of the context."""
25
- return self._file_backend
26
-
27
22
  async def restore(self) -> bool:
28
23
  logger.debug("Restoring context from file: {file_backend}", file_backend=self._file_backend)
29
24
  if self._history: