klaude-code 1.2.9__py3-none-any.whl → 1.2.11__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.
- klaude_code/cli/main.py +11 -5
- klaude_code/cli/runtime.py +21 -21
- klaude_code/command/__init__.py +68 -23
- klaude_code/command/clear_cmd.py +6 -2
- klaude_code/command/command_abc.py +5 -2
- klaude_code/command/diff_cmd.py +5 -2
- klaude_code/command/export_cmd.py +7 -4
- klaude_code/command/help_cmd.py +6 -2
- klaude_code/command/model_cmd.py +5 -2
- klaude_code/command/prompt_command.py +8 -3
- klaude_code/command/refresh_cmd.py +6 -2
- klaude_code/command/registry.py +17 -5
- klaude_code/command/release_notes_cmd.py +5 -2
- klaude_code/command/status_cmd.py +8 -4
- klaude_code/command/terminal_setup_cmd.py +7 -4
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/agent.py +62 -9
- klaude_code/core/executor.py +1 -4
- klaude_code/core/manager/agent_manager.py +19 -14
- klaude_code/core/manager/llm_clients.py +47 -22
- klaude_code/core/manager/llm_clients_builder.py +22 -13
- klaude_code/core/manager/sub_agent_manager.py +1 -1
- klaude_code/core/prompt.py +4 -4
- klaude_code/core/prompts/prompt-claude-code.md +1 -12
- klaude_code/core/prompts/prompt-minimal.md +12 -0
- klaude_code/core/reminders.py +0 -3
- klaude_code/core/task.py +6 -2
- klaude_code/core/tool/file/_utils.py +30 -0
- klaude_code/core/tool/file/edit_tool.py +5 -30
- klaude_code/core/tool/file/multi_edit_tool.py +6 -31
- klaude_code/core/tool/file/read_tool.py +6 -18
- klaude_code/core/tool/file/write_tool.py +5 -30
- klaude_code/core/tool/memory/__init__.py +5 -0
- klaude_code/core/tool/memory/memory_tool.md +4 -0
- klaude_code/core/tool/memory/skill_loader.py +3 -2
- klaude_code/core/tool/memory/skill_tool.py +13 -0
- klaude_code/core/tool/todo/todo_write_tool.md +0 -157
- klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
- klaude_code/core/tool/tool_registry.py +3 -4
- klaude_code/llm/__init__.py +2 -12
- klaude_code/llm/anthropic/client.py +2 -1
- klaude_code/llm/client.py +2 -2
- klaude_code/llm/codex/client.py +1 -1
- klaude_code/llm/openai_compatible/client.py +3 -2
- klaude_code/llm/openrouter/client.py +3 -3
- klaude_code/llm/registry.py +33 -7
- klaude_code/llm/responses/client.py +2 -1
- klaude_code/llm/responses/input.py +1 -1
- klaude_code/llm/usage.py +17 -8
- klaude_code/protocol/model.py +15 -7
- klaude_code/protocol/op.py +5 -1
- klaude_code/protocol/sub_agent.py +1 -0
- klaude_code/session/export.py +16 -6
- klaude_code/session/session.py +10 -4
- klaude_code/session/templates/export_session.html +155 -0
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/modes/repl/clipboard.py +5 -5
- klaude_code/ui/modes/repl/event_handler.py +1 -5
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +3 -34
- klaude_code/ui/renderers/metadata.py +22 -1
- klaude_code/ui/renderers/tools.py +13 -2
- klaude_code/ui/rich/markdown.py +4 -1
- klaude_code/ui/terminal/__init__.py +55 -0
- klaude_code/ui/terminal/control.py +2 -2
- klaude_code/version.py +3 -3
- {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/METADATA +1 -4
- {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/RECORD +69 -66
- {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.9.dist-info → klaude_code-1.2.11.dist-info}/entry_points.txt +0 -0
klaude_code/core/agent.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import AsyncGenerator, Iterable
|
|
3
|
+
from collections.abc import AsyncGenerator, Callable, Iterable
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Protocol
|
|
5
|
+
from typing import TYPE_CHECKING, Protocol
|
|
6
6
|
|
|
7
7
|
from klaude_code.core.prompt import get_system_prompt as load_system_prompt
|
|
8
8
|
from klaude_code.core.reminders import Reminder, load_agent_reminders
|
|
@@ -14,21 +14,38 @@ from klaude_code.protocol.model import UserInputPayload
|
|
|
14
14
|
from klaude_code.session import Session
|
|
15
15
|
from klaude_code.trace import DebugType, log_debug
|
|
16
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from klaude_code.core.manager.llm_clients import LLMClients
|
|
19
|
+
|
|
17
20
|
|
|
18
21
|
@dataclass(frozen=True)
|
|
19
22
|
class AgentProfile:
|
|
20
23
|
"""Encapsulates the active LLM client plus prompts/tools/reminders."""
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
llm_client_factory: Callable[[], LLMClientABC]
|
|
23
26
|
system_prompt: str | None
|
|
24
27
|
tools: list[llm_param.ToolSchema]
|
|
25
28
|
reminders: list[Reminder]
|
|
26
29
|
|
|
30
|
+
_llm_client: LLMClientABC | None = None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def llm_client(self) -> LLMClientABC:
|
|
34
|
+
if self._llm_client is None:
|
|
35
|
+
object.__setattr__(self, "_llm_client", self.llm_client_factory())
|
|
36
|
+
return self._llm_client # type: ignore[return-value]
|
|
37
|
+
|
|
27
38
|
|
|
28
39
|
class ModelProfileProvider(Protocol):
|
|
29
40
|
"""Strategy interface for constructing agent profiles."""
|
|
30
41
|
|
|
31
42
|
def build_profile(
|
|
43
|
+
self,
|
|
44
|
+
llm_clients: LLMClients,
|
|
45
|
+
sub_agent_type: tools.SubAgentType | None = None,
|
|
46
|
+
) -> AgentProfile: ...
|
|
47
|
+
|
|
48
|
+
def build_profile_eager(
|
|
32
49
|
self,
|
|
33
50
|
llm_client: LLMClientABC,
|
|
34
51
|
sub_agent_type: tools.SubAgentType | None = None,
|
|
@@ -39,13 +56,26 @@ class DefaultModelProfileProvider(ModelProfileProvider):
|
|
|
39
56
|
"""Default provider backed by global prompts/tool/reminder registries."""
|
|
40
57
|
|
|
41
58
|
def build_profile(
|
|
59
|
+
self,
|
|
60
|
+
llm_clients: LLMClients,
|
|
61
|
+
sub_agent_type: tools.SubAgentType | None = None,
|
|
62
|
+
) -> AgentProfile:
|
|
63
|
+
model_name = llm_clients.main_model_name
|
|
64
|
+
return AgentProfile(
|
|
65
|
+
llm_client_factory=lambda: llm_clients.main,
|
|
66
|
+
system_prompt=load_system_prompt(model_name, sub_agent_type),
|
|
67
|
+
tools=load_agent_tools(model_name, sub_agent_type),
|
|
68
|
+
reminders=load_agent_reminders(model_name, sub_agent_type),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def build_profile_eager(
|
|
42
72
|
self,
|
|
43
73
|
llm_client: LLMClientABC,
|
|
44
74
|
sub_agent_type: tools.SubAgentType | None = None,
|
|
45
75
|
) -> AgentProfile:
|
|
46
76
|
model_name = llm_client.model_name
|
|
47
77
|
return AgentProfile(
|
|
48
|
-
|
|
78
|
+
llm_client_factory=lambda: llm_client,
|
|
49
79
|
system_prompt=load_system_prompt(model_name, sub_agent_type),
|
|
50
80
|
tools=load_agent_tools(model_name, sub_agent_type),
|
|
51
81
|
reminders=load_agent_reminders(model_name, sub_agent_type),
|
|
@@ -56,13 +86,26 @@ class VanillaModelProfileProvider(ModelProfileProvider):
|
|
|
56
86
|
"""Provider that strips prompts, reminders, and tools for vanilla mode."""
|
|
57
87
|
|
|
58
88
|
def build_profile(
|
|
89
|
+
self,
|
|
90
|
+
llm_clients: LLMClients,
|
|
91
|
+
sub_agent_type: tools.SubAgentType | None = None,
|
|
92
|
+
) -> AgentProfile:
|
|
93
|
+
model_name = llm_clients.main_model_name
|
|
94
|
+
return AgentProfile(
|
|
95
|
+
llm_client_factory=lambda: llm_clients.main,
|
|
96
|
+
system_prompt=None,
|
|
97
|
+
tools=load_agent_tools(model_name, vanilla=True),
|
|
98
|
+
reminders=load_agent_reminders(model_name, vanilla=True),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def build_profile_eager(
|
|
59
102
|
self,
|
|
60
103
|
llm_client: LLMClientABC,
|
|
61
104
|
sub_agent_type: tools.SubAgentType | None = None,
|
|
62
105
|
) -> AgentProfile:
|
|
63
106
|
model_name = llm_client.model_name
|
|
64
107
|
return AgentProfile(
|
|
65
|
-
|
|
108
|
+
llm_client_factory=lambda: llm_client,
|
|
66
109
|
system_prompt=None,
|
|
67
110
|
tools=load_agent_tools(model_name, vanilla=True),
|
|
68
111
|
reminders=load_agent_reminders(model_name, vanilla=True),
|
|
@@ -74,12 +117,14 @@ class Agent:
|
|
|
74
117
|
self,
|
|
75
118
|
session: Session,
|
|
76
119
|
profile: AgentProfile,
|
|
120
|
+
model_name: str | None = None,
|
|
77
121
|
):
|
|
78
122
|
self.session: Session = session
|
|
79
123
|
self.profile: AgentProfile = profile
|
|
80
124
|
self._current_task: TaskExecutor | None = None
|
|
81
|
-
|
|
82
|
-
|
|
125
|
+
self._prev_context_token: int = 0 # Track context size from previous task for delta calculation
|
|
126
|
+
if not self.session.model_name and model_name:
|
|
127
|
+
self.session.model_name = model_name
|
|
83
128
|
|
|
84
129
|
def cancel(self) -> Iterable[events.Event]:
|
|
85
130
|
"""Handle agent cancellation and persist an interrupt marker and tool cancellations.
|
|
@@ -125,6 +170,12 @@ class Agent:
|
|
|
125
170
|
|
|
126
171
|
try:
|
|
127
172
|
async for event in task.run(user_input):
|
|
173
|
+
# Compute context_delta for TaskMetadataEvent
|
|
174
|
+
if isinstance(event, events.TaskMetadataEvent):
|
|
175
|
+
usage = event.metadata.main.usage
|
|
176
|
+
if usage is not None and usage.context_token is not None:
|
|
177
|
+
usage.context_delta = usage.context_token - self._prev_context_token
|
|
178
|
+
self._prev_context_token = usage.context_token
|
|
128
179
|
yield event
|
|
129
180
|
finally:
|
|
130
181
|
self._current_task = None
|
|
@@ -148,11 +199,13 @@ class Agent:
|
|
|
148
199
|
self.session.append_history([item])
|
|
149
200
|
yield events.DeveloperMessageEvent(session_id=self.session.id, item=item)
|
|
150
201
|
|
|
151
|
-
def set_model_profile(self, profile: AgentProfile) -> None:
|
|
202
|
+
def set_model_profile(self, profile: AgentProfile, model_name: str | None = None) -> None:
|
|
152
203
|
"""Apply a fully constructed profile to the agent."""
|
|
153
204
|
|
|
154
205
|
self.profile = profile
|
|
155
|
-
if
|
|
206
|
+
if model_name:
|
|
207
|
+
self.session.model_name = model_name
|
|
208
|
+
elif not self.session.model_name:
|
|
156
209
|
self.session.model_name = profile.llm_client.model_name
|
|
157
210
|
|
|
158
211
|
def get_llm_client(self) -> LLMClientABC:
|
klaude_code/core/executor.py
CHANGED
|
@@ -114,9 +114,6 @@ class ExecutorContext:
|
|
|
114
114
|
|
|
115
115
|
async def handle_init_agent(self, operation: op.InitAgentOperation) -> None:
|
|
116
116
|
"""Initialize an agent for a session and replay history to UI."""
|
|
117
|
-
if operation.session_id is None:
|
|
118
|
-
raise ValueError("session_id cannot be None")
|
|
119
|
-
|
|
120
117
|
await self.agent_manager.ensure_agent(operation.session_id)
|
|
121
118
|
|
|
122
119
|
async def handle_user_input(self, operation: op.UserInputOperation) -> None:
|
|
@@ -482,4 +479,4 @@ class Executor:
|
|
|
482
479
|
|
|
483
480
|
# Static type check: ExecutorContext must satisfy OperationHandler protocol.
|
|
484
481
|
# If this line causes a type error, ExecutorContext is missing required methods.
|
|
485
|
-
_: type[OperationHandler] = ExecutorContext
|
|
482
|
+
_: type[OperationHandler] = ExecutorContext
|
|
@@ -38,16 +38,21 @@ class AgentManager:
|
|
|
38
38
|
|
|
39
39
|
await self._event_queue.put(event)
|
|
40
40
|
|
|
41
|
-
async def ensure_agent(self, session_id: str) -> Agent:
|
|
42
|
-
"""Return an existing agent for the session or create a new one.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
async def ensure_agent(self, session_id: str | None = None) -> Agent:
|
|
42
|
+
"""Return an existing agent for the session or create a new one.
|
|
43
|
+
|
|
44
|
+
If session_id is None, a new session is created with an auto-generated ID.
|
|
45
|
+
If session_id is provided, attempts to load existing session or creates new one.
|
|
46
|
+
"""
|
|
47
|
+
if session_id is None:
|
|
48
|
+
session = Session.create()
|
|
49
|
+
else:
|
|
50
|
+
agent = self._active_agents.get(session_id)
|
|
51
|
+
if agent is not None:
|
|
52
|
+
return agent
|
|
53
|
+
session = Session.load(session_id)
|
|
54
|
+
profile = self._model_profile_provider.build_profile(self._llm_clients)
|
|
55
|
+
agent = Agent(session=session, profile=profile, model_name=self._llm_clients.main_model_name)
|
|
51
56
|
|
|
52
57
|
async for evt in agent.replay_history():
|
|
53
58
|
await self.emit_event(evt)
|
|
@@ -55,13 +60,13 @@ class AgentManager:
|
|
|
55
60
|
await self.emit_event(
|
|
56
61
|
events.WelcomeEvent(
|
|
57
62
|
work_dir=str(session.work_dir),
|
|
58
|
-
llm_config=self._llm_clients.
|
|
63
|
+
llm_config=self._llm_clients.get_llm_config(),
|
|
59
64
|
)
|
|
60
65
|
)
|
|
61
66
|
|
|
62
|
-
self._active_agents[
|
|
67
|
+
self._active_agents[session.id] = agent
|
|
63
68
|
log_debug(
|
|
64
|
-
f"Initialized agent for session: {
|
|
69
|
+
f"Initialized agent for session: {session.id}",
|
|
65
70
|
style="cyan",
|
|
66
71
|
debug_type=DebugType.EXECUTION,
|
|
67
72
|
)
|
|
@@ -76,7 +81,7 @@ class AgentManager:
|
|
|
76
81
|
|
|
77
82
|
llm_config = config.get_model_config(model_name)
|
|
78
83
|
llm_client = create_llm_client(llm_config)
|
|
79
|
-
agent.set_model_profile(self._model_profile_provider.
|
|
84
|
+
agent.set_model_profile(self._model_profile_provider.build_profile_eager(llm_client), model_name=model_name)
|
|
80
85
|
|
|
81
86
|
developer_item = model.DeveloperMessageItem(
|
|
82
87
|
content=f"switched to model: {model_name}",
|
|
@@ -2,41 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from dataclasses import field as dataclass_field
|
|
5
|
+
from collections.abc import Callable
|
|
7
6
|
|
|
8
7
|
from klaude_code.llm.client import LLMClientABC
|
|
8
|
+
from klaude_code.protocol import llm_param
|
|
9
9
|
from klaude_code.protocol.tools import SubAgentType
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
"""
|
|
12
|
+
class LLMClients:
|
|
13
|
+
"""Container for LLM clients used by main agent and sub-agents."""
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
main_factory: Callable[[], LLMClientABC],
|
|
18
|
+
main_model_name: str,
|
|
19
|
+
main_llm_config: llm_param.LLMConfigParameter,
|
|
20
|
+
) -> None:
|
|
21
|
+
self._main_factory: Callable[[], LLMClientABC] | None = main_factory
|
|
22
|
+
self._main_client: LLMClientABC | None = None
|
|
23
|
+
self._main_model_name: str = main_model_name
|
|
24
|
+
self._main_llm_config: llm_param.LLMConfigParameter = main_llm_config
|
|
25
|
+
self._sub_clients: dict[SubAgentType, LLMClientABC] = {}
|
|
26
|
+
self._sub_factories: dict[SubAgentType, Callable[[], LLMClientABC]] = {}
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
@property
|
|
29
|
+
def main_model_name(self) -> str:
|
|
30
|
+
return self._main_model_name
|
|
20
31
|
|
|
32
|
+
def get_llm_config(self) -> llm_param.LLMConfigParameter:
|
|
33
|
+
return self._main_llm_config
|
|
21
34
|
|
|
22
|
-
@
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
@property
|
|
36
|
+
def main(self) -> LLMClientABC:
|
|
37
|
+
if self._main_client is None:
|
|
38
|
+
if self._main_factory is None:
|
|
39
|
+
raise RuntimeError("Main client factory not set")
|
|
40
|
+
self._main_client = self._main_factory()
|
|
41
|
+
self._main_factory = None
|
|
42
|
+
return self._main_client
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
def register_sub_client_factory(
|
|
45
|
+
self,
|
|
46
|
+
sub_agent_type: SubAgentType,
|
|
47
|
+
factory: Callable[[], LLMClientABC],
|
|
48
|
+
) -> None:
|
|
49
|
+
self._sub_factories[sub_agent_type] = factory
|
|
28
50
|
|
|
29
51
|
def get_client(self, sub_agent_type: SubAgentType | None = None) -> LLMClientABC:
|
|
30
|
-
"""Return client for a sub-agent type or the main client.
|
|
52
|
+
"""Return client for a sub-agent type or the main client."""
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
if sub_agent_type is None:
|
|
55
|
+
return self.main
|
|
34
56
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"""
|
|
57
|
+
existing = self._sub_clients.get(sub_agent_type)
|
|
58
|
+
if existing is not None:
|
|
59
|
+
return existing
|
|
39
60
|
|
|
40
|
-
|
|
61
|
+
factory = self._sub_factories.get(sub_agent_type)
|
|
62
|
+
if factory is None:
|
|
41
63
|
return self.main
|
|
42
|
-
|
|
64
|
+
|
|
65
|
+
client = factory()
|
|
66
|
+
self._sub_clients[sub_agent_type] = client
|
|
67
|
+
return client
|
|
@@ -6,8 +6,7 @@ from klaude_code.config import Config
|
|
|
6
6
|
from klaude_code.core.manager.llm_clients import LLMClients
|
|
7
7
|
from klaude_code.llm.client import LLMClientABC
|
|
8
8
|
from klaude_code.llm.registry import create_llm_client
|
|
9
|
-
from klaude_code.protocol.sub_agent import
|
|
10
|
-
from klaude_code.protocol.tools import SubAgentType
|
|
9
|
+
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
11
10
|
from klaude_code.trace import DebugType, log_debug
|
|
12
11
|
|
|
13
12
|
|
|
@@ -15,7 +14,6 @@ def build_llm_clients(
|
|
|
15
14
|
config: Config,
|
|
16
15
|
*,
|
|
17
16
|
model_override: str | None = None,
|
|
18
|
-
enabled_sub_agents: list[SubAgentType] | None = None,
|
|
19
17
|
) -> LLMClients:
|
|
20
18
|
"""Create an ``LLMClients`` bundle driven by application config."""
|
|
21
19
|
|
|
@@ -32,18 +30,29 @@ def build_llm_clients(
|
|
|
32
30
|
debug_type=DebugType.LLM_CONFIG,
|
|
33
31
|
)
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
sub_clients: dict[SubAgentType, LLMClientABC] = {}
|
|
33
|
+
main_model_name = str(llm_config.model)
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
def _main_factory() -> LLMClientABC:
|
|
36
|
+
return create_llm_client(llm_config)
|
|
37
|
+
|
|
38
|
+
clients = LLMClients(
|
|
39
|
+
main_factory=_main_factory,
|
|
40
|
+
main_model_name=main_model_name,
|
|
41
|
+
main_llm_config=llm_config,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
for profile in iter_sub_agent_profiles():
|
|
45
|
+
model_name = config.subagent_models.get(profile.name)
|
|
41
46
|
if not model_name:
|
|
42
47
|
continue
|
|
43
|
-
|
|
44
|
-
if not profile.enabled_for_model(
|
|
48
|
+
|
|
49
|
+
if not profile.enabled_for_model(main_model_name):
|
|
45
50
|
continue
|
|
46
|
-
sub_llm_config = config.get_model_config(model_name)
|
|
47
|
-
sub_clients[sub_agent_type] = create_llm_client(sub_llm_config)
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
def _factory(model_name_for_factory: str = model_name) -> LLMClientABC:
|
|
53
|
+
sub_llm_config = config.get_model_config(model_name_for_factory)
|
|
54
|
+
return create_llm_client(sub_llm_config)
|
|
55
|
+
|
|
56
|
+
clients.register_sub_client_factory(profile.name, _factory)
|
|
57
|
+
|
|
58
|
+
return clients
|
|
@@ -43,7 +43,7 @@ class SubAgentManager:
|
|
|
43
43
|
child_session = Session(work_dir=parent_session.work_dir)
|
|
44
44
|
child_session.sub_agent_state = state
|
|
45
45
|
|
|
46
|
-
child_profile = self._model_profile_provider.
|
|
46
|
+
child_profile = self._model_profile_provider.build_profile_eager(
|
|
47
47
|
self._llm_clients.get_client(state.sub_agent_type),
|
|
48
48
|
state.sub_agent_type,
|
|
49
49
|
)
|
klaude_code/core/prompt.py
CHANGED
|
@@ -6,8 +6,8 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
COMMAND_DESCRIPTIONS: dict[str, str] = {
|
|
8
8
|
"rg": "ripgrep - fast text search",
|
|
9
|
-
"fd": "
|
|
10
|
-
"tree": "
|
|
9
|
+
"fd": "simple and fast alternative to find",
|
|
10
|
+
"tree": "directory listing as a tree",
|
|
11
11
|
"sg": "ast-grep - AST-aware code search",
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ COMMAND_DESCRIPTIONS: dict[str, str] = {
|
|
|
15
15
|
PROMPT_FILES: dict[str, str] = {
|
|
16
16
|
"main_gpt_5_1": "prompts/prompt-codex-gpt-5-1.md",
|
|
17
17
|
"main_gpt_5_1_codex_max": "prompts/prompt-codex-gpt-5-1-codex-max.md",
|
|
18
|
-
"
|
|
18
|
+
"main": "prompts/prompt-claude-code.md",
|
|
19
19
|
"main_gemini": "prompts/prompt-gemini.md", # https://ai.google.dev/gemini-api/docs/prompting-strategies?hl=zh-cn#agentic-si-template
|
|
20
20
|
# Sub-agent prompts keyed by their name
|
|
21
21
|
"Task": "prompts/prompt-subagent.md",
|
|
@@ -49,7 +49,7 @@ def _get_file_key(model_name: str, sub_agent_type: str | None) -> str:
|
|
|
49
49
|
case name if "gemini" in name:
|
|
50
50
|
return "main_gemini"
|
|
51
51
|
case _:
|
|
52
|
-
return "
|
|
52
|
+
return "main"
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def _build_env_info(model_name: str) -> str:
|
|
@@ -84,15 +84,4 @@ assistant: [Uses the Explore tool to find the files that handle client errors in
|
|
|
84
84
|
<example>
|
|
85
85
|
user: What is the codebase structure?
|
|
86
86
|
assistant: [Uses the Explore tool]
|
|
87
|
-
</example>
|
|
88
|
-
|
|
89
|
-
## Memory
|
|
90
|
-
MEMORY PROTOCOL:
|
|
91
|
-
1. Use the `view` command of your `Memory` tool to check for earlier progress.
|
|
92
|
-
2. ... (work on the task) ...
|
|
93
|
-
- As you make progress, record status / progress / thoughts etc in your memory.
|
|
94
|
-
ASSUME INTERRUPTION: Your context window might be reset at any moment, so you risk losing any progress that is not recorded in your memory directory.
|
|
95
|
-
|
|
96
|
-
Note: when editing your memory folder, always try to keep its content up-to-date, coherent and organized. You can rename or delete files that are no longer relevant. Do not create new files unless necessary.
|
|
97
|
-
|
|
98
|
-
Only write down information relevant to current project in your memory system.
|
|
87
|
+
</example>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are an interactive CLI tool. Use the tools available to you to assist the user.
|
|
2
|
+
|
|
3
|
+
## Guidelines
|
|
4
|
+
- Never use emojis.
|
|
5
|
+
- Your output will be displayed on a command line interface. Your responses should be short and concise.
|
|
6
|
+
- Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
|
|
7
|
+
- NEVER create Markdown files unless they're absolutely necessary for achieving your goal.
|
|
8
|
+
- Use TodoWrite tool to help you manage and plan tasks.
|
|
9
|
+
- For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
|
|
10
|
+
|
|
11
|
+
## Professional objectivity
|
|
12
|
+
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if you honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs. Avoid using over-the-top validation or excessive praise when responding to users such as "You're absolutely right" or similar phrases.
|
klaude_code/core/reminders.py
CHANGED
|
@@ -344,9 +344,6 @@ async def last_path_memory_reminder(
|
|
|
344
344
|
paths.append(path)
|
|
345
345
|
except json.JSONDecodeError:
|
|
346
346
|
continue
|
|
347
|
-
elif tool_call.name == tools.BASH:
|
|
348
|
-
# TODO: haiku check file path
|
|
349
|
-
pass
|
|
350
347
|
paths = list(set(paths))
|
|
351
348
|
memories: list[Memory] = []
|
|
352
349
|
if len(paths) == 0:
|
klaude_code/core/task.py
CHANGED
|
@@ -29,9 +29,11 @@ class MetadataAccumulator:
|
|
|
29
29
|
self._sub_agent_metadata: list[model.TaskMetadata] = []
|
|
30
30
|
self._throughput_weighted_sum: float = 0.0
|
|
31
31
|
self._throughput_tracked_tokens: int = 0
|
|
32
|
+
self._turn_count: int = 0
|
|
32
33
|
|
|
33
34
|
def add(self, turn_metadata: model.ResponseMetadataItem) -> None:
|
|
34
35
|
"""Merge a turn's metadata into the accumulated state."""
|
|
36
|
+
self._turn_count += 1
|
|
35
37
|
main = self._main
|
|
36
38
|
usage = turn_metadata.usage
|
|
37
39
|
|
|
@@ -43,10 +45,11 @@ class MetadataAccumulator:
|
|
|
43
45
|
acc_usage.cached_tokens += usage.cached_tokens
|
|
44
46
|
acc_usage.reasoning_tokens += usage.reasoning_tokens
|
|
45
47
|
acc_usage.output_tokens += usage.output_tokens
|
|
48
|
+
acc_usage.last_turn_output_token = usage.output_tokens
|
|
46
49
|
acc_usage.currency = usage.currency
|
|
47
50
|
|
|
48
|
-
if usage.
|
|
49
|
-
acc_usage.
|
|
51
|
+
if usage.context_token is not None:
|
|
52
|
+
acc_usage.context_token = usage.context_token
|
|
50
53
|
if usage.context_limit is not None:
|
|
51
54
|
acc_usage.context_limit = usage.context_limit
|
|
52
55
|
|
|
@@ -91,6 +94,7 @@ class MetadataAccumulator:
|
|
|
91
94
|
main.usage.throughput_tps = None
|
|
92
95
|
|
|
93
96
|
main.task_duration_s = task_duration_s
|
|
97
|
+
main.turn_count = self._turn_count
|
|
94
98
|
return model.TaskMetadataItem(main=main, sub_agent_task_metadata=self._sub_agent_metadata)
|
|
95
99
|
|
|
96
100
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Shared utility functions for file tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_directory(path: str) -> bool:
|
|
10
|
+
"""Check if path is a directory."""
|
|
11
|
+
return os.path.isdir(path)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def file_exists(path: str) -> bool:
|
|
15
|
+
"""Check if path exists."""
|
|
16
|
+
return os.path.exists(path)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def read_text(path: str) -> str:
|
|
20
|
+
"""Read text from file with UTF-8 encoding."""
|
|
21
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
22
|
+
return f.read()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def write_text(path: str, content: str) -> None:
|
|
26
|
+
"""Write text to file, creating parent directories if needed."""
|
|
27
|
+
parent = Path(path).parent
|
|
28
|
+
parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
30
|
+
f.write(content)
|
|
@@ -7,38 +7,13 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from pydantic import BaseModel, Field
|
|
9
9
|
|
|
10
|
+
from klaude_code.core.tool.file._utils import file_exists, is_directory, read_text, write_text
|
|
10
11
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
11
12
|
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
12
13
|
from klaude_code.core.tool.tool_registry import register
|
|
13
14
|
from klaude_code.protocol import llm_param, model, tools
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def _is_directory(path: str) -> bool:
|
|
17
|
-
try:
|
|
18
|
-
return Path(path).is_dir()
|
|
19
|
-
except Exception:
|
|
20
|
-
return False
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _file_exists(path: str) -> bool:
|
|
24
|
-
try:
|
|
25
|
-
return Path(path).exists()
|
|
26
|
-
except Exception:
|
|
27
|
-
return False
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _read_text(path: str) -> str:
|
|
31
|
-
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
32
|
-
return f.read()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _write_text(path: str, content: str) -> None:
|
|
36
|
-
parent = Path(path).parent
|
|
37
|
-
parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
-
with open(path, "w", encoding="utf-8") as f:
|
|
39
|
-
f.write(content)
|
|
40
|
-
|
|
41
|
-
|
|
42
17
|
@register(tools.EDIT)
|
|
43
18
|
class EditTool(ToolABC):
|
|
44
19
|
class EditArguments(BaseModel):
|
|
@@ -119,7 +94,7 @@ class EditTool(ToolABC):
|
|
|
119
94
|
file_path = os.path.abspath(args.file_path)
|
|
120
95
|
|
|
121
96
|
# Common file errors
|
|
122
|
-
if
|
|
97
|
+
if is_directory(file_path):
|
|
123
98
|
return model.ToolResultItem(
|
|
124
99
|
status="error",
|
|
125
100
|
output="<tool_use_error>Illegal operation on a directory. edit</tool_use_error>",
|
|
@@ -136,7 +111,7 @@ class EditTool(ToolABC):
|
|
|
136
111
|
|
|
137
112
|
# FileTracker checks (only for editing existing files)
|
|
138
113
|
file_tracker = get_current_file_tracker()
|
|
139
|
-
if not
|
|
114
|
+
if not file_exists(file_path):
|
|
140
115
|
# We require reading before editing
|
|
141
116
|
return model.ToolResultItem(
|
|
142
117
|
status="error",
|
|
@@ -163,7 +138,7 @@ class EditTool(ToolABC):
|
|
|
163
138
|
|
|
164
139
|
# Edit existing file: validate and apply
|
|
165
140
|
try:
|
|
166
|
-
before = await asyncio.to_thread(
|
|
141
|
+
before = await asyncio.to_thread(read_text, file_path)
|
|
167
142
|
except FileNotFoundError:
|
|
168
143
|
return model.ToolResultItem(
|
|
169
144
|
status="error",
|
|
@@ -197,7 +172,7 @@ class EditTool(ToolABC):
|
|
|
197
172
|
|
|
198
173
|
# Write back
|
|
199
174
|
try:
|
|
200
|
-
await asyncio.to_thread(
|
|
175
|
+
await asyncio.to_thread(write_text, file_path, after)
|
|
201
176
|
except Exception as e: # pragma: no cover
|
|
202
177
|
return model.ToolResultItem(status="error", output=f"<tool_use_error>{e}</tool_use_error>")
|
|
203
178
|
|