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.
Files changed (140) hide show
  1. klaude_code/cli/main.py +9 -4
  2. klaude_code/cli/runtime.py +42 -43
  3. klaude_code/command/__init__.py +7 -5
  4. klaude_code/command/clear_cmd.py +6 -29
  5. klaude_code/command/command_abc.py +44 -8
  6. klaude_code/command/diff_cmd.py +33 -27
  7. klaude_code/command/export_cmd.py +18 -26
  8. klaude_code/command/help_cmd.py +10 -8
  9. klaude_code/command/model_cmd.py +11 -40
  10. klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
  11. klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
  12. klaude_code/command/prompt-init.md +2 -5
  13. klaude_code/command/prompt_command.py +6 -6
  14. klaude_code/command/refresh_cmd.py +4 -5
  15. klaude_code/command/registry.py +16 -19
  16. klaude_code/command/terminal_setup_cmd.py +12 -11
  17. klaude_code/config/__init__.py +4 -0
  18. klaude_code/config/config.py +25 -26
  19. klaude_code/config/list_model.py +8 -3
  20. klaude_code/config/select_model.py +1 -1
  21. klaude_code/const/__init__.py +1 -1
  22. klaude_code/core/__init__.py +0 -3
  23. klaude_code/core/agent.py +25 -50
  24. klaude_code/core/executor.py +268 -101
  25. klaude_code/core/prompt.py +12 -12
  26. klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
  27. klaude_code/core/reminders.py +76 -95
  28. klaude_code/core/task.py +21 -14
  29. klaude_code/core/tool/__init__.py +45 -11
  30. klaude_code/core/tool/file/apply_patch.py +5 -1
  31. klaude_code/core/tool/file/apply_patch_tool.py +11 -13
  32. klaude_code/core/tool/file/edit_tool.py +27 -23
  33. klaude_code/core/tool/file/multi_edit_tool.py +15 -17
  34. klaude_code/core/tool/file/read_tool.py +41 -36
  35. klaude_code/core/tool/file/write_tool.py +13 -15
  36. klaude_code/core/tool/memory/memory_tool.py +85 -68
  37. klaude_code/core/tool/memory/skill_tool.py +10 -12
  38. klaude_code/core/tool/shell/bash_tool.py +24 -22
  39. klaude_code/core/tool/shell/command_safety.py +12 -1
  40. klaude_code/core/tool/sub_agent_tool.py +11 -12
  41. klaude_code/core/tool/todo/todo_write_tool.py +21 -28
  42. klaude_code/core/tool/todo/update_plan_tool.py +14 -24
  43. klaude_code/core/tool/tool_abc.py +3 -4
  44. klaude_code/core/tool/tool_context.py +7 -7
  45. klaude_code/core/tool/tool_registry.py +30 -47
  46. klaude_code/core/tool/tool_runner.py +35 -43
  47. klaude_code/core/tool/truncation.py +14 -20
  48. klaude_code/core/tool/web/mermaid_tool.py +12 -14
  49. klaude_code/core/tool/web/web_fetch_tool.py +15 -17
  50. klaude_code/core/turn.py +19 -7
  51. klaude_code/llm/__init__.py +3 -4
  52. klaude_code/llm/anthropic/client.py +30 -46
  53. klaude_code/llm/anthropic/input.py +4 -11
  54. klaude_code/llm/client.py +29 -8
  55. klaude_code/llm/input_common.py +66 -36
  56. klaude_code/llm/openai_compatible/client.py +42 -84
  57. klaude_code/llm/openai_compatible/input.py +11 -16
  58. klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
  59. klaude_code/llm/openrouter/client.py +40 -289
  60. klaude_code/llm/openrouter/input.py +13 -35
  61. klaude_code/llm/openrouter/reasoning_handler.py +209 -0
  62. klaude_code/llm/registry.py +5 -75
  63. klaude_code/llm/responses/client.py +34 -55
  64. klaude_code/llm/responses/input.py +24 -26
  65. klaude_code/llm/usage.py +109 -0
  66. klaude_code/protocol/__init__.py +4 -0
  67. klaude_code/protocol/events.py +3 -2
  68. klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
  69. klaude_code/protocol/model.py +49 -4
  70. klaude_code/protocol/op.py +18 -16
  71. klaude_code/protocol/op_handler.py +28 -0
  72. klaude_code/{core → protocol}/sub_agent.py +7 -0
  73. klaude_code/session/export.py +150 -70
  74. klaude_code/session/session.py +28 -14
  75. klaude_code/session/templates/export_session.html +180 -42
  76. klaude_code/trace/__init__.py +2 -2
  77. klaude_code/trace/log.py +11 -5
  78. klaude_code/ui/__init__.py +91 -8
  79. klaude_code/ui/core/__init__.py +1 -0
  80. klaude_code/ui/core/display.py +103 -0
  81. klaude_code/ui/core/input.py +71 -0
  82. klaude_code/ui/modes/__init__.py +1 -0
  83. klaude_code/ui/modes/debug/__init__.py +1 -0
  84. klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
  85. klaude_code/ui/modes/exec/__init__.py +1 -0
  86. klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
  87. klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
  88. klaude_code/ui/modes/repl/clipboard.py +152 -0
  89. klaude_code/ui/modes/repl/completers.py +429 -0
  90. klaude_code/ui/modes/repl/display.py +60 -0
  91. klaude_code/ui/modes/repl/event_handler.py +375 -0
  92. klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
  93. klaude_code/ui/modes/repl/key_bindings.py +170 -0
  94. klaude_code/ui/{repl → modes/repl}/renderer.py +109 -132
  95. klaude_code/ui/renderers/assistant.py +21 -0
  96. klaude_code/ui/renderers/common.py +0 -16
  97. klaude_code/ui/renderers/developer.py +18 -18
  98. klaude_code/ui/renderers/diffs.py +36 -14
  99. klaude_code/ui/renderers/errors.py +1 -1
  100. klaude_code/ui/renderers/metadata.py +50 -27
  101. klaude_code/ui/renderers/sub_agent.py +43 -9
  102. klaude_code/ui/renderers/thinking.py +33 -1
  103. klaude_code/ui/renderers/tools.py +212 -20
  104. klaude_code/ui/renderers/user_input.py +19 -23
  105. klaude_code/ui/rich/__init__.py +1 -0
  106. klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
  107. klaude_code/ui/{renderers → rich}/status.py +29 -18
  108. klaude_code/ui/{base → rich}/theme.py +8 -2
  109. klaude_code/ui/terminal/__init__.py +1 -0
  110. klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
  111. klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
  112. klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
  113. klaude_code/ui/utils/__init__.py +1 -0
  114. klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
  115. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
  116. klaude_code-1.2.3.dist-info/RECORD +161 -0
  117. klaude_code/core/clipboard_manifest.py +0 -124
  118. klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
  119. klaude_code/ui/base/__init__.py +0 -1
  120. klaude_code/ui/base/display_abc.py +0 -36
  121. klaude_code/ui/base/input_abc.py +0 -20
  122. klaude_code/ui/repl/display.py +0 -36
  123. klaude_code/ui/repl/event_handler.py +0 -247
  124. klaude_code/ui/repl/input.py +0 -773
  125. klaude_code/ui/rich_ext/__init__.py +0 -1
  126. klaude_code-1.2.1.dist-info/RECORD +0 -151
  127. /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
  128. /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
  129. /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
  130. /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
  131. /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
  132. /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
  133. /klaude_code/ui/{base → core}/stage_manager.py +0 -0
  134. /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
  135. /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
  136. /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
  137. /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
  138. /klaude_code/ui/{base → utils}/debouncer.py +0 -0
  139. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
  140. {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.llm_parameter import ToolSchema
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.const import (
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.llm_parameter import ToolSchema
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 READ_CHAR_LIMIT_PER_LINE, READ_GLOBAL_LINE_CAP, READ_MAX_CHARS, READ_MAX_KB
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", output="<tool_use_error>Illegal operation on a directory. read</tool_use_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(status="error", output="<tool_use_error>File does not exist.</tool_use_error>")
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(status="error", output="<tool_use_error>File does not exist.</tool_use_error>")
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", output="<tool_use_error>Illegal operation on a directory. read</tool_use_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.llm_parameter import ToolSchema
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)