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
|
@@ -11,9 +11,7 @@ from klaude_code.core.tool.file.edit_tool import EditTool
|
|
|
11
11
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
12
12
|
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
13
13
|
from klaude_code.core.tool.tool_registry import register
|
|
14
|
-
from klaude_code.protocol
|
|
15
|
-
from klaude_code.protocol.model import ToolResultItem, ToolResultUIExtra, ToolResultUIExtraType
|
|
16
|
-
from klaude_code.protocol.tools import MULTI_EDIT
|
|
14
|
+
from klaude_code.protocol import llm_param, model, tools
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
def _is_directory(path: str) -> bool:
|
|
@@ -42,7 +40,7 @@ def _write_text(path: str, content: str) -> None:
|
|
|
42
40
|
f.write(content)
|
|
43
41
|
|
|
44
42
|
|
|
45
|
-
@register(MULTI_EDIT)
|
|
43
|
+
@register(tools.MULTI_EDIT)
|
|
46
44
|
class MultiEditTool(ToolABC):
|
|
47
45
|
class MultiEditEditItem(BaseModel):
|
|
48
46
|
old_string: str
|
|
@@ -54,9 +52,9 @@ class MultiEditTool(ToolABC):
|
|
|
54
52
|
edits: list[MultiEditTool.MultiEditEditItem]
|
|
55
53
|
|
|
56
54
|
@classmethod
|
|
57
|
-
def schema(cls) -> ToolSchema:
|
|
58
|
-
return ToolSchema(
|
|
59
|
-
name=MULTI_EDIT,
|
|
55
|
+
def schema(cls) -> llm_param.ToolSchema:
|
|
56
|
+
return llm_param.ToolSchema(
|
|
57
|
+
name=tools.MULTI_EDIT,
|
|
60
58
|
type="function",
|
|
61
59
|
description=load_desc(Path(__file__).parent / "multi_edit_tool.md"),
|
|
62
60
|
parameters={
|
|
@@ -98,17 +96,17 @@ class MultiEditTool(ToolABC):
|
|
|
98
96
|
)
|
|
99
97
|
|
|
100
98
|
@classmethod
|
|
101
|
-
async def call(cls, arguments: str) -> ToolResultItem:
|
|
99
|
+
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
102
100
|
try:
|
|
103
101
|
args = MultiEditTool.MultiEditArguments.model_validate_json(arguments)
|
|
104
102
|
except Exception as e: # pragma: no cover - defensive
|
|
105
|
-
return ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
103
|
+
return model.ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
106
104
|
|
|
107
105
|
file_path = os.path.abspath(args.file_path)
|
|
108
106
|
|
|
109
107
|
# Directory error first
|
|
110
108
|
if _is_directory(file_path):
|
|
111
|
-
return ToolResultItem(
|
|
109
|
+
return model.ToolResultItem(
|
|
112
110
|
status="error",
|
|
113
111
|
output="<tool_use_error>Illegal operation on a directory. multi_edit</tool_use_error>",
|
|
114
112
|
)
|
|
@@ -120,7 +118,7 @@ class MultiEditTool(ToolABC):
|
|
|
120
118
|
if file_tracker is not None:
|
|
121
119
|
tracked = file_tracker.get(file_path)
|
|
122
120
|
if tracked is None:
|
|
123
|
-
return ToolResultItem(
|
|
121
|
+
return model.ToolResultItem(
|
|
124
122
|
status="error",
|
|
125
123
|
output=("File has not been read yet. Read it first before writing to it."),
|
|
126
124
|
)
|
|
@@ -129,7 +127,7 @@ class MultiEditTool(ToolABC):
|
|
|
129
127
|
except Exception:
|
|
130
128
|
current_mtime = tracked
|
|
131
129
|
if current_mtime != tracked:
|
|
132
|
-
return ToolResultItem(
|
|
130
|
+
return model.ToolResultItem(
|
|
133
131
|
status="error",
|
|
134
132
|
output=(
|
|
135
133
|
"File has been modified externally. Either by user or a linter. Read it first before writing to it."
|
|
@@ -138,7 +136,7 @@ class MultiEditTool(ToolABC):
|
|
|
138
136
|
else:
|
|
139
137
|
# Allow creation only if first edit is creating content (old_string == "")
|
|
140
138
|
if not args.edits or args.edits[0].old_string != "":
|
|
141
|
-
return ToolResultItem(
|
|
139
|
+
return model.ToolResultItem(
|
|
142
140
|
status="error",
|
|
143
141
|
output=("File has not been read yet. Read it first before writing to it."),
|
|
144
142
|
)
|
|
@@ -159,7 +157,7 @@ class MultiEditTool(ToolABC):
|
|
|
159
157
|
replace_all=edit.replace_all,
|
|
160
158
|
)
|
|
161
159
|
if err is not None:
|
|
162
|
-
return ToolResultItem(status="error", output=err)
|
|
160
|
+
return model.ToolResultItem(status="error", output=err)
|
|
163
161
|
# Apply to staged content
|
|
164
162
|
staged = EditTool.execute(
|
|
165
163
|
content=staged,
|
|
@@ -172,7 +170,7 @@ class MultiEditTool(ToolABC):
|
|
|
172
170
|
try:
|
|
173
171
|
await asyncio.to_thread(_write_text, file_path, staged)
|
|
174
172
|
except Exception as e: # pragma: no cover
|
|
175
|
-
return ToolResultItem(status="error", output=f"<tool_use_error>{e}</tool_use_error>")
|
|
173
|
+
return model.ToolResultItem(status="error", output=f"<tool_use_error>{e}</tool_use_error>")
|
|
176
174
|
|
|
177
175
|
# Prepare UI extra: unified diff
|
|
178
176
|
diff_lines = list(
|
|
@@ -185,7 +183,7 @@ class MultiEditTool(ToolABC):
|
|
|
185
183
|
)
|
|
186
184
|
)
|
|
187
185
|
diff_text = "\n".join(diff_lines)
|
|
188
|
-
ui_extra = ToolResultUIExtra(type=ToolResultUIExtraType.DIFF_TEXT, diff_text=diff_text)
|
|
186
|
+
ui_extra = model.ToolResultUIExtra(type=model.ToolResultUIExtraType.DIFF_TEXT, diff_text=diff_text)
|
|
189
187
|
|
|
190
188
|
# Update tracker
|
|
191
189
|
if file_tracker is not None:
|
|
@@ -198,4 +196,4 @@ class MultiEditTool(ToolABC):
|
|
|
198
196
|
lines = [f"Applied {len(args.edits)} edits to {file_path}:"]
|
|
199
197
|
for i, edit in enumerate(args.edits, start=1):
|
|
200
198
|
lines.append(f'{i}. Replaced "{edit.old_string}" with "{edit.new_string}"')
|
|
201
|
-
return ToolResultItem(status="success", output="\n".join(lines), ui_extra=ui_extra)
|
|
199
|
+
return model.ToolResultItem(status="success", output="\n".join(lines), ui_extra=ui_extra)
|
|
@@ -8,19 +8,11 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
|
|
11
|
-
from klaude_code
|
|
12
|
-
READ_CHAR_LIMIT_PER_LINE,
|
|
13
|
-
READ_GLOBAL_LINE_CAP,
|
|
14
|
-
READ_MAX_CHARS,
|
|
15
|
-
READ_MAX_IMAGE_BYTES,
|
|
16
|
-
READ_MAX_KB,
|
|
17
|
-
)
|
|
11
|
+
from klaude_code import const
|
|
18
12
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
19
13
|
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
20
14
|
from klaude_code.core.tool.tool_registry import register
|
|
21
|
-
from klaude_code.protocol
|
|
22
|
-
from klaude_code.protocol.model import ImageURLPart, ToolResultItem
|
|
23
|
-
from klaude_code.protocol.tools import READ
|
|
15
|
+
from klaude_code.protocol import llm_param, model, tools
|
|
24
16
|
|
|
25
17
|
SYSTEM_REMINDER_MALICIOUS = (
|
|
26
18
|
"<system-reminder>\n"
|
|
@@ -61,8 +53,8 @@ class ReadOptions:
|
|
|
61
53
|
file_path: str
|
|
62
54
|
offset: int
|
|
63
55
|
limit: int | None
|
|
64
|
-
char_limit_per_line: int | None = READ_CHAR_LIMIT_PER_LINE
|
|
65
|
-
global_line_cap: int | None = READ_GLOBAL_LINE_CAP
|
|
56
|
+
char_limit_per_line: int | None = const.READ_CHAR_LIMIT_PER_LINE
|
|
57
|
+
global_line_cap: int | None = const.READ_GLOBAL_LINE_CAP
|
|
66
58
|
|
|
67
59
|
|
|
68
60
|
@dataclass
|
|
@@ -135,7 +127,7 @@ def _encode_image_to_data_url(file_path: str, mime_type: str) -> str:
|
|
|
135
127
|
return f"data:{mime_type};base64,{encoded}"
|
|
136
128
|
|
|
137
129
|
|
|
138
|
-
@register(READ)
|
|
130
|
+
@register(tools.READ)
|
|
139
131
|
class ReadTool(ToolABC):
|
|
140
132
|
class ReadArguments(BaseModel):
|
|
141
133
|
file_path: str
|
|
@@ -143,9 +135,9 @@ class ReadTool(ToolABC):
|
|
|
143
135
|
limit: int | None = Field(default=None)
|
|
144
136
|
|
|
145
137
|
@classmethod
|
|
146
|
-
def schema(cls) -> ToolSchema:
|
|
147
|
-
return ToolSchema(
|
|
148
|
-
name=READ,
|
|
138
|
+
def schema(cls) -> llm_param.ToolSchema:
|
|
139
|
+
return llm_param.ToolSchema(
|
|
140
|
+
name=tools.READ,
|
|
149
141
|
type="function",
|
|
150
142
|
description=load_desc(Path(__file__).parent / "read_tool.md"),
|
|
151
143
|
parameters={
|
|
@@ -170,20 +162,25 @@ class ReadTool(ToolABC):
|
|
|
170
162
|
)
|
|
171
163
|
|
|
172
164
|
@classmethod
|
|
173
|
-
async def call(cls, arguments: str) -> ToolResultItem:
|
|
165
|
+
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
174
166
|
try:
|
|
175
167
|
args = ReadTool.ReadArguments.model_validate_json(arguments)
|
|
176
168
|
except Exception as e: # pragma: no cover - defensive
|
|
177
|
-
return ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
169
|
+
return model.ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
178
170
|
return await cls.call_with_args(args)
|
|
179
171
|
|
|
180
172
|
@classmethod
|
|
181
173
|
def _effective_limits(cls) -> tuple[int | None, int | None, int | None, int | None]:
|
|
182
174
|
"""Return effective limits based on current policy: char_per_line, global_line_cap, max_chars, max_kb"""
|
|
183
|
-
return
|
|
175
|
+
return (
|
|
176
|
+
const.READ_CHAR_LIMIT_PER_LINE,
|
|
177
|
+
const.READ_GLOBAL_LINE_CAP,
|
|
178
|
+
const.READ_MAX_CHARS,
|
|
179
|
+
const.READ_MAX_KB,
|
|
180
|
+
)
|
|
184
181
|
|
|
185
182
|
@classmethod
|
|
186
|
-
async def call_with_args(cls, args: ReadTool.ReadArguments) -> ToolResultItem:
|
|
183
|
+
async def call_with_args(cls, args: ReadTool.ReadArguments) -> model.ToolResultItem:
|
|
187
184
|
# Accept relative path by resolving to absolute (schema encourages absolute)
|
|
188
185
|
file_path = os.path.abspath(args.file_path)
|
|
189
186
|
|
|
@@ -192,15 +189,19 @@ class ReadTool(ToolABC):
|
|
|
192
189
|
|
|
193
190
|
# Common file errors
|
|
194
191
|
if _is_directory(file_path):
|
|
195
|
-
return ToolResultItem(
|
|
196
|
-
status="error",
|
|
192
|
+
return model.ToolResultItem(
|
|
193
|
+
status="error",
|
|
194
|
+
output="<tool_use_error>Illegal operation on a directory. read</tool_use_error>",
|
|
197
195
|
)
|
|
198
196
|
if not _file_exists(file_path):
|
|
199
|
-
return ToolResultItem(
|
|
197
|
+
return model.ToolResultItem(
|
|
198
|
+
status="error",
|
|
199
|
+
output="<tool_use_error>File does not exist.</tool_use_error>",
|
|
200
|
+
)
|
|
200
201
|
|
|
201
202
|
# Check for PDF files
|
|
202
203
|
if Path(file_path).suffix.lower() == ".pdf":
|
|
203
|
-
return ToolResultItem(
|
|
204
|
+
return model.ToolResultItem(
|
|
204
205
|
status="error",
|
|
205
206
|
output=(
|
|
206
207
|
"<tool_use_error>PDF files are not supported by this tool. "
|
|
@@ -226,9 +227,9 @@ class ReadTool(ToolABC):
|
|
|
226
227
|
|
|
227
228
|
is_image_file = _is_supported_image_file(file_path)
|
|
228
229
|
if is_image_file:
|
|
229
|
-
if size_bytes > READ_MAX_IMAGE_BYTES:
|
|
230
|
+
if size_bytes > const.READ_MAX_IMAGE_BYTES:
|
|
230
231
|
size_mb = size_bytes / (1024 * 1024)
|
|
231
|
-
return ToolResultItem(
|
|
232
|
+
return model.ToolResultItem(
|
|
232
233
|
status="error",
|
|
233
234
|
output=(
|
|
234
235
|
f"<tool_use_error>Image size ({size_mb:.2f}MB) exceeds maximum supported size (4.00MB) for inline transfer.</tool_use_error>"
|
|
@@ -238,7 +239,7 @@ class ReadTool(ToolABC):
|
|
|
238
239
|
mime_type = _image_mime_type(file_path)
|
|
239
240
|
data_url = _encode_image_to_data_url(file_path, mime_type)
|
|
240
241
|
except Exception as exc:
|
|
241
|
-
return ToolResultItem(
|
|
242
|
+
return model.ToolResultItem(
|
|
242
243
|
status="error",
|
|
243
244
|
output=f"<tool_use_error>Failed to read image file: {exc}</tool_use_error>",
|
|
244
245
|
)
|
|
@@ -246,8 +247,8 @@ class ReadTool(ToolABC):
|
|
|
246
247
|
_track_file_access(file_path)
|
|
247
248
|
size_kb = size_bytes / 1024.0 if size_bytes else 0.0
|
|
248
249
|
output_text = f"[image] {Path(file_path).name} ({size_kb:.1f}KB)"
|
|
249
|
-
image_part = ImageURLPart(image_url=ImageURLPart.ImageURL(url=data_url, id=None))
|
|
250
|
-
return ToolResultItem(status="success", output=output_text, images=[image_part])
|
|
250
|
+
image_part = model.ImageURLPart(image_url=model.ImageURLPart.ImageURL(url=data_url, id=None))
|
|
251
|
+
return model.ToolResultItem(status="success", output=output_text, images=[image_part])
|
|
251
252
|
|
|
252
253
|
if (
|
|
253
254
|
not is_image_file
|
|
@@ -257,7 +258,7 @@ class ReadTool(ToolABC):
|
|
|
257
258
|
and size_bytes > max_kb * 1024
|
|
258
259
|
):
|
|
259
260
|
size_kb = size_bytes / 1024.0
|
|
260
|
-
return ToolResultItem(
|
|
261
|
+
return model.ToolResultItem(
|
|
261
262
|
status="error",
|
|
262
263
|
output=(
|
|
263
264
|
f"File content ({size_kb:.1f}KB) exceeds maximum allowed size ({max_kb}KB). Please use offset and limit parameters to read specific portions of the file, or use the `rg` command to search for specific content."
|
|
@@ -285,10 +286,14 @@ class ReadTool(ToolABC):
|
|
|
285
286
|
)
|
|
286
287
|
|
|
287
288
|
except FileNotFoundError:
|
|
288
|
-
return ToolResultItem(
|
|
289
|
+
return model.ToolResultItem(
|
|
290
|
+
status="error",
|
|
291
|
+
output="<tool_use_error>File does not exist.</tool_use_error>",
|
|
292
|
+
)
|
|
289
293
|
except IsADirectoryError:
|
|
290
|
-
return ToolResultItem(
|
|
291
|
-
status="error",
|
|
294
|
+
return model.ToolResultItem(
|
|
295
|
+
status="error",
|
|
296
|
+
output="<tool_use_error>Illegal operation on a directory. read</tool_use_error>",
|
|
292
297
|
)
|
|
293
298
|
|
|
294
299
|
# If offset beyond total lines, emit system reminder warning
|
|
@@ -296,11 +301,11 @@ class ReadTool(ToolABC):
|
|
|
296
301
|
warn = f"<system-reminder>Warning: the file exists but is shorter than the provided offset ({offset}). The file has {read_result.total_lines} lines.</system-reminder>"
|
|
297
302
|
# Update FileTracker (we still consider it as a read attempt)
|
|
298
303
|
_track_file_access(file_path)
|
|
299
|
-
return ToolResultItem(status="success", output=warn)
|
|
304
|
+
return model.ToolResultItem(status="success", output=warn)
|
|
300
305
|
|
|
301
306
|
# After limit/offset, if total selected chars exceed limit, error (only check if limits are enabled)
|
|
302
307
|
if max_chars is not None and read_result.selected_chars_count > max_chars:
|
|
303
|
-
return ToolResultItem(
|
|
308
|
+
return model.ToolResultItem(
|
|
304
309
|
status="error",
|
|
305
310
|
output=(
|
|
306
311
|
f"File content ({read_result.selected_chars_count} chars) exceeds maximum allowed tokens ({max_chars}). Please use offset and limit parameters to read specific portions of the file, or use the `rg` command to search for specific content."
|
|
@@ -318,4 +323,4 @@ class ReadTool(ToolABC):
|
|
|
318
323
|
# Update FileTracker with last modified time
|
|
319
324
|
_track_file_access(file_path)
|
|
320
325
|
|
|
321
|
-
return ToolResultItem(status="success", output=read_result_str)
|
|
326
|
+
return model.ToolResultItem(status="success", output=read_result_str)
|
|
@@ -10,9 +10,7 @@ from pydantic import BaseModel
|
|
|
10
10
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
11
11
|
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
12
12
|
from klaude_code.core.tool.tool_registry import register
|
|
13
|
-
from klaude_code.protocol
|
|
14
|
-
from klaude_code.protocol.model import ToolResultItem, ToolResultUIExtra, ToolResultUIExtraType
|
|
15
|
-
from klaude_code.protocol.tools import WRITE
|
|
13
|
+
from klaude_code.protocol import llm_param, model, tools
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
def _is_directory(path: str) -> bool:
|
|
@@ -46,12 +44,12 @@ class WriteArguments(BaseModel):
|
|
|
46
44
|
content: str
|
|
47
45
|
|
|
48
46
|
|
|
49
|
-
@register(WRITE)
|
|
47
|
+
@register(tools.WRITE)
|
|
50
48
|
class WriteTool(ToolABC):
|
|
51
49
|
@classmethod
|
|
52
|
-
def schema(cls) -> ToolSchema:
|
|
53
|
-
return ToolSchema(
|
|
54
|
-
name=WRITE,
|
|
50
|
+
def schema(cls) -> llm_param.ToolSchema:
|
|
51
|
+
return llm_param.ToolSchema(
|
|
52
|
+
name=tools.WRITE,
|
|
55
53
|
type="function",
|
|
56
54
|
description=load_desc(Path(__file__).parent / "write_tool.md"),
|
|
57
55
|
parameters={
|
|
@@ -72,16 +70,16 @@ class WriteTool(ToolABC):
|
|
|
72
70
|
)
|
|
73
71
|
|
|
74
72
|
@classmethod
|
|
75
|
-
async def call(cls, arguments: str) -> ToolResultItem:
|
|
73
|
+
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
76
74
|
try:
|
|
77
75
|
args = WriteArguments.model_validate_json(arguments)
|
|
78
76
|
except Exception as e: # pragma: no cover - defensive
|
|
79
|
-
return ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
77
|
+
return model.ToolResultItem(status="error", output=f"Invalid arguments: {e}")
|
|
80
78
|
|
|
81
79
|
file_path = os.path.abspath(args.file_path)
|
|
82
80
|
|
|
83
81
|
if _is_directory(file_path):
|
|
84
|
-
return ToolResultItem(
|
|
82
|
+
return model.ToolResultItem(
|
|
85
83
|
status="error",
|
|
86
84
|
output="<tool_use_error>Illegal operation on a directory. write</tool_use_error>",
|
|
87
85
|
)
|
|
@@ -94,7 +92,7 @@ class WriteTool(ToolABC):
|
|
|
94
92
|
if file_tracker is not None:
|
|
95
93
|
tracked_mtime = file_tracker.get(file_path)
|
|
96
94
|
if tracked_mtime is None:
|
|
97
|
-
return ToolResultItem(
|
|
95
|
+
return model.ToolResultItem(
|
|
98
96
|
status="error",
|
|
99
97
|
output=("File has not been read yet. Read it first before writing to it."),
|
|
100
98
|
)
|
|
@@ -103,7 +101,7 @@ class WriteTool(ToolABC):
|
|
|
103
101
|
except Exception:
|
|
104
102
|
current_mtime = tracked_mtime
|
|
105
103
|
if current_mtime != tracked_mtime:
|
|
106
|
-
return ToolResultItem(
|
|
104
|
+
return model.ToolResultItem(
|
|
107
105
|
status="error",
|
|
108
106
|
output=(
|
|
109
107
|
"File has been modified externally. Either by user or a linter. "
|
|
@@ -122,7 +120,7 @@ class WriteTool(ToolABC):
|
|
|
122
120
|
try:
|
|
123
121
|
await asyncio.to_thread(_write_text, file_path, args.content)
|
|
124
122
|
except Exception as e: # pragma: no cover
|
|
125
|
-
return ToolResultItem(status="error", output=f"<tool_use_error>{e}</tool_use_error>")
|
|
123
|
+
return model.ToolResultItem(status="error", output=f"<tool_use_error>{e}</tool_use_error>")
|
|
126
124
|
|
|
127
125
|
if file_tracker is not None:
|
|
128
126
|
try:
|
|
@@ -142,7 +140,7 @@ class WriteTool(ToolABC):
|
|
|
142
140
|
)
|
|
143
141
|
)
|
|
144
142
|
diff_text = "\n".join(diff_lines)
|
|
145
|
-
ui_extra = ToolResultUIExtra(type=ToolResultUIExtraType.DIFF_TEXT, diff_text=diff_text)
|
|
143
|
+
ui_extra = model.ToolResultUIExtra(type=model.ToolResultUIExtraType.DIFF_TEXT, diff_text=diff_text)
|
|
146
144
|
|
|
147
145
|
message = f"File {'overwritten' if exists else 'created'} successfully at: {file_path}"
|
|
148
|
-
return ToolResultItem(status="success", output=message, ui_extra=ui_extra)
|
|
146
|
+
return model.ToolResultItem(status="success", output=message, ui_extra=ui_extra)
|