klaude-code 1.2.1__py3-none-any.whl → 1.2.3__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 +9 -4
- klaude_code/cli/runtime.py +42 -43
- klaude_code/command/__init__.py +7 -5
- klaude_code/command/clear_cmd.py +6 -29
- klaude_code/command/command_abc.py +44 -8
- klaude_code/command/diff_cmd.py +33 -27
- klaude_code/command/export_cmd.py +18 -26
- klaude_code/command/help_cmd.py +10 -8
- klaude_code/command/model_cmd.py +11 -40
- klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
- klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
- klaude_code/command/prompt-init.md +2 -5
- klaude_code/command/prompt_command.py +6 -6
- klaude_code/command/refresh_cmd.py +4 -5
- klaude_code/command/registry.py +16 -19
- klaude_code/command/terminal_setup_cmd.py +12 -11
- klaude_code/config/__init__.py +4 -0
- klaude_code/config/config.py +25 -26
- klaude_code/config/list_model.py +8 -3
- klaude_code/config/select_model.py +1 -1
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/__init__.py +0 -3
- klaude_code/core/agent.py +25 -50
- klaude_code/core/executor.py +268 -101
- klaude_code/core/prompt.py +12 -12
- klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
- klaude_code/core/reminders.py +76 -95
- klaude_code/core/task.py +21 -14
- klaude_code/core/tool/__init__.py +45 -11
- klaude_code/core/tool/file/apply_patch.py +5 -1
- klaude_code/core/tool/file/apply_patch_tool.py +11 -13
- klaude_code/core/tool/file/edit_tool.py +27 -23
- klaude_code/core/tool/file/multi_edit_tool.py +15 -17
- klaude_code/core/tool/file/read_tool.py +41 -36
- klaude_code/core/tool/file/write_tool.py +13 -15
- klaude_code/core/tool/memory/memory_tool.py +85 -68
- klaude_code/core/tool/memory/skill_tool.py +10 -12
- klaude_code/core/tool/shell/bash_tool.py +24 -22
- klaude_code/core/tool/shell/command_safety.py +12 -1
- klaude_code/core/tool/sub_agent_tool.py +11 -12
- klaude_code/core/tool/todo/todo_write_tool.py +21 -28
- klaude_code/core/tool/todo/update_plan_tool.py +14 -24
- klaude_code/core/tool/tool_abc.py +3 -4
- klaude_code/core/tool/tool_context.py +7 -7
- klaude_code/core/tool/tool_registry.py +30 -47
- klaude_code/core/tool/tool_runner.py +35 -43
- klaude_code/core/tool/truncation.py +14 -20
- klaude_code/core/tool/web/mermaid_tool.py +12 -14
- klaude_code/core/tool/web/web_fetch_tool.py +15 -17
- klaude_code/core/turn.py +19 -7
- klaude_code/llm/__init__.py +3 -4
- klaude_code/llm/anthropic/client.py +30 -46
- klaude_code/llm/anthropic/input.py +4 -11
- klaude_code/llm/client.py +29 -8
- klaude_code/llm/input_common.py +66 -36
- klaude_code/llm/openai_compatible/client.py +42 -84
- klaude_code/llm/openai_compatible/input.py +11 -16
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
- klaude_code/llm/openrouter/client.py +40 -289
- klaude_code/llm/openrouter/input.py +13 -35
- klaude_code/llm/openrouter/reasoning_handler.py +209 -0
- klaude_code/llm/registry.py +5 -75
- klaude_code/llm/responses/client.py +34 -55
- klaude_code/llm/responses/input.py +24 -26
- klaude_code/llm/usage.py +109 -0
- klaude_code/protocol/__init__.py +4 -0
- klaude_code/protocol/events.py +3 -2
- klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
- klaude_code/protocol/model.py +49 -4
- klaude_code/protocol/op.py +18 -16
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/{core → protocol}/sub_agent.py +7 -0
- klaude_code/session/export.py +150 -70
- klaude_code/session/session.py +28 -14
- klaude_code/session/templates/export_session.html +180 -42
- klaude_code/trace/__init__.py +2 -2
- klaude_code/trace/log.py +11 -5
- klaude_code/ui/__init__.py +91 -8
- klaude_code/ui/core/__init__.py +1 -0
- klaude_code/ui/core/display.py +103 -0
- klaude_code/ui/core/input.py +71 -0
- klaude_code/ui/modes/__init__.py +1 -0
- klaude_code/ui/modes/debug/__init__.py +1 -0
- klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
- klaude_code/ui/modes/exec/__init__.py +1 -0
- klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
- klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
- klaude_code/ui/modes/repl/clipboard.py +152 -0
- klaude_code/ui/modes/repl/completers.py +429 -0
- klaude_code/ui/modes/repl/display.py +60 -0
- klaude_code/ui/modes/repl/event_handler.py +375 -0
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
- klaude_code/ui/modes/repl/key_bindings.py +170 -0
- klaude_code/ui/{repl → modes/repl}/renderer.py +109 -132
- klaude_code/ui/renderers/assistant.py +21 -0
- klaude_code/ui/renderers/common.py +0 -16
- klaude_code/ui/renderers/developer.py +18 -18
- klaude_code/ui/renderers/diffs.py +36 -14
- klaude_code/ui/renderers/errors.py +1 -1
- klaude_code/ui/renderers/metadata.py +50 -27
- klaude_code/ui/renderers/sub_agent.py +43 -9
- klaude_code/ui/renderers/thinking.py +33 -1
- klaude_code/ui/renderers/tools.py +212 -20
- klaude_code/ui/renderers/user_input.py +19 -23
- klaude_code/ui/rich/__init__.py +1 -0
- klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
- klaude_code/ui/{renderers → rich}/status.py +29 -18
- klaude_code/ui/{base → rich}/theme.py +8 -2
- klaude_code/ui/terminal/__init__.py +1 -0
- klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
- klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
- klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
- klaude_code/ui/utils/__init__.py +1 -0
- klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
- klaude_code-1.2.3.dist-info/RECORD +161 -0
- klaude_code/core/clipboard_manifest.py +0 -124
- klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
- klaude_code/ui/base/__init__.py +0 -1
- klaude_code/ui/base/display_abc.py +0 -36
- klaude_code/ui/base/input_abc.py +0 -20
- klaude_code/ui/repl/display.py +0 -36
- klaude_code/ui/repl/event_handler.py +0 -247
- klaude_code/ui/repl/input.py +0 -773
- klaude_code/ui/rich_ext/__init__.py +0 -1
- klaude_code-1.2.1.dist-info/RECORD +0 -151
- /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
- /klaude_code/ui/{base → core}/stage_manager.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
- /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
- /klaude_code/ui/{base → utils}/debouncer.py +0 -0
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/entry_points.txt +0 -0
|
@@ -13,9 +13,7 @@ from pydantic import BaseModel, Field
|
|
|
13
13
|
|
|
14
14
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
15
15
|
from klaude_code.core.tool.tool_registry import register
|
|
16
|
-
from klaude_code.protocol
|
|
17
|
-
from klaude_code.protocol.model import ToolResultItem, ToolResultUIExtra, ToolResultUIExtraType
|
|
18
|
-
from klaude_code.protocol.tools import MEMORY
|
|
16
|
+
from klaude_code.protocol import llm_param, model, tools
|
|
19
17
|
|
|
20
18
|
MEMORY_VIRTUAL_ROOT = "/memories"
|
|
21
19
|
MEMORY_DIR_NAME = ".claude/memories"
|
|
@@ -102,7 +100,7 @@ def _format_numbered_line(line_no: int, content: str) -> str:
|
|
|
102
100
|
return f"{line_no:>6}|{content}"
|
|
103
101
|
|
|
104
102
|
|
|
105
|
-
def _make_diff_ui_extra(before: str, after: str, path: str) -> ToolResultUIExtra:
|
|
103
|
+
def _make_diff_ui_extra(before: str, after: str, path: str) -> model.ToolResultUIExtra:
|
|
106
104
|
diff_lines = list(
|
|
107
105
|
difflib.unified_diff(
|
|
108
106
|
before.splitlines(),
|
|
@@ -113,10 +111,10 @@ def _make_diff_ui_extra(before: str, after: str, path: str) -> ToolResultUIExtra
|
|
|
113
111
|
)
|
|
114
112
|
)
|
|
115
113
|
diff_text = "\n".join(diff_lines)
|
|
116
|
-
return ToolResultUIExtra(type=ToolResultUIExtraType.DIFF_TEXT, diff_text=diff_text)
|
|
114
|
+
return model.ToolResultUIExtra(type=model.ToolResultUIExtraType.DIFF_TEXT, diff_text=diff_text)
|
|
117
115
|
|
|
118
116
|
|
|
119
|
-
@register(MEMORY)
|
|
117
|
+
@register(tools.MEMORY)
|
|
120
118
|
class MemoryTool(ToolABC):
|
|
121
119
|
class MemoryArguments(BaseModel):
|
|
122
120
|
command: Literal["view", "create", "str_replace", "insert", "delete", "rename"]
|
|
@@ -136,9 +134,9 @@ class MemoryTool(ToolABC):
|
|
|
136
134
|
new_path: str | None = Field(default=None)
|
|
137
135
|
|
|
138
136
|
@classmethod
|
|
139
|
-
def schema(cls) -> ToolSchema:
|
|
140
|
-
return ToolSchema(
|
|
141
|
-
name=MEMORY,
|
|
137
|
+
def schema(cls) -> llm_param.ToolSchema:
|
|
138
|
+
return llm_param.ToolSchema(
|
|
139
|
+
name=tools.MEMORY,
|
|
142
140
|
type="function",
|
|
143
141
|
description=load_desc(Path(__file__).parent / "memory_tool.md"),
|
|
144
142
|
parameters={
|
|
@@ -146,7 +144,14 @@ class MemoryTool(ToolABC):
|
|
|
146
144
|
"properties": {
|
|
147
145
|
"command": {
|
|
148
146
|
"type": "string",
|
|
149
|
-
"enum": [
|
|
147
|
+
"enum": [
|
|
148
|
+
"view",
|
|
149
|
+
"create",
|
|
150
|
+
"str_replace",
|
|
151
|
+
"insert",
|
|
152
|
+
"delete",
|
|
153
|
+
"rename",
|
|
154
|
+
],
|
|
150
155
|
"description": "The memory operation to perform",
|
|
151
156
|
},
|
|
152
157
|
"path": {
|
|
@@ -193,11 +198,11 @@ class MemoryTool(ToolABC):
|
|
|
193
198
|
)
|
|
194
199
|
|
|
195
200
|
@classmethod
|
|
196
|
-
async def call(cls, arguments: str) -> ToolResultItem:
|
|
201
|
+
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
197
202
|
try:
|
|
198
203
|
args = cls.MemoryArguments.model_validate_json(arguments)
|
|
199
204
|
except Exception as e:
|
|
200
|
-
return ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
205
|
+
return model.ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
201
206
|
|
|
202
207
|
command = args.command
|
|
203
208
|
if command == "view":
|
|
@@ -213,37 +218,40 @@ class MemoryTool(ToolABC):
|
|
|
213
218
|
elif command == "rename":
|
|
214
219
|
return await cls._rename(args)
|
|
215
220
|
else:
|
|
216
|
-
return ToolResultItem(status="error", output=f"Unknown command: {command}")
|
|
221
|
+
return model.ToolResultItem(status="error", output=f"Unknown command: {command}")
|
|
217
222
|
|
|
218
223
|
@classmethod
|
|
219
|
-
async def _view(cls, args: MemoryArguments) -> ToolResultItem:
|
|
224
|
+
async def _view(cls, args: MemoryArguments) -> model.ToolResultItem:
|
|
220
225
|
if args.path is None:
|
|
221
|
-
return ToolResultItem(status="error", output="path is required for view command")
|
|
226
|
+
return model.ToolResultItem(status="error", output="path is required for view command")
|
|
222
227
|
|
|
223
228
|
actual_path, error = _validate_path(args.path)
|
|
224
229
|
if error:
|
|
225
|
-
return ToolResultItem(status="error", output=error)
|
|
230
|
+
return model.ToolResultItem(status="error", output=error)
|
|
226
231
|
assert actual_path is not None
|
|
227
232
|
|
|
228
233
|
# Ensure memories directory exists
|
|
229
234
|
_ensure_memories_dir()
|
|
230
235
|
|
|
231
236
|
if not actual_path.exists():
|
|
232
|
-
return ToolResultItem(status="error", output=f"Path does not exist: {args.path}")
|
|
237
|
+
return model.ToolResultItem(status="error", output=f"Path does not exist: {args.path}")
|
|
233
238
|
|
|
234
239
|
if actual_path.is_dir():
|
|
235
240
|
# List directory contents
|
|
236
241
|
try:
|
|
237
|
-
entries = sorted(
|
|
242
|
+
entries = sorted(
|
|
243
|
+
actual_path.iterdir(),
|
|
244
|
+
key=lambda p: (not p.is_dir(), p.name.lower()),
|
|
245
|
+
)
|
|
238
246
|
lines = [f"Directory: {args.path}"]
|
|
239
247
|
for entry in entries:
|
|
240
248
|
prefix = "/" if entry.is_dir() else ""
|
|
241
249
|
lines.append(f"- {entry.name}{prefix}")
|
|
242
250
|
if len(entries) == 0:
|
|
243
251
|
lines.append("(empty directory)")
|
|
244
|
-
return ToolResultItem(status="success", output="\n".join(lines))
|
|
252
|
+
return model.ToolResultItem(status="success", output="\n".join(lines))
|
|
245
253
|
except Exception as e:
|
|
246
|
-
return ToolResultItem(status="error", output=f"Failed to list directory: {e}")
|
|
254
|
+
return model.ToolResultItem(status="error", output=f"Failed to list directory: {e}")
|
|
247
255
|
else:
|
|
248
256
|
# Read file contents
|
|
249
257
|
try:
|
|
@@ -259,7 +267,7 @@ class MemoryTool(ToolABC):
|
|
|
259
267
|
end = min(total_lines, args.view_range[1])
|
|
260
268
|
|
|
261
269
|
if start > total_lines:
|
|
262
|
-
return ToolResultItem(
|
|
270
|
+
return model.ToolResultItem(
|
|
263
271
|
status="success",
|
|
264
272
|
output=f"File has {total_lines} lines, requested start line {start} is beyond end of file",
|
|
265
273
|
)
|
|
@@ -269,25 +277,28 @@ class MemoryTool(ToolABC):
|
|
|
269
277
|
output = "\n".join(numbered)
|
|
270
278
|
if not output:
|
|
271
279
|
output = "(empty file)"
|
|
272
|
-
return ToolResultItem(status="success", output=output)
|
|
280
|
+
return model.ToolResultItem(status="success", output=output)
|
|
273
281
|
except Exception as e:
|
|
274
|
-
return ToolResultItem(status="error", output=f"Failed to read file: {e}")
|
|
282
|
+
return model.ToolResultItem(status="error", output=f"Failed to read file: {e}")
|
|
275
283
|
|
|
276
284
|
@classmethod
|
|
277
|
-
async def _create(cls, args: MemoryArguments) -> ToolResultItem:
|
|
285
|
+
async def _create(cls, args: MemoryArguments) -> model.ToolResultItem:
|
|
278
286
|
if args.path is None:
|
|
279
|
-
return ToolResultItem(status="error", output="path is required for create command")
|
|
287
|
+
return model.ToolResultItem(status="error", output="path is required for create command")
|
|
280
288
|
if args.file_text is None:
|
|
281
|
-
return ToolResultItem(status="error", output="file_text is required for create command")
|
|
289
|
+
return model.ToolResultItem(status="error", output="file_text is required for create command")
|
|
282
290
|
|
|
283
291
|
actual_path, error = _validate_path(args.path)
|
|
284
292
|
if error:
|
|
285
|
-
return ToolResultItem(status="error", output=error)
|
|
293
|
+
return model.ToolResultItem(status="error", output=error)
|
|
286
294
|
assert actual_path is not None
|
|
287
295
|
|
|
288
296
|
# Cannot create the root directory itself
|
|
289
297
|
if args.path == MEMORY_VIRTUAL_ROOT or args.path == MEMORY_VIRTUAL_ROOT + "/":
|
|
290
|
-
return ToolResultItem(
|
|
298
|
+
return model.ToolResultItem(
|
|
299
|
+
status="error",
|
|
300
|
+
output="Cannot create the memories root directory as a file",
|
|
301
|
+
)
|
|
291
302
|
|
|
292
303
|
try:
|
|
293
304
|
# Read existing content for diff (if file exists)
|
|
@@ -300,60 +311,64 @@ class MemoryTool(ToolABC):
|
|
|
300
311
|
await asyncio.to_thread(actual_path.write_text, args.file_text, encoding="utf-8")
|
|
301
312
|
|
|
302
313
|
ui_extra = _make_diff_ui_extra(before, args.file_text, args.path)
|
|
303
|
-
return ToolResultItem(status="success", output=f"File created: {args.path}", ui_extra=ui_extra)
|
|
314
|
+
return model.ToolResultItem(status="success", output=f"File created: {args.path}", ui_extra=ui_extra)
|
|
304
315
|
except Exception as e:
|
|
305
|
-
return ToolResultItem(status="error", output=f"Failed to create file: {e}")
|
|
316
|
+
return model.ToolResultItem(status="error", output=f"Failed to create file: {e}")
|
|
306
317
|
|
|
307
318
|
@classmethod
|
|
308
|
-
async def _str_replace(cls, args: MemoryArguments) -> ToolResultItem:
|
|
319
|
+
async def _str_replace(cls, args: MemoryArguments) -> model.ToolResultItem:
|
|
309
320
|
if args.path is None:
|
|
310
|
-
return ToolResultItem(status="error", output="path is required for str_replace command")
|
|
321
|
+
return model.ToolResultItem(status="error", output="path is required for str_replace command")
|
|
311
322
|
if args.old_str is None:
|
|
312
|
-
return ToolResultItem(status="error", output="old_str is required for str_replace command")
|
|
323
|
+
return model.ToolResultItem(status="error", output="old_str is required for str_replace command")
|
|
313
324
|
if args.new_str is None:
|
|
314
|
-
return ToolResultItem(status="error", output="new_str is required for str_replace command")
|
|
325
|
+
return model.ToolResultItem(status="error", output="new_str is required for str_replace command")
|
|
315
326
|
|
|
316
327
|
actual_path, error = _validate_path(args.path)
|
|
317
328
|
if error:
|
|
318
|
-
return ToolResultItem(status="error", output=error)
|
|
329
|
+
return model.ToolResultItem(status="error", output=error)
|
|
319
330
|
assert actual_path is not None
|
|
320
331
|
|
|
321
332
|
if not actual_path.exists():
|
|
322
|
-
return ToolResultItem(status="error", output=f"File does not exist: {args.path}")
|
|
333
|
+
return model.ToolResultItem(status="error", output=f"File does not exist: {args.path}")
|
|
323
334
|
if actual_path.is_dir():
|
|
324
|
-
return ToolResultItem(status="error", output="Cannot perform str_replace on a directory")
|
|
335
|
+
return model.ToolResultItem(status="error", output="Cannot perform str_replace on a directory")
|
|
325
336
|
|
|
326
337
|
try:
|
|
327
338
|
before = await asyncio.to_thread(actual_path.read_text, encoding="utf-8")
|
|
328
339
|
if args.old_str not in before:
|
|
329
|
-
return ToolResultItem(status="error", output=f"String not found in file: {args.old_str}")
|
|
340
|
+
return model.ToolResultItem(status="error", output=f"String not found in file: {args.old_str}")
|
|
330
341
|
|
|
331
342
|
after = before.replace(args.old_str, args.new_str, 1)
|
|
332
343
|
await asyncio.to_thread(actual_path.write_text, after, encoding="utf-8")
|
|
333
344
|
|
|
334
345
|
ui_extra = _make_diff_ui_extra(before, after, args.path)
|
|
335
|
-
return ToolResultItem(
|
|
346
|
+
return model.ToolResultItem(
|
|
347
|
+
status="success",
|
|
348
|
+
output=f"Replaced text in {args.path}",
|
|
349
|
+
ui_extra=ui_extra,
|
|
350
|
+
)
|
|
336
351
|
except Exception as e:
|
|
337
|
-
return ToolResultItem(status="error", output=f"Failed to replace text: {e}")
|
|
352
|
+
return model.ToolResultItem(status="error", output=f"Failed to replace text: {e}")
|
|
338
353
|
|
|
339
354
|
@classmethod
|
|
340
|
-
async def _insert(cls, args: MemoryArguments) -> ToolResultItem:
|
|
355
|
+
async def _insert(cls, args: MemoryArguments) -> model.ToolResultItem:
|
|
341
356
|
if args.path is None:
|
|
342
|
-
return ToolResultItem(status="error", output="path is required for insert command")
|
|
357
|
+
return model.ToolResultItem(status="error", output="path is required for insert command")
|
|
343
358
|
if args.insert_line is None:
|
|
344
|
-
return ToolResultItem(status="error", output="insert_line is required for insert command")
|
|
359
|
+
return model.ToolResultItem(status="error", output="insert_line is required for insert command")
|
|
345
360
|
if args.insert_text is None:
|
|
346
|
-
return ToolResultItem(status="error", output="insert_text is required for insert command")
|
|
361
|
+
return model.ToolResultItem(status="error", output="insert_text is required for insert command")
|
|
347
362
|
|
|
348
363
|
actual_path, error = _validate_path(args.path)
|
|
349
364
|
if error:
|
|
350
|
-
return ToolResultItem(status="error", output=error)
|
|
365
|
+
return model.ToolResultItem(status="error", output=error)
|
|
351
366
|
assert actual_path is not None
|
|
352
367
|
|
|
353
368
|
if not actual_path.exists():
|
|
354
|
-
return ToolResultItem(status="error", output=f"File does not exist: {args.path}")
|
|
369
|
+
return model.ToolResultItem(status="error", output=f"File does not exist: {args.path}")
|
|
355
370
|
if actual_path.is_dir():
|
|
356
|
-
return ToolResultItem(status="error", output="Cannot insert into a directory")
|
|
371
|
+
return model.ToolResultItem(status="error", output="Cannot insert into a directory")
|
|
357
372
|
|
|
358
373
|
try:
|
|
359
374
|
before = await asyncio.to_thread(actual_path.read_text, encoding="utf-8")
|
|
@@ -377,69 +392,71 @@ class MemoryTool(ToolABC):
|
|
|
377
392
|
await asyncio.to_thread(actual_path.write_text, after, encoding="utf-8")
|
|
378
393
|
|
|
379
394
|
ui_extra = _make_diff_ui_extra(before, after, args.path)
|
|
380
|
-
return ToolResultItem(
|
|
381
|
-
status="success",
|
|
395
|
+
return model.ToolResultItem(
|
|
396
|
+
status="success",
|
|
397
|
+
output=f"Inserted text at line {args.insert_line} in {args.path}",
|
|
398
|
+
ui_extra=ui_extra,
|
|
382
399
|
)
|
|
383
400
|
except Exception as e:
|
|
384
|
-
return ToolResultItem(status="error", output=f"Failed to insert text: {e}")
|
|
401
|
+
return model.ToolResultItem(status="error", output=f"Failed to insert text: {e}")
|
|
385
402
|
|
|
386
403
|
@classmethod
|
|
387
|
-
async def _delete(cls, args: MemoryArguments) -> ToolResultItem:
|
|
404
|
+
async def _delete(cls, args: MemoryArguments) -> model.ToolResultItem:
|
|
388
405
|
if args.path is None:
|
|
389
|
-
return ToolResultItem(status="error", output="path is required for delete command")
|
|
406
|
+
return model.ToolResultItem(status="error", output="path is required for delete command")
|
|
390
407
|
|
|
391
408
|
# Prevent deleting the root memories directory
|
|
392
409
|
if args.path == MEMORY_VIRTUAL_ROOT or args.path == MEMORY_VIRTUAL_ROOT + "/":
|
|
393
|
-
return ToolResultItem(status="error", output="Cannot delete the memories root directory")
|
|
410
|
+
return model.ToolResultItem(status="error", output="Cannot delete the memories root directory")
|
|
394
411
|
|
|
395
412
|
actual_path, error = _validate_path(args.path)
|
|
396
413
|
if error:
|
|
397
|
-
return ToolResultItem(status="error", output=error)
|
|
414
|
+
return model.ToolResultItem(status="error", output=error)
|
|
398
415
|
assert actual_path is not None
|
|
399
416
|
|
|
400
417
|
if not actual_path.exists():
|
|
401
|
-
return ToolResultItem(status="error", output=f"Path does not exist: {args.path}")
|
|
418
|
+
return model.ToolResultItem(status="error", output=f"Path does not exist: {args.path}")
|
|
402
419
|
|
|
403
420
|
try:
|
|
404
421
|
if actual_path.is_dir():
|
|
405
422
|
await asyncio.to_thread(shutil.rmtree, actual_path)
|
|
406
|
-
return ToolResultItem(status="success", output=f"Directory deleted: {args.path}")
|
|
423
|
+
return model.ToolResultItem(status="success", output=f"Directory deleted: {args.path}")
|
|
407
424
|
else:
|
|
408
425
|
await asyncio.to_thread(os.remove, actual_path)
|
|
409
|
-
return ToolResultItem(status="success", output=f"File deleted: {args.path}")
|
|
426
|
+
return model.ToolResultItem(status="success", output=f"File deleted: {args.path}")
|
|
410
427
|
except Exception as e:
|
|
411
|
-
return ToolResultItem(status="error", output=f"Failed to delete: {e}")
|
|
428
|
+
return model.ToolResultItem(status="error", output=f"Failed to delete: {e}")
|
|
412
429
|
|
|
413
430
|
@classmethod
|
|
414
|
-
async def _rename(cls, args: MemoryArguments) -> ToolResultItem:
|
|
431
|
+
async def _rename(cls, args: MemoryArguments) -> model.ToolResultItem:
|
|
415
432
|
if args.old_path is None:
|
|
416
|
-
return ToolResultItem(status="error", output="old_path is required for rename command")
|
|
433
|
+
return model.ToolResultItem(status="error", output="old_path is required for rename command")
|
|
417
434
|
if args.new_path is None:
|
|
418
|
-
return ToolResultItem(status="error", output="new_path is required for rename command")
|
|
435
|
+
return model.ToolResultItem(status="error", output="new_path is required for rename command")
|
|
419
436
|
|
|
420
437
|
# Prevent renaming the root memories directory
|
|
421
438
|
if args.old_path == MEMORY_VIRTUAL_ROOT or args.old_path == MEMORY_VIRTUAL_ROOT + "/":
|
|
422
|
-
return ToolResultItem(status="error", output="Cannot rename the memories root directory")
|
|
439
|
+
return model.ToolResultItem(status="error", output="Cannot rename the memories root directory")
|
|
423
440
|
|
|
424
441
|
old_actual, error = _validate_path(args.old_path)
|
|
425
442
|
if error:
|
|
426
|
-
return ToolResultItem(status="error", output=f"Invalid old_path: {error}")
|
|
443
|
+
return model.ToolResultItem(status="error", output=f"Invalid old_path: {error}")
|
|
427
444
|
assert old_actual is not None
|
|
428
445
|
|
|
429
446
|
new_actual, error = _validate_path(args.new_path)
|
|
430
447
|
if error:
|
|
431
|
-
return ToolResultItem(status="error", output=f"Invalid new_path: {error}")
|
|
448
|
+
return model.ToolResultItem(status="error", output=f"Invalid new_path: {error}")
|
|
432
449
|
assert new_actual is not None
|
|
433
450
|
|
|
434
451
|
if not old_actual.exists():
|
|
435
|
-
return ToolResultItem(status="error", output=f"Source path does not exist: {args.old_path}")
|
|
452
|
+
return model.ToolResultItem(status="error", output=f"Source path does not exist: {args.old_path}")
|
|
436
453
|
if new_actual.exists():
|
|
437
|
-
return ToolResultItem(status="error", output=f"Destination already exists: {args.new_path}")
|
|
454
|
+
return model.ToolResultItem(status="error", output=f"Destination already exists: {args.new_path}")
|
|
438
455
|
|
|
439
456
|
try:
|
|
440
457
|
# Ensure parent directory of destination exists
|
|
441
458
|
new_actual.parent.mkdir(parents=True, exist_ok=True)
|
|
442
459
|
await asyncio.to_thread(shutil.move, str(old_actual), str(new_actual))
|
|
443
|
-
return ToolResultItem(status="success", output=f"Renamed {args.old_path} to {args.new_path}")
|
|
460
|
+
return model.ToolResultItem(status="success", output=f"Renamed {args.old_path} to {args.new_path}")
|
|
444
461
|
except Exception as e:
|
|
445
|
-
return ToolResultItem(status="error", output=f"Failed to rename: {e}")
|
|
462
|
+
return model.ToolResultItem(status="error", output=f"Failed to rename: {e}")
|
|
@@ -5,12 +5,10 @@ from pydantic import BaseModel
|
|
|
5
5
|
from klaude_code.core.tool.memory.skill_loader import SkillLoader
|
|
6
6
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
7
7
|
from klaude_code.core.tool.tool_registry import register
|
|
8
|
-
from klaude_code.protocol
|
|
9
|
-
from klaude_code.protocol.model import ToolResultItem
|
|
10
|
-
from klaude_code.protocol.tools import SKILL
|
|
8
|
+
from klaude_code.protocol import llm_param, model, tools
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
@register(SKILL)
|
|
11
|
+
@register(tools.SKILL)
|
|
14
12
|
class SkillTool(ToolABC):
|
|
15
13
|
"""Tool to execute/load a skill within the main conversation"""
|
|
16
14
|
|
|
@@ -22,12 +20,12 @@ class SkillTool(ToolABC):
|
|
|
22
20
|
cls._skill_loader = loader
|
|
23
21
|
|
|
24
22
|
@classmethod
|
|
25
|
-
def schema(cls) -> ToolSchema:
|
|
23
|
+
def schema(cls) -> llm_param.ToolSchema:
|
|
26
24
|
"""Generate schema with embedded available skills metadata"""
|
|
27
25
|
skills_xml = cls._generate_skills_xml()
|
|
28
26
|
|
|
29
|
-
return ToolSchema(
|
|
30
|
-
name=SKILL,
|
|
27
|
+
return llm_param.ToolSchema(
|
|
28
|
+
name=tools.SKILL,
|
|
31
29
|
type="function",
|
|
32
30
|
description=load_desc(Path(__file__).parent / "skill_tool.md", {"skills_xml": skills_xml}),
|
|
33
31
|
parameters={
|
|
@@ -61,18 +59,18 @@ class SkillTool(ToolABC):
|
|
|
61
59
|
command: str
|
|
62
60
|
|
|
63
61
|
@classmethod
|
|
64
|
-
async def call(cls, arguments: str) -> ToolResultItem:
|
|
62
|
+
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
65
63
|
"""Load and return full skill content"""
|
|
66
64
|
try:
|
|
67
65
|
args = cls.SkillArguments.model_validate_json(arguments)
|
|
68
66
|
except ValueError as e:
|
|
69
|
-
return ToolResultItem(
|
|
67
|
+
return model.ToolResultItem(
|
|
70
68
|
status="error",
|
|
71
69
|
output=f"Invalid arguments: {e}",
|
|
72
70
|
)
|
|
73
71
|
|
|
74
72
|
if not cls._skill_loader:
|
|
75
|
-
return ToolResultItem(
|
|
73
|
+
return model.ToolResultItem(
|
|
76
74
|
status="error",
|
|
77
75
|
output="Skill loader not initialized",
|
|
78
76
|
)
|
|
@@ -81,7 +79,7 @@ class SkillTool(ToolABC):
|
|
|
81
79
|
|
|
82
80
|
if not skill:
|
|
83
81
|
available = ", ".join(cls._skill_loader.list_skills())
|
|
84
|
-
return ToolResultItem(
|
|
82
|
+
return model.ToolResultItem(
|
|
85
83
|
status="error",
|
|
86
84
|
output=f"Skill '{args.command}' does not exist. Available skills: {available}",
|
|
87
85
|
)
|
|
@@ -96,4 +94,4 @@ class SkillTool(ToolABC):
|
|
|
96
94
|
Base directory for this skill: {base_dir}
|
|
97
95
|
|
|
98
96
|
{skill.to_prompt()}"""
|
|
99
|
-
return ToolResultItem(status="success", output=result)
|
|
97
|
+
return model.ToolResultItem(status="success", output=result)
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import re
|
|
2
3
|
import subprocess
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
from pydantic import BaseModel
|
|
6
7
|
|
|
7
|
-
from klaude_code
|
|
8
|
+
from klaude_code import const
|
|
8
9
|
from klaude_code.core.tool.shell.command_safety import is_safe_command, strip_bash_lc
|
|
9
10
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
10
11
|
from klaude_code.core.tool.tool_registry import register
|
|
11
|
-
from klaude_code.protocol
|
|
12
|
-
from klaude_code.protocol.model import ToolResultItem
|
|
13
|
-
from klaude_code.protocol.tools import BASH
|
|
12
|
+
from klaude_code.protocol import llm_param, model, tools
|
|
14
13
|
|
|
14
|
+
# Regex to strip ANSI escape sequences from command output
|
|
15
|
+
_ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*m")
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
@register(tools.BASH)
|
|
17
19
|
class BashTool(ToolABC):
|
|
18
20
|
@classmethod
|
|
19
|
-
def schema(cls) -> ToolSchema:
|
|
20
|
-
return ToolSchema(
|
|
21
|
-
name=BASH,
|
|
21
|
+
def schema(cls) -> llm_param.ToolSchema:
|
|
22
|
+
return llm_param.ToolSchema(
|
|
23
|
+
name=tools.BASH,
|
|
22
24
|
type="function",
|
|
23
25
|
description=load_desc(Path(__file__).parent / "bash_tool.md"),
|
|
24
26
|
parameters={
|
|
@@ -30,8 +32,8 @@ class BashTool(ToolABC):
|
|
|
30
32
|
},
|
|
31
33
|
"timeout_ms": {
|
|
32
34
|
"type": "integer",
|
|
33
|
-
"description": f"The timeout for the command in milliseconds, default is {BASH_DEFAULT_TIMEOUT_MS}",
|
|
34
|
-
"default": BASH_DEFAULT_TIMEOUT_MS,
|
|
35
|
+
"description": f"The timeout for the command in milliseconds, default is {const.BASH_DEFAULT_TIMEOUT_MS}",
|
|
36
|
+
"default": const.BASH_DEFAULT_TIMEOUT_MS,
|
|
35
37
|
},
|
|
36
38
|
},
|
|
37
39
|
"required": ["command"],
|
|
@@ -40,27 +42,27 @@ class BashTool(ToolABC):
|
|
|
40
42
|
|
|
41
43
|
class BashArguments(BaseModel):
|
|
42
44
|
command: str
|
|
43
|
-
timeout_ms: int = BASH_DEFAULT_TIMEOUT_MS
|
|
45
|
+
timeout_ms: int = const.BASH_DEFAULT_TIMEOUT_MS
|
|
44
46
|
|
|
45
47
|
@classmethod
|
|
46
|
-
async def call(cls, arguments: str) -> ToolResultItem:
|
|
48
|
+
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
47
49
|
try:
|
|
48
50
|
args = BashTool.BashArguments.model_validate_json(arguments)
|
|
49
51
|
except ValueError as e:
|
|
50
|
-
return ToolResultItem(
|
|
52
|
+
return model.ToolResultItem(
|
|
51
53
|
status="error",
|
|
52
54
|
output=f"Invalid arguments: {e}",
|
|
53
55
|
)
|
|
54
56
|
return await cls.call_with_args(args)
|
|
55
57
|
|
|
56
58
|
@classmethod
|
|
57
|
-
async def call_with_args(cls, args: BashArguments) -> ToolResultItem:
|
|
59
|
+
async def call_with_args(cls, args: BashArguments) -> model.ToolResultItem:
|
|
58
60
|
command_str = strip_bash_lc(args.command)
|
|
59
61
|
|
|
60
62
|
# Safety check: only execute commands proven as "known safe"
|
|
61
63
|
result = is_safe_command(command_str)
|
|
62
64
|
if not result.is_safe:
|
|
63
|
-
return ToolResultItem(
|
|
65
|
+
return model.ToolResultItem(
|
|
64
66
|
status="error",
|
|
65
67
|
output=f"Command rejected: {result.error_msg}",
|
|
66
68
|
)
|
|
@@ -80,8 +82,8 @@ class BashTool(ToolABC):
|
|
|
80
82
|
check=False,
|
|
81
83
|
)
|
|
82
84
|
|
|
83
|
-
stdout = completed.stdout or ""
|
|
84
|
-
stderr = completed.stderr or ""
|
|
85
|
+
stdout = _ANSI_ESCAPE_RE.sub("", completed.stdout or "")
|
|
86
|
+
stderr = _ANSI_ESCAPE_RE.sub("", completed.stderr or "")
|
|
85
87
|
rc = completed.returncode
|
|
86
88
|
|
|
87
89
|
if rc == 0:
|
|
@@ -89,7 +91,7 @@ class BashTool(ToolABC):
|
|
|
89
91
|
# Include stderr if there is useful diagnostics despite success
|
|
90
92
|
if stderr.strip():
|
|
91
93
|
output = (output + ("\n" if output else "")) + f"[stderr]\n{stderr}"
|
|
92
|
-
return ToolResultItem(
|
|
94
|
+
return model.ToolResultItem(
|
|
93
95
|
status="success",
|
|
94
96
|
output=output.strip(),
|
|
95
97
|
)
|
|
@@ -101,23 +103,23 @@ class BashTool(ToolABC):
|
|
|
101
103
|
combined += f"[stderr]\n{stderr}"
|
|
102
104
|
if not combined:
|
|
103
105
|
combined = f"Command exited with code {rc}"
|
|
104
|
-
return ToolResultItem(
|
|
106
|
+
return model.ToolResultItem(
|
|
105
107
|
status="error",
|
|
106
108
|
output=combined.strip(),
|
|
107
109
|
)
|
|
108
110
|
|
|
109
111
|
except subprocess.TimeoutExpired:
|
|
110
|
-
return ToolResultItem(
|
|
112
|
+
return model.ToolResultItem(
|
|
111
113
|
status="error",
|
|
112
114
|
output=f"Timeout after {args.timeout_ms} ms running: {command_str}",
|
|
113
115
|
)
|
|
114
116
|
except FileNotFoundError:
|
|
115
|
-
return ToolResultItem(
|
|
117
|
+
return model.ToolResultItem(
|
|
116
118
|
status="error",
|
|
117
119
|
output="bash not found on system path",
|
|
118
120
|
)
|
|
119
121
|
except Exception as e: # safeguard against unexpected failures
|
|
120
|
-
return ToolResultItem(
|
|
122
|
+
return model.ToolResultItem(
|
|
121
123
|
status="error",
|
|
122
124
|
output=f"Execution error: {e}",
|
|
123
125
|
)
|
|
@@ -323,7 +323,18 @@ def _is_safe_argv(argv: list[str]) -> SafetyCheckResult:
|
|
|
323
323
|
return SafetyCheckResult(True)
|
|
324
324
|
|
|
325
325
|
# Build tools and linters - allow all subcommands
|
|
326
|
-
if cmd0 in {
|
|
326
|
+
if cmd0 in {
|
|
327
|
+
"cargo",
|
|
328
|
+
"uv",
|
|
329
|
+
"go",
|
|
330
|
+
"ruff",
|
|
331
|
+
"pyright",
|
|
332
|
+
"make",
|
|
333
|
+
"isort",
|
|
334
|
+
"npm",
|
|
335
|
+
"pnpm",
|
|
336
|
+
"bun",
|
|
337
|
+
}:
|
|
327
338
|
return SafetyCheckResult(True)
|
|
328
339
|
|
|
329
340
|
if cmd0 == "sed":
|
|
@@ -12,11 +12,10 @@ from typing import TYPE_CHECKING, ClassVar
|
|
|
12
12
|
|
|
13
13
|
from klaude_code.core.tool.tool_abc import ToolABC
|
|
14
14
|
from klaude_code.core.tool.tool_context import current_run_subtask_callback
|
|
15
|
-
from klaude_code.protocol
|
|
16
|
-
from klaude_code.protocol.model import SubAgentState, ToolResultItem, ToolResultUIExtra, ToolResultUIExtraType
|
|
15
|
+
from klaude_code.protocol import llm_param, model
|
|
17
16
|
|
|
18
17
|
if TYPE_CHECKING:
|
|
19
|
-
from klaude_code.
|
|
18
|
+
from klaude_code.protocol.sub_agent import SubAgentProfile
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class SubAgentTool(ToolABC):
|
|
@@ -38,9 +37,9 @@ class SubAgentTool(ToolABC):
|
|
|
38
37
|
)
|
|
39
38
|
|
|
40
39
|
@classmethod
|
|
41
|
-
def schema(cls) -> ToolSchema:
|
|
40
|
+
def schema(cls) -> llm_param.ToolSchema:
|
|
42
41
|
profile = cls._profile
|
|
43
|
-
return ToolSchema(
|
|
42
|
+
return llm_param.ToolSchema(
|
|
44
43
|
name=profile.name,
|
|
45
44
|
type="function",
|
|
46
45
|
description=profile.description,
|
|
@@ -48,17 +47,17 @@ class SubAgentTool(ToolABC):
|
|
|
48
47
|
)
|
|
49
48
|
|
|
50
49
|
@classmethod
|
|
51
|
-
async def call(cls, arguments: str) -> ToolResultItem:
|
|
50
|
+
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
52
51
|
profile = cls._profile
|
|
53
52
|
|
|
54
53
|
try:
|
|
55
54
|
args = json.loads(arguments)
|
|
56
55
|
except json.JSONDecodeError as e:
|
|
57
|
-
return ToolResultItem(status="error", output=f"Invalid JSON arguments: {e}")
|
|
56
|
+
return model.ToolResultItem(status="error", output=f"Invalid JSON arguments: {e}")
|
|
58
57
|
|
|
59
58
|
runner = current_run_subtask_callback.get()
|
|
60
59
|
if runner is None:
|
|
61
|
-
return ToolResultItem(status="error", output="No subtask runner available in this context")
|
|
60
|
+
return model.ToolResultItem(status="error", output="No subtask runner available in this context")
|
|
62
61
|
|
|
63
62
|
# Build the prompt using the profile's prompt builder
|
|
64
63
|
prompt = profile.prompt_builder(args)
|
|
@@ -66,7 +65,7 @@ class SubAgentTool(ToolABC):
|
|
|
66
65
|
|
|
67
66
|
try:
|
|
68
67
|
result = await runner(
|
|
69
|
-
SubAgentState(
|
|
68
|
+
model.SubAgentState(
|
|
70
69
|
sub_agent_type=profile.name,
|
|
71
70
|
sub_agent_desc=description,
|
|
72
71
|
sub_agent_prompt=prompt,
|
|
@@ -75,10 +74,10 @@ class SubAgentTool(ToolABC):
|
|
|
75
74
|
except asyncio.CancelledError:
|
|
76
75
|
raise
|
|
77
76
|
except Exception as e:
|
|
78
|
-
return ToolResultItem(status="error", output=f"Failed to run subtask: {e}")
|
|
77
|
+
return model.ToolResultItem(status="error", output=f"Failed to run subtask: {e}")
|
|
79
78
|
|
|
80
|
-
return ToolResultItem(
|
|
79
|
+
return model.ToolResultItem(
|
|
81
80
|
status="success" if not result.error else "error",
|
|
82
81
|
output=result.task_result or "",
|
|
83
|
-
ui_extra=ToolResultUIExtra(type=ToolResultUIExtraType.SESSION_ID, session_id=result.session_id),
|
|
82
|
+
ui_extra=model.ToolResultUIExtra(type=model.ToolResultUIExtraType.SESSION_ID, session_id=result.session_id),
|
|
84
83
|
)
|