klaude-code 1.2.6__py3-none-any.whl → 1.8.0__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/auth/__init__.py +24 -0
- klaude_code/auth/codex/__init__.py +20 -0
- klaude_code/auth/codex/exceptions.py +17 -0
- klaude_code/auth/codex/jwt_utils.py +45 -0
- klaude_code/auth/codex/oauth.py +229 -0
- klaude_code/auth/codex/token_manager.py +84 -0
- klaude_code/cli/auth_cmd.py +73 -0
- klaude_code/cli/config_cmd.py +91 -0
- klaude_code/cli/cost_cmd.py +338 -0
- klaude_code/cli/debug.py +78 -0
- klaude_code/cli/list_model.py +307 -0
- klaude_code/cli/main.py +233 -134
- klaude_code/cli/runtime.py +309 -117
- klaude_code/{version.py → cli/self_update.py} +114 -5
- klaude_code/cli/session_cmd.py +37 -21
- klaude_code/command/__init__.py +88 -27
- klaude_code/command/clear_cmd.py +8 -7
- klaude_code/command/command_abc.py +31 -31
- klaude_code/command/debug_cmd.py +79 -0
- klaude_code/command/export_cmd.py +19 -53
- klaude_code/command/export_online_cmd.py +154 -0
- klaude_code/command/fork_session_cmd.py +267 -0
- klaude_code/command/help_cmd.py +7 -8
- klaude_code/command/model_cmd.py +60 -10
- klaude_code/command/model_select.py +84 -0
- klaude_code/command/prompt-jj-describe.md +32 -0
- klaude_code/command/prompt_command.py +19 -11
- klaude_code/command/refresh_cmd.py +8 -10
- klaude_code/command/registry.py +139 -40
- klaude_code/command/release_notes_cmd.py +84 -0
- klaude_code/command/resume_cmd.py +111 -0
- klaude_code/command/status_cmd.py +104 -60
- klaude_code/command/terminal_setup_cmd.py +7 -9
- klaude_code/command/thinking_cmd.py +98 -0
- klaude_code/config/__init__.py +14 -6
- klaude_code/config/assets/__init__.py +1 -0
- klaude_code/config/assets/builtin_config.yaml +303 -0
- klaude_code/config/builtin_config.py +38 -0
- klaude_code/config/config.py +378 -109
- klaude_code/config/select_model.py +117 -53
- klaude_code/config/thinking.py +269 -0
- klaude_code/{const/__init__.py → const.py} +50 -19
- klaude_code/core/agent.py +20 -28
- klaude_code/core/executor.py +327 -112
- klaude_code/core/manager/__init__.py +2 -4
- klaude_code/core/manager/llm_clients.py +1 -15
- klaude_code/core/manager/llm_clients_builder.py +10 -11
- klaude_code/core/manager/sub_agent_manager.py +37 -6
- klaude_code/core/prompt.py +63 -44
- klaude_code/core/prompts/prompt-claude-code.md +2 -13
- klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
- klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
- klaude_code/core/prompts/prompt-codex.md +9 -42
- klaude_code/core/prompts/prompt-minimal.md +12 -0
- klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
- klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
- klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
- klaude_code/core/reminders.py +283 -95
- klaude_code/core/task.py +113 -75
- klaude_code/core/tool/__init__.py +24 -31
- klaude_code/core/tool/file/_utils.py +36 -0
- klaude_code/core/tool/file/apply_patch.py +17 -25
- klaude_code/core/tool/file/apply_patch_tool.py +57 -77
- klaude_code/core/tool/file/diff_builder.py +151 -0
- klaude_code/core/tool/file/edit_tool.py +50 -63
- klaude_code/core/tool/file/move_tool.md +41 -0
- klaude_code/core/tool/file/move_tool.py +435 -0
- klaude_code/core/tool/file/read_tool.md +1 -1
- klaude_code/core/tool/file/read_tool.py +86 -86
- klaude_code/core/tool/file/write_tool.py +59 -69
- klaude_code/core/tool/report_back_tool.py +84 -0
- klaude_code/core/tool/shell/bash_tool.py +265 -22
- klaude_code/core/tool/shell/command_safety.py +3 -6
- klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
- klaude_code/core/tool/sub_agent_tool.py +13 -2
- klaude_code/core/tool/todo/todo_write_tool.md +0 -157
- klaude_code/core/tool/todo/todo_write_tool.py +1 -1
- klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
- klaude_code/core/tool/todo/update_plan_tool.py +1 -1
- klaude_code/core/tool/tool_abc.py +18 -0
- klaude_code/core/tool/tool_context.py +27 -12
- klaude_code/core/tool/tool_registry.py +7 -7
- klaude_code/core/tool/tool_runner.py +44 -36
- klaude_code/core/tool/truncation.py +29 -14
- klaude_code/core/tool/web/mermaid_tool.md +43 -0
- klaude_code/core/tool/web/mermaid_tool.py +2 -5
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +112 -22
- klaude_code/core/tool/web/web_search_tool.md +23 -0
- klaude_code/core/tool/web/web_search_tool.py +130 -0
- klaude_code/core/turn.py +168 -66
- klaude_code/llm/__init__.py +2 -10
- klaude_code/llm/anthropic/client.py +190 -178
- klaude_code/llm/anthropic/input.py +39 -15
- klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code/llm/bedrock/client.py +60 -0
- klaude_code/llm/client.py +7 -21
- klaude_code/llm/codex/__init__.py +5 -0
- klaude_code/llm/codex/client.py +149 -0
- klaude_code/llm/google/__init__.py +3 -0
- klaude_code/llm/google/client.py +309 -0
- klaude_code/llm/google/input.py +215 -0
- klaude_code/llm/input_common.py +3 -9
- klaude_code/llm/openai_compatible/client.py +72 -164
- klaude_code/llm/openai_compatible/input.py +6 -4
- klaude_code/llm/openai_compatible/stream.py +273 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- klaude_code/llm/openrouter/client.py +89 -160
- klaude_code/llm/openrouter/input.py +18 -30
- klaude_code/llm/openrouter/reasoning.py +118 -0
- klaude_code/llm/registry.py +39 -7
- klaude_code/llm/responses/client.py +184 -171
- klaude_code/llm/responses/input.py +20 -1
- klaude_code/llm/usage.py +17 -12
- klaude_code/protocol/commands.py +17 -1
- klaude_code/protocol/events.py +31 -4
- klaude_code/protocol/llm_param.py +13 -10
- klaude_code/protocol/model.py +232 -29
- klaude_code/protocol/op.py +90 -1
- klaude_code/protocol/op_handler.py +35 -1
- klaude_code/protocol/sub_agent/__init__.py +117 -0
- klaude_code/protocol/sub_agent/explore.py +63 -0
- klaude_code/protocol/sub_agent/oracle.py +91 -0
- klaude_code/protocol/sub_agent/task.py +61 -0
- klaude_code/protocol/sub_agent/web.py +79 -0
- klaude_code/protocol/tools.py +4 -2
- klaude_code/session/__init__.py +2 -2
- klaude_code/session/codec.py +71 -0
- klaude_code/session/export.py +293 -86
- klaude_code/session/selector.py +89 -67
- klaude_code/session/session.py +320 -309
- klaude_code/session/store.py +220 -0
- klaude_code/session/templates/export_session.html +595 -83
- klaude_code/session/templates/mermaid_viewer.html +926 -0
- klaude_code/skill/__init__.py +27 -0
- klaude_code/skill/assets/deslop/SKILL.md +17 -0
- klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
- klaude_code/skill/assets/handoff/SKILL.md +39 -0
- klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
- klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
- klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
- klaude_code/skill/manager.py +70 -0
- klaude_code/skill/system_skills.py +192 -0
- klaude_code/trace/__init__.py +20 -2
- klaude_code/trace/log.py +150 -5
- klaude_code/ui/__init__.py +4 -9
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +7 -7
- klaude_code/ui/modes/debug/display.py +2 -1
- klaude_code/ui/modes/repl/__init__.py +3 -48
- klaude_code/ui/modes/repl/clipboard.py +5 -5
- klaude_code/ui/modes/repl/completers.py +487 -123
- klaude_code/ui/modes/repl/display.py +5 -4
- klaude_code/ui/modes/repl/event_handler.py +370 -117
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
- klaude_code/ui/modes/repl/key_bindings.py +146 -23
- klaude_code/ui/modes/repl/renderer.py +189 -99
- klaude_code/ui/renderers/assistant.py +9 -2
- klaude_code/ui/renderers/bash_syntax.py +178 -0
- klaude_code/ui/renderers/common.py +78 -0
- klaude_code/ui/renderers/developer.py +104 -48
- klaude_code/ui/renderers/diffs.py +87 -6
- klaude_code/ui/renderers/errors.py +11 -6
- klaude_code/ui/renderers/mermaid_viewer.py +57 -0
- klaude_code/ui/renderers/metadata.py +112 -76
- klaude_code/ui/renderers/sub_agent.py +92 -7
- klaude_code/ui/renderers/thinking.py +40 -18
- klaude_code/ui/renderers/tools.py +405 -227
- klaude_code/ui/renderers/user_input.py +73 -13
- klaude_code/ui/rich/__init__.py +10 -1
- klaude_code/ui/rich/cjk_wrap.py +228 -0
- klaude_code/ui/rich/code_panel.py +131 -0
- klaude_code/ui/rich/live.py +17 -0
- klaude_code/ui/rich/markdown.py +305 -170
- klaude_code/ui/rich/searchable_text.py +10 -13
- klaude_code/ui/rich/status.py +190 -49
- klaude_code/ui/rich/theme.py +135 -39
- klaude_code/ui/terminal/__init__.py +55 -0
- klaude_code/ui/terminal/color.py +1 -1
- klaude_code/ui/terminal/control.py +13 -22
- klaude_code/ui/terminal/notifier.py +44 -4
- klaude_code/ui/terminal/selector.py +658 -0
- klaude_code/ui/utils/common.py +0 -18
- klaude_code-1.8.0.dist-info/METADATA +377 -0
- klaude_code-1.8.0.dist-info/RECORD +219 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
- klaude_code/command/diff_cmd.py +0 -138
- klaude_code/command/prompt-dev-docs-update.md +0 -56
- klaude_code/command/prompt-dev-docs.md +0 -46
- klaude_code/config/list_model.py +0 -162
- klaude_code/core/manager/agent_manager.py +0 -127
- klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
- klaude_code/core/tool/file/multi_edit_tool.md +0 -42
- klaude_code/core/tool/file/multi_edit_tool.py +0 -199
- klaude_code/core/tool/memory/memory_tool.md +0 -16
- klaude_code/core/tool/memory/memory_tool.py +0 -462
- klaude_code/llm/openrouter/reasoning_handler.py +0 -209
- klaude_code/protocol/sub_agent.py +0 -348
- klaude_code/ui/utils/debouncer.py +0 -42
- klaude_code-1.2.6.dist-info/METADATA +0 -178
- klaude_code-1.2.6.dist-info/RECORD +0 -167
- /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
- /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
- /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from typing import Any, cast
|
|
3
4
|
|
|
4
|
-
from rich
|
|
5
|
+
from rich import box
|
|
6
|
+
from rich.console import Group, RenderableType
|
|
5
7
|
from rich.padding import Padding
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.style import Style
|
|
6
10
|
from rich.text import Text
|
|
7
11
|
|
|
8
12
|
from klaude_code import const
|
|
9
|
-
from klaude_code.protocol import events, model
|
|
13
|
+
from klaude_code.protocol import events, model, tools
|
|
10
14
|
from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
|
|
11
15
|
from klaude_code.ui.renderers import diffs as r_diffs
|
|
12
|
-
from klaude_code.ui.renderers
|
|
16
|
+
from klaude_code.ui.renderers import mermaid_viewer as r_mermaid_viewer
|
|
17
|
+
from klaude_code.ui.renderers.bash_syntax import highlight_bash_command
|
|
18
|
+
from klaude_code.ui.renderers.common import create_grid, truncate_display
|
|
19
|
+
from klaude_code.ui.rich.code_panel import CodePanel
|
|
20
|
+
from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
13
21
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
14
|
-
|
|
22
|
+
|
|
23
|
+
# Tool markers (Unicode symbols for UI display)
|
|
24
|
+
MARK_GENERIC = "⚒"
|
|
25
|
+
MARK_BASH = "$"
|
|
26
|
+
MARK_PLAN = "◈"
|
|
27
|
+
MARK_READ = "→"
|
|
28
|
+
MARK_EDIT = "±"
|
|
29
|
+
MARK_WRITE = "+"
|
|
30
|
+
MARK_MOVE = "±"
|
|
31
|
+
MARK_MERMAID = "⧉"
|
|
32
|
+
MARK_WEB_FETCH = "→"
|
|
33
|
+
MARK_WEB_SEARCH = "✱"
|
|
34
|
+
MARK_DONE = "✔"
|
|
35
|
+
MARK_SKILL = "✪"
|
|
36
|
+
|
|
37
|
+
# Todo status markers
|
|
38
|
+
MARK_TODO_PENDING = "▢"
|
|
39
|
+
MARK_TODO_IN_PROGRESS = "◉"
|
|
40
|
+
MARK_TODO_COMPLETED = "✔"
|
|
15
41
|
|
|
16
42
|
|
|
17
43
|
def is_sub_agent_tool(tool_name: str) -> bool:
|
|
@@ -30,7 +56,7 @@ def render_path(path: str, style: str, is_directory: bool = False) -> Text:
|
|
|
30
56
|
return Text(path, style=style)
|
|
31
57
|
|
|
32
58
|
|
|
33
|
-
def render_generic_tool_call(tool_name: str, arguments: str, markup: str =
|
|
59
|
+
def render_generic_tool_call(tool_name: str, arguments: str, markup: str = MARK_GENERIC) -> RenderableType:
|
|
34
60
|
grid = create_grid()
|
|
35
61
|
|
|
36
62
|
tool_name_column = Text.assemble((markup, ThemeKey.TOOL_MARK), " ", (tool_name, ThemeKey.TOOL_NAME))
|
|
@@ -58,9 +84,73 @@ def render_generic_tool_call(tool_name: str, arguments: str, markup: str = "•"
|
|
|
58
84
|
return grid
|
|
59
85
|
|
|
60
86
|
|
|
87
|
+
def render_bash_tool_call(arguments: str) -> RenderableType:
|
|
88
|
+
grid = create_grid()
|
|
89
|
+
tool_name_column = Text.assemble((MARK_BASH, ThemeKey.TOOL_MARK), " ", ("Bash", ThemeKey.TOOL_NAME))
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
payload_raw: Any = json.loads(arguments) if arguments else {}
|
|
93
|
+
except json.JSONDecodeError:
|
|
94
|
+
summary = Text(
|
|
95
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
96
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
97
|
+
)
|
|
98
|
+
grid.add_row(tool_name_column, summary)
|
|
99
|
+
return grid
|
|
100
|
+
|
|
101
|
+
if not isinstance(payload_raw, dict):
|
|
102
|
+
summary = Text(
|
|
103
|
+
str(payload_raw)[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
104
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
105
|
+
)
|
|
106
|
+
grid.add_row(tool_name_column, summary)
|
|
107
|
+
return grid
|
|
108
|
+
|
|
109
|
+
payload: dict[str, object] = cast(dict[str, object], payload_raw)
|
|
110
|
+
|
|
111
|
+
command = payload.get("command")
|
|
112
|
+
timeout_ms = payload.get("timeout_ms")
|
|
113
|
+
|
|
114
|
+
# Build the command display with optional timeout suffix
|
|
115
|
+
if isinstance(command, str) and command.strip():
|
|
116
|
+
cmd_str = command.strip()
|
|
117
|
+
line_count = len(cmd_str.splitlines())
|
|
118
|
+
|
|
119
|
+
highlighted = highlight_bash_command(cmd_str)
|
|
120
|
+
|
|
121
|
+
# For commands > 10 lines, use CodePanel for better display
|
|
122
|
+
if line_count > 10:
|
|
123
|
+
code_panel = CodePanel(highlighted, border_style=ThemeKey.LINES)
|
|
124
|
+
if isinstance(timeout_ms, int):
|
|
125
|
+
if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
|
|
126
|
+
timeout_text = Text(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
127
|
+
else:
|
|
128
|
+
timeout_text = Text(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
|
|
129
|
+
grid.add_row(tool_name_column, Group(code_panel, timeout_text))
|
|
130
|
+
else:
|
|
131
|
+
grid.add_row(tool_name_column, code_panel)
|
|
132
|
+
return grid
|
|
133
|
+
if isinstance(timeout_ms, int):
|
|
134
|
+
if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
|
|
135
|
+
highlighted.append(f" {timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
136
|
+
else:
|
|
137
|
+
highlighted.append(f" {timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
|
|
138
|
+
grid.add_row(tool_name_column, highlighted)
|
|
139
|
+
else:
|
|
140
|
+
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
141
|
+
if isinstance(timeout_ms, int):
|
|
142
|
+
if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
|
|
143
|
+
summary.append(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
144
|
+
else:
|
|
145
|
+
summary.append(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
|
|
146
|
+
grid.add_row(tool_name_column, summary)
|
|
147
|
+
|
|
148
|
+
return grid
|
|
149
|
+
|
|
150
|
+
|
|
61
151
|
def render_update_plan_tool_call(arguments: str) -> RenderableType:
|
|
62
152
|
grid = create_grid()
|
|
63
|
-
tool_name_column = Text.assemble((
|
|
153
|
+
tool_name_column = Text.assemble((MARK_PLAN, ThemeKey.TOOL_MARK), " ", ("Update Plan", ThemeKey.TOOL_NAME))
|
|
64
154
|
explanation_column = Text("")
|
|
65
155
|
|
|
66
156
|
if arguments:
|
|
@@ -117,154 +207,157 @@ def render_read_tool_call(arguments: str) -> RenderableType:
|
|
|
117
207
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
118
208
|
)
|
|
119
209
|
)
|
|
120
|
-
grid.add_row(Text(
|
|
210
|
+
grid.add_row(Text(MARK_READ, ThemeKey.TOOL_MARK), render_result)
|
|
121
211
|
return grid
|
|
122
212
|
|
|
123
213
|
|
|
124
|
-
def render_edit_tool_call(arguments: str) ->
|
|
125
|
-
|
|
214
|
+
def render_edit_tool_call(arguments: str) -> RenderableType:
|
|
215
|
+
grid = create_grid()
|
|
216
|
+
tool_name_column = Text.assemble((MARK_EDIT, ThemeKey.TOOL_MARK), " ", ("Edit", ThemeKey.TOOL_NAME))
|
|
126
217
|
try:
|
|
127
218
|
json_dict = json.loads(arguments)
|
|
128
219
|
file_path = json_dict.get("file_path")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.
|
|
133
|
-
|
|
220
|
+
replace_all = json_dict.get("replace_all", False)
|
|
221
|
+
path_text = render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
222
|
+
if replace_all:
|
|
223
|
+
old_string = json_dict.get("old_string", "")
|
|
224
|
+
new_string = json_dict.get("new_string", "")
|
|
225
|
+
replace_info = Text("Replacing all ", ThemeKey.TOOL_RESULT_TRUNCATED)
|
|
226
|
+
replace_info.append(old_string, ThemeKey.BASH_STRING)
|
|
227
|
+
replace_info.append(" → ", ThemeKey.BASH_OPERATOR)
|
|
228
|
+
replace_info.append(new_string, ThemeKey.BASH_STRING)
|
|
229
|
+
arguments_column: RenderableType = Group(path_text, replace_info)
|
|
230
|
+
else:
|
|
231
|
+
arguments_column = path_text
|
|
134
232
|
except json.JSONDecodeError:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
.append_text(
|
|
139
|
-
Text(
|
|
140
|
-
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
141
|
-
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
142
|
-
)
|
|
143
|
-
)
|
|
233
|
+
arguments_column = Text(
|
|
234
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
235
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
144
236
|
)
|
|
145
|
-
|
|
237
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
238
|
+
return grid
|
|
146
239
|
|
|
147
240
|
|
|
148
|
-
def render_write_tool_call(arguments: str) ->
|
|
149
|
-
|
|
241
|
+
def render_write_tool_call(arguments: str) -> RenderableType:
|
|
242
|
+
grid = create_grid()
|
|
150
243
|
try:
|
|
151
244
|
json_dict = json.loads(arguments)
|
|
152
|
-
file_path = json_dict.get("file_path")
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
op_label = "Overwrite"
|
|
160
|
-
render_result = (
|
|
161
|
-
render_result.append_text(Text(op_label, ThemeKey.TOOL_NAME))
|
|
162
|
-
.append_text(Text(" "))
|
|
163
|
-
.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
164
|
-
)
|
|
245
|
+
file_path = json_dict.get("file_path", "")
|
|
246
|
+
tool_name_column = Text.assemble((MARK_WRITE, ThemeKey.TOOL_MARK), " ", ("Write", ThemeKey.TOOL_NAME))
|
|
247
|
+
# Markdown files show path in result panel, skip here to avoid duplication
|
|
248
|
+
if file_path.endswith(".md"):
|
|
249
|
+
arguments_column = Text("")
|
|
250
|
+
else:
|
|
251
|
+
arguments_column = render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
165
252
|
except json.JSONDecodeError:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
.
|
|
170
|
-
Text(
|
|
171
|
-
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
172
|
-
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
173
|
-
)
|
|
174
|
-
)
|
|
253
|
+
tool_name_column = Text.assemble((MARK_WRITE, ThemeKey.TOOL_MARK), " ", ("Write", ThemeKey.TOOL_NAME))
|
|
254
|
+
arguments_column = Text(
|
|
255
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
256
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
175
257
|
)
|
|
176
|
-
|
|
258
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
259
|
+
return grid
|
|
260
|
+
|
|
177
261
|
|
|
262
|
+
def render_move_tool_call(arguments: str) -> RenderableType:
|
|
263
|
+
grid = create_grid()
|
|
264
|
+
tool_name_column = Text.assemble((MARK_MOVE, ThemeKey.TOOL_MARK), " ", ("Move", ThemeKey.TOOL_NAME))
|
|
178
265
|
|
|
179
|
-
def render_multi_edit_tool_call(arguments: str) -> Text:
|
|
180
|
-
render_result: Text = Text.assemble(("→ ", ThemeKey.TOOL_MARK), ("MultiEdit", ThemeKey.TOOL_NAME), " ")
|
|
181
266
|
try:
|
|
182
|
-
|
|
183
|
-
file_path = json_dict.get("file_path")
|
|
184
|
-
edits = json_dict.get("edits", [])
|
|
185
|
-
render_result = (
|
|
186
|
-
render_result.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
187
|
-
.append_text(Text(" - "))
|
|
188
|
-
.append_text(Text(f"{len(edits)}", ThemeKey.TOOL_PARAM_BOLD))
|
|
189
|
-
.append_text(Text(" updates", ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
190
|
-
)
|
|
267
|
+
payload = json.loads(arguments)
|
|
191
268
|
except json.JSONDecodeError:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
196
|
-
)
|
|
269
|
+
arguments_column = Text(
|
|
270
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
271
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
197
272
|
)
|
|
198
|
-
|
|
273
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
274
|
+
return grid
|
|
275
|
+
|
|
276
|
+
source_path = payload.get("source_file_path", "")
|
|
277
|
+
target_path = payload.get("target_file_path", "")
|
|
278
|
+
start_line = payload.get("start_line", "")
|
|
279
|
+
end_line = payload.get("end_line", "")
|
|
280
|
+
|
|
281
|
+
# Build display: source:start-end -> target
|
|
282
|
+
parts = Text()
|
|
283
|
+
if source_path:
|
|
284
|
+
parts.append_text(render_path(source_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
285
|
+
if start_line and end_line:
|
|
286
|
+
parts.append(f":{start_line}-{end_line}", style=ThemeKey.TOOL_PARAM)
|
|
287
|
+
parts.append(" -> ", style=ThemeKey.TOOL_PARAM)
|
|
288
|
+
if target_path:
|
|
289
|
+
parts.append_text(render_path(target_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
290
|
+
|
|
291
|
+
grid.add_row(tool_name_column, parts)
|
|
292
|
+
return grid
|
|
199
293
|
|
|
200
294
|
|
|
201
295
|
def render_apply_patch_tool_call(arguments: str) -> RenderableType:
|
|
296
|
+
grid = create_grid()
|
|
297
|
+
tool_name_column = Text.assemble((MARK_EDIT, ThemeKey.TOOL_MARK), " ", ("Apply Patch", ThemeKey.TOOL_NAME))
|
|
298
|
+
|
|
202
299
|
try:
|
|
203
300
|
payload = json.loads(arguments)
|
|
204
301
|
except json.JSONDecodeError:
|
|
205
|
-
|
|
206
|
-
(
|
|
207
|
-
|
|
208
|
-
" ",
|
|
209
|
-
Text(
|
|
210
|
-
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
211
|
-
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
212
|
-
),
|
|
302
|
+
arguments_column = Text(
|
|
303
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
304
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
213
305
|
)
|
|
306
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
307
|
+
return grid
|
|
214
308
|
|
|
215
309
|
patch_content = payload.get("patch", "")
|
|
216
|
-
|
|
217
|
-
grid = create_grid()
|
|
218
|
-
header = Text.assemble(("→ ", ThemeKey.TOOL_MARK), ("Apply Patch", ThemeKey.TOOL_NAME))
|
|
219
|
-
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
310
|
+
arguments_column = Text("", ThemeKey.TOOL_PARAM)
|
|
220
311
|
|
|
221
312
|
if isinstance(patch_content, str):
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
313
|
+
update_count = 0
|
|
314
|
+
add_count = 0
|
|
315
|
+
delete_count = 0
|
|
316
|
+
for line in patch_content.splitlines():
|
|
317
|
+
if line.startswith("*** Update File:"):
|
|
318
|
+
update_count += 1
|
|
319
|
+
elif line.startswith("*** Add File:"):
|
|
320
|
+
add_count += 1
|
|
321
|
+
elif line.startswith("*** Delete File:"):
|
|
322
|
+
delete_count += 1
|
|
323
|
+
|
|
324
|
+
parts: list[str] = []
|
|
325
|
+
if update_count > 0:
|
|
326
|
+
parts.append(f"Update File × {update_count}" if update_count > 1 else "Update File")
|
|
327
|
+
if add_count > 0:
|
|
328
|
+
parts.append(f"Add File × {add_count}" if add_count > 1 else "Add File")
|
|
329
|
+
if delete_count > 0:
|
|
330
|
+
parts.append(f"Delete File × {delete_count}" if delete_count > 1 else "Delete File")
|
|
331
|
+
|
|
332
|
+
if parts:
|
|
333
|
+
arguments_column = Text(", ".join(parts), ThemeKey.TOOL_PARAM)
|
|
225
334
|
else:
|
|
226
|
-
|
|
335
|
+
arguments_column = Text(
|
|
227
336
|
str(patch_content)[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
228
337
|
ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
229
338
|
)
|
|
230
339
|
|
|
231
|
-
|
|
232
|
-
grid.add_row(header, summary)
|
|
233
|
-
else:
|
|
234
|
-
grid.add_row(header, Text("", ThemeKey.TOOL_PARAM))
|
|
235
|
-
|
|
340
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
236
341
|
return grid
|
|
237
342
|
|
|
238
343
|
|
|
239
344
|
def render_todo(tr: events.ToolResultEvent) -> RenderableType:
|
|
240
|
-
|
|
241
|
-
return Text.assemble(
|
|
242
|
-
(" ✘", ThemeKey.ERROR_BOLD),
|
|
243
|
-
" ",
|
|
244
|
-
Text("(no content)", style=ThemeKey.ERROR),
|
|
245
|
-
)
|
|
246
|
-
if tr.ui_extra.type != model.ToolResultUIExtraType.TODO_LIST or tr.ui_extra.todo_list is None:
|
|
247
|
-
return Text.assemble(
|
|
248
|
-
(" ✘", ThemeKey.ERROR_BOLD),
|
|
249
|
-
" ",
|
|
250
|
-
Text("(invalid ui_extra)", style=ThemeKey.ERROR),
|
|
251
|
-
)
|
|
252
|
-
|
|
345
|
+
assert isinstance(tr.ui_extra, model.TodoListUIExtra)
|
|
253
346
|
ui_extra = tr.ui_extra.todo_list
|
|
254
347
|
todo_grid = create_grid()
|
|
255
348
|
for todo in ui_extra.todos:
|
|
256
349
|
is_new_completed = todo.content in ui_extra.new_completed
|
|
257
350
|
match todo.status:
|
|
258
351
|
case "pending":
|
|
259
|
-
mark =
|
|
352
|
+
mark = MARK_TODO_PENDING
|
|
260
353
|
mark_style = ThemeKey.TODO_PENDING_MARK
|
|
261
354
|
text_style = ThemeKey.TODO_PENDING
|
|
262
355
|
case "in_progress":
|
|
263
|
-
mark =
|
|
356
|
+
mark = MARK_TODO_IN_PROGRESS
|
|
264
357
|
mark_style = ThemeKey.TODO_IN_PROGRESS_MARK
|
|
265
358
|
text_style = ThemeKey.TODO_IN_PROGRESS
|
|
266
359
|
case "completed":
|
|
267
|
-
mark =
|
|
360
|
+
mark = MARK_TODO_COMPLETED
|
|
268
361
|
mark_style = ThemeKey.TODO_NEW_COMPLETED_MARK if is_new_completed else ThemeKey.TODO_COMPLETED_MARK
|
|
269
362
|
text_style = ThemeKey.TODO_NEW_COMPLETED if is_new_completed else ThemeKey.TODO_COMPLETED
|
|
270
363
|
text = Text(todo.content)
|
|
@@ -277,128 +370,168 @@ def render_todo(tr: events.ToolResultEvent) -> RenderableType:
|
|
|
277
370
|
def render_generic_tool_result(result: str, *, is_error: bool = False) -> RenderableType:
|
|
278
371
|
"""Render a generic tool result as indented, truncated text."""
|
|
279
372
|
style = ThemeKey.ERROR if is_error else ThemeKey.TOOL_RESULT
|
|
280
|
-
return Padding.indent(
|
|
373
|
+
return Padding.indent(truncate_display(result, base_style=style), level=2)
|
|
281
374
|
|
|
282
375
|
|
|
283
376
|
def _extract_mermaid_link(
|
|
284
377
|
ui_extra: model.ToolResultUIExtra | None,
|
|
285
378
|
) -> model.MermaidLinkUIExtra | None:
|
|
286
|
-
if ui_extra
|
|
287
|
-
return
|
|
288
|
-
|
|
289
|
-
return None
|
|
290
|
-
return ui_extra.mermaid_link
|
|
379
|
+
if isinstance(ui_extra, model.MermaidLinkUIExtra):
|
|
380
|
+
return ui_extra
|
|
381
|
+
return None
|
|
291
382
|
|
|
292
383
|
|
|
293
|
-
def
|
|
384
|
+
def render_mermaid_tool_call(arguments: str) -> RenderableType:
|
|
294
385
|
grid = create_grid()
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
"create": "Create",
|
|
298
|
-
"str_replace": "Replace",
|
|
299
|
-
"insert": "Insert",
|
|
300
|
-
"delete": "Delete",
|
|
301
|
-
"rename": "Rename",
|
|
302
|
-
}
|
|
386
|
+
tool_name_column = Text.assemble((MARK_MERMAID, ThemeKey.TOOL_MARK), " ", ("Mermaid", ThemeKey.TOOL_NAME))
|
|
387
|
+
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
303
388
|
|
|
304
389
|
try:
|
|
305
390
|
payload: dict[str, str] = json.loads(arguments)
|
|
306
391
|
except json.JSONDecodeError:
|
|
307
|
-
tool_name_column = Text.assemble(("★", ThemeKey.TOOL_MARK), " ", ("Memory", ThemeKey.TOOL_NAME))
|
|
308
392
|
summary = Text(
|
|
309
393
|
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
310
394
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
311
395
|
)
|
|
312
|
-
|
|
313
|
-
|
|
396
|
+
else:
|
|
397
|
+
code = payload.get("code", "")
|
|
398
|
+
if code:
|
|
399
|
+
line_count = len(code.splitlines())
|
|
400
|
+
summary = Text(f"{line_count} lines", ThemeKey.TOOL_PARAM)
|
|
401
|
+
else:
|
|
402
|
+
summary = Text("0 lines", ThemeKey.TOOL_PARAM)
|
|
314
403
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
tool_name_column = Text.assemble(("★", ThemeKey.TOOL_MARK), " ", (f"{display_name} Memory", ThemeKey.TOOL_NAME))
|
|
404
|
+
grid.add_row(tool_name_column, summary)
|
|
405
|
+
return grid
|
|
318
406
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
407
|
+
|
|
408
|
+
def _truncate_url(url: str, max_length: int = 400) -> str:
|
|
409
|
+
"""Truncate URL for display, preserving domain and path structure."""
|
|
410
|
+
if len(url) <= max_length:
|
|
411
|
+
return url
|
|
412
|
+
# Remove protocol for display
|
|
413
|
+
display_url = url
|
|
414
|
+
for prefix in ("https://", "http://"):
|
|
415
|
+
if display_url.startswith(prefix):
|
|
416
|
+
display_url = display_url[len(prefix) :]
|
|
417
|
+
break
|
|
418
|
+
if len(display_url) <= max_length:
|
|
419
|
+
return display_url
|
|
420
|
+
# Truncate with ellipsis
|
|
421
|
+
return display_url[: max_length - 3] + "..."
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _render_mermaid_viewer_link(
|
|
425
|
+
tr: events.ToolResultEvent,
|
|
426
|
+
link_info: model.MermaidLinkUIExtra,
|
|
427
|
+
*,
|
|
428
|
+
use_osc8: bool,
|
|
429
|
+
) -> RenderableType:
|
|
430
|
+
viewer_path = r_mermaid_viewer.build_viewer(code=link_info.code, link=link_info.link, tool_call_id=tr.tool_call_id)
|
|
431
|
+
if viewer_path is None:
|
|
432
|
+
return Text(link_info.link, style=ThemeKey.TOOL_RESULT_MERMAID, overflow="fold")
|
|
433
|
+
|
|
434
|
+
display_path = str(viewer_path)
|
|
435
|
+
|
|
436
|
+
file_url = ""
|
|
437
|
+
if use_osc8:
|
|
438
|
+
try:
|
|
439
|
+
file_url = viewer_path.resolve().as_uri()
|
|
440
|
+
except ValueError:
|
|
441
|
+
file_url = f"file://{viewer_path.as_posix()}"
|
|
442
|
+
|
|
443
|
+
rendered = Text.assemble(("saved in:", ThemeKey.TOOL_RESULT), " ")
|
|
444
|
+
start = len(rendered)
|
|
445
|
+
rendered.append(display_path, ThemeKey.TOOL_RESULT_MERMAID)
|
|
446
|
+
end = len(rendered)
|
|
447
|
+
|
|
448
|
+
if use_osc8 and file_url:
|
|
449
|
+
rendered.stylize(Style(link=file_url), start, end)
|
|
450
|
+
|
|
451
|
+
return rendered
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def render_web_fetch_tool_call(arguments: str) -> RenderableType:
|
|
455
|
+
grid = create_grid()
|
|
456
|
+
tool_name_column = Text.assemble((MARK_WEB_FETCH, ThemeKey.TOOL_MARK), " ", ("Fetch", ThemeKey.TOOL_NAME))
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
payload: dict[str, str] = json.loads(arguments)
|
|
460
|
+
except json.JSONDecodeError:
|
|
461
|
+
summary = Text(
|
|
462
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
463
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
329
464
|
)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
elif command == "view" and path:
|
|
336
|
-
view_range = payload.get("view_range")
|
|
337
|
-
summary = Text(path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
338
|
-
if view_range and isinstance(view_range, list) and len(view_range) >= 2:
|
|
339
|
-
summary.append(f" {view_range[0]}:{view_range[1]}", ThemeKey.TOOL_PARAM)
|
|
340
|
-
elif path:
|
|
341
|
-
summary = Text(path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
465
|
+
grid.add_row(tool_name_column, summary)
|
|
466
|
+
return grid
|
|
467
|
+
|
|
468
|
+
url = payload.get("url", "")
|
|
469
|
+
summary = Text(_truncate_url(url), ThemeKey.TOOL_PARAM_FILE_PATH) if url else Text("(no url)", ThemeKey.TOOL_PARAM)
|
|
342
470
|
|
|
343
471
|
grid.add_row(tool_name_column, summary)
|
|
344
472
|
return grid
|
|
345
473
|
|
|
346
474
|
|
|
347
|
-
def
|
|
475
|
+
def render_web_search_tool_call(arguments: str) -> RenderableType:
|
|
348
476
|
grid = create_grid()
|
|
349
|
-
tool_name_column = Text.assemble((
|
|
350
|
-
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
477
|
+
tool_name_column = Text.assemble((MARK_WEB_SEARCH, ThemeKey.TOOL_MARK), " ", ("Web Search", ThemeKey.TOOL_NAME))
|
|
351
478
|
|
|
352
479
|
try:
|
|
353
|
-
payload: dict[str,
|
|
480
|
+
payload: dict[str, Any] = json.loads(arguments)
|
|
354
481
|
except json.JSONDecodeError:
|
|
355
482
|
summary = Text(
|
|
356
483
|
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
357
484
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
358
485
|
)
|
|
486
|
+
grid.add_row(tool_name_column, summary)
|
|
487
|
+
return grid
|
|
488
|
+
|
|
489
|
+
query = payload.get("query", "")
|
|
490
|
+
max_results = payload.get("max_results")
|
|
491
|
+
|
|
492
|
+
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
493
|
+
if query:
|
|
494
|
+
# Truncate long queries
|
|
495
|
+
display_query = query if len(query) <= 80 else query[:77] + "..."
|
|
496
|
+
summary.append(display_query, ThemeKey.TOOL_PARAM)
|
|
359
497
|
else:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
else:
|
|
365
|
-
summary = Text("0 lines", ThemeKey.TOOL_PARAM)
|
|
498
|
+
summary.append("(no query)", ThemeKey.TOOL_PARAM)
|
|
499
|
+
|
|
500
|
+
if isinstance(max_results, int) and max_results != 10:
|
|
501
|
+
summary.append(f" (max {max_results})", ThemeKey.TOOL_TIMEOUT)
|
|
366
502
|
|
|
367
503
|
grid.add_row(tool_name_column, summary)
|
|
368
504
|
return grid
|
|
369
505
|
|
|
370
506
|
|
|
371
507
|
def render_mermaid_tool_result(tr: events.ToolResultEvent) -> RenderableType:
|
|
508
|
+
from klaude_code.ui.terminal import supports_osc8_hyperlinks
|
|
509
|
+
|
|
372
510
|
link_info = _extract_mermaid_link(tr.ui_extra)
|
|
373
511
|
if link_info is None:
|
|
374
512
|
return render_generic_tool_result(tr.result, is_error=tr.status == "error")
|
|
375
513
|
|
|
376
|
-
|
|
377
|
-
|
|
514
|
+
use_osc8 = supports_osc8_hyperlinks()
|
|
515
|
+
viewer = _render_mermaid_viewer_link(tr, link_info, use_osc8=use_osc8)
|
|
516
|
+
return Padding.indent(viewer, level=2)
|
|
378
517
|
|
|
379
518
|
|
|
380
519
|
def _extract_truncation(
|
|
381
520
|
ui_extra: model.ToolResultUIExtra | None,
|
|
382
521
|
) -> model.TruncationUIExtra | None:
|
|
383
|
-
if ui_extra
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
return None
|
|
387
|
-
return ui_extra.truncation
|
|
522
|
+
if isinstance(ui_extra, model.TruncationUIExtra):
|
|
523
|
+
return ui_extra
|
|
524
|
+
return None
|
|
388
525
|
|
|
389
526
|
|
|
390
527
|
def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
|
|
391
528
|
"""Render truncation info for the user."""
|
|
392
|
-
original_kb = ui_extra.original_length / 1024
|
|
393
529
|
truncated_kb = ui_extra.truncated_length / 1024
|
|
530
|
+
|
|
394
531
|
text = Text.assemble(
|
|
395
|
-
("
|
|
396
|
-
(
|
|
397
|
-
("
|
|
398
|
-
(f"{truncated_kb:.1f}KB", ThemeKey.TOOL_RESULT_BOLD),
|
|
399
|
-
(" hidden\nFull output saved to ", ThemeKey.TOOL_RESULT),
|
|
400
|
-
(ui_extra.saved_file_path, ThemeKey.TOOL_RESULT),
|
|
401
|
-
("\nUse Read with limit+offset or rg/grep to inspect", ThemeKey.TOOL_RESULT),
|
|
532
|
+
("Offload context to ", ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
533
|
+
(ui_extra.saved_file_path, ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
534
|
+
(f", {truncated_kb:.1f}KB truncated", ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
402
535
|
)
|
|
403
536
|
return Padding.indent(text, level=2)
|
|
404
537
|
|
|
@@ -408,35 +541,28 @@ def get_truncation_info(tr: events.ToolResultEvent) -> model.TruncationUIExtra |
|
|
|
408
541
|
return _extract_truncation(tr.ui_extra)
|
|
409
542
|
|
|
410
543
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
"
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
"Bash": ">",
|
|
418
|
-
"apply_patch": "→",
|
|
419
|
-
"TodoWrite": "◎",
|
|
420
|
-
"update_plan": "◎",
|
|
421
|
-
"Mermaid": "⧉",
|
|
422
|
-
"Memory": "★",
|
|
423
|
-
"Skill": "◈",
|
|
424
|
-
}
|
|
544
|
+
def render_report_back_tool_call() -> RenderableType:
|
|
545
|
+
grid = create_grid()
|
|
546
|
+
tool_name_column = Text.assemble((MARK_DONE, ThemeKey.TOOL_MARK), " ", ("Report Back", ThemeKey.TOOL_NAME))
|
|
547
|
+
grid.add_row(tool_name_column, "")
|
|
548
|
+
return grid
|
|
549
|
+
|
|
425
550
|
|
|
426
551
|
# Tool name to active form mapping (for spinner status)
|
|
427
552
|
_TOOL_ACTIVE_FORM: dict[str, str] = {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
553
|
+
tools.BASH: "Bashing",
|
|
554
|
+
tools.APPLY_PATCH: "Patching",
|
|
555
|
+
tools.MOVE: "Moving",
|
|
556
|
+
tools.EDIT: "Editing",
|
|
557
|
+
tools.READ: "Reading",
|
|
558
|
+
tools.WRITE: "Writing",
|
|
559
|
+
tools.TODO_WRITE: "Planning",
|
|
560
|
+
tools.UPDATE_PLAN: "Planning",
|
|
561
|
+
tools.SKILL: "Skilling",
|
|
562
|
+
tools.MERMAID: "Diagramming",
|
|
563
|
+
tools.WEB_FETCH: "Fetching Web",
|
|
564
|
+
tools.WEB_SEARCH: "Searching Web",
|
|
565
|
+
tools.REPORT_BACK: "Reporting",
|
|
440
566
|
}
|
|
441
567
|
|
|
442
568
|
|
|
@@ -463,7 +589,6 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
|
|
|
463
589
|
|
|
464
590
|
Returns a Rich Renderable or None if the tool call should not be rendered.
|
|
465
591
|
"""
|
|
466
|
-
from klaude_code.protocol import tools
|
|
467
592
|
|
|
468
593
|
if is_sub_agent_tool(e.tool_name):
|
|
469
594
|
return None
|
|
@@ -475,40 +600,66 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
|
|
|
475
600
|
return render_edit_tool_call(e.arguments)
|
|
476
601
|
case tools.WRITE:
|
|
477
602
|
return render_write_tool_call(e.arguments)
|
|
478
|
-
case tools.
|
|
479
|
-
return
|
|
603
|
+
case tools.MOVE:
|
|
604
|
+
return render_move_tool_call(e.arguments)
|
|
480
605
|
case tools.BASH:
|
|
481
|
-
return
|
|
606
|
+
return render_bash_tool_call(e.arguments)
|
|
482
607
|
case tools.APPLY_PATCH:
|
|
483
608
|
return render_apply_patch_tool_call(e.arguments)
|
|
484
609
|
case tools.TODO_WRITE:
|
|
485
|
-
return render_generic_tool_call("Update Todos", "",
|
|
610
|
+
return render_generic_tool_call("Update Todos", "", MARK_PLAN)
|
|
486
611
|
case tools.UPDATE_PLAN:
|
|
487
612
|
return render_update_plan_tool_call(e.arguments)
|
|
488
613
|
case tools.MERMAID:
|
|
489
614
|
return render_mermaid_tool_call(e.arguments)
|
|
490
|
-
case tools.MEMORY:
|
|
491
|
-
return render_memory_tool_call(e.arguments)
|
|
492
615
|
case tools.SKILL:
|
|
493
|
-
return render_generic_tool_call(e.tool_name, e.arguments,
|
|
616
|
+
return render_generic_tool_call(e.tool_name, e.arguments, MARK_SKILL)
|
|
617
|
+
case tools.REPORT_BACK:
|
|
618
|
+
return render_report_back_tool_call()
|
|
619
|
+
case tools.WEB_FETCH:
|
|
620
|
+
return render_web_fetch_tool_call(e.arguments)
|
|
621
|
+
case tools.WEB_SEARCH:
|
|
622
|
+
return render_web_search_tool_call(e.arguments)
|
|
494
623
|
case _:
|
|
495
624
|
return render_generic_tool_call(e.tool_name, e.arguments)
|
|
496
625
|
|
|
497
626
|
|
|
498
|
-
def
|
|
499
|
-
if ui_extra
|
|
500
|
-
return
|
|
501
|
-
if ui_extra
|
|
502
|
-
|
|
627
|
+
def _extract_diff(ui_extra: model.ToolResultUIExtra | None) -> model.DiffUIExtra | None:
|
|
628
|
+
if isinstance(ui_extra, model.DiffUIExtra):
|
|
629
|
+
return ui_extra
|
|
630
|
+
if isinstance(ui_extra, model.MultiUIExtra):
|
|
631
|
+
for item in ui_extra.items:
|
|
632
|
+
if isinstance(item, model.DiffUIExtra):
|
|
633
|
+
return item
|
|
503
634
|
return None
|
|
504
635
|
|
|
505
636
|
|
|
506
|
-
def
|
|
637
|
+
def _extract_markdown_doc(ui_extra: model.ToolResultUIExtra | None) -> model.MarkdownDocUIExtra | None:
|
|
638
|
+
if isinstance(ui_extra, model.MarkdownDocUIExtra):
|
|
639
|
+
return ui_extra
|
|
640
|
+
if isinstance(ui_extra, model.MultiUIExtra):
|
|
641
|
+
for item in ui_extra.items:
|
|
642
|
+
if isinstance(item, model.MarkdownDocUIExtra):
|
|
643
|
+
return item
|
|
644
|
+
return None
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def render_markdown_doc(md_ui: model.MarkdownDocUIExtra, *, code_theme: str) -> RenderableType:
|
|
648
|
+
"""Render markdown document content in a panel."""
|
|
649
|
+
header = render_path(md_ui.file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
650
|
+
return Panel.fit(
|
|
651
|
+
Group(header, Text(""), NoInsetMarkdown(md_ui.content, code_theme=code_theme)),
|
|
652
|
+
box=box.SIMPLE,
|
|
653
|
+
border_style=ThemeKey.LINES,
|
|
654
|
+
style=ThemeKey.WRITE_MARKDOWN_PANEL,
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def render_tool_result(e: events.ToolResultEvent, *, code_theme: str = "monokai") -> RenderableType | None:
|
|
507
659
|
"""Unified entry point for rendering tool results.
|
|
508
660
|
|
|
509
661
|
Returns a Rich Renderable or None if the tool result should not be rendered.
|
|
510
662
|
"""
|
|
511
|
-
from klaude_code.protocol import tools
|
|
512
663
|
from klaude_code.ui.renderers import errors as r_errors
|
|
513
664
|
|
|
514
665
|
if is_sub_agent_tool(e.tool_name):
|
|
@@ -516,36 +667,63 @@ def render_tool_result(e: events.ToolResultEvent) -> RenderableType | None:
|
|
|
516
667
|
|
|
517
668
|
# Handle error case
|
|
518
669
|
if e.status == "error" and e.ui_extra is None:
|
|
519
|
-
error_msg =
|
|
520
|
-
return r_errors.
|
|
670
|
+
error_msg = truncate_display(e.result)
|
|
671
|
+
return r_errors.render_tool_error(error_msg)
|
|
672
|
+
|
|
673
|
+
# Render multiple ui blocks if present
|
|
674
|
+
if isinstance(e.ui_extra, model.MultiUIExtra) and e.ui_extra.items:
|
|
675
|
+
rendered: list[RenderableType] = []
|
|
676
|
+
for item in e.ui_extra.items:
|
|
677
|
+
if isinstance(item, model.MarkdownDocUIExtra):
|
|
678
|
+
rendered.append(Padding.indent(render_markdown_doc(item, code_theme=code_theme), level=2))
|
|
679
|
+
elif isinstance(item, model.DiffUIExtra):
|
|
680
|
+
show_file_name = e.tool_name in (tools.APPLY_PATCH, tools.MOVE)
|
|
681
|
+
rendered.append(
|
|
682
|
+
Padding.indent(r_diffs.render_structured_diff(item, show_file_name=show_file_name), level=2)
|
|
683
|
+
)
|
|
684
|
+
return Group(*rendered) if rendered else None
|
|
521
685
|
|
|
522
686
|
# Show truncation info if output was truncated and saved to file
|
|
523
687
|
truncation_info = get_truncation_info(e)
|
|
524
688
|
if truncation_info:
|
|
525
|
-
return render_truncation_info(truncation_info)
|
|
689
|
+
return Group(render_truncation_info(truncation_info), render_generic_tool_result(e.result))
|
|
526
690
|
|
|
527
|
-
|
|
691
|
+
diff_ui = _extract_diff(e.ui_extra)
|
|
692
|
+
md_ui = _extract_markdown_doc(e.ui_extra)
|
|
528
693
|
|
|
529
694
|
match e.tool_name:
|
|
530
695
|
case tools.READ:
|
|
531
696
|
return None
|
|
532
|
-
case tools.EDIT
|
|
533
|
-
return Padding.indent(r_diffs.
|
|
534
|
-
case tools.
|
|
535
|
-
if
|
|
536
|
-
return Padding.indent(
|
|
537
|
-
|
|
538
|
-
|
|
697
|
+
case tools.EDIT:
|
|
698
|
+
return Padding.indent(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""), level=2)
|
|
699
|
+
case tools.WRITE:
|
|
700
|
+
if md_ui:
|
|
701
|
+
return Padding.indent(render_markdown_doc(md_ui, code_theme=code_theme), level=2)
|
|
702
|
+
return Padding.indent(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""), level=2)
|
|
703
|
+
case tools.MOVE:
|
|
704
|
+
# Same-file move returns single DiffUIExtra, cross-file returns MultiUIExtra (handled above)
|
|
705
|
+
if diff_ui:
|
|
706
|
+
return Padding.indent(r_diffs.render_structured_diff(diff_ui, show_file_name=True), level=2)
|
|
539
707
|
return None
|
|
708
|
+
case tools.APPLY_PATCH:
|
|
709
|
+
if md_ui:
|
|
710
|
+
return Padding.indent(render_markdown_doc(md_ui, code_theme=code_theme), level=2)
|
|
711
|
+
if diff_ui:
|
|
712
|
+
return Padding.indent(r_diffs.render_structured_diff(diff_ui, show_file_name=True), level=2)
|
|
713
|
+
if len(e.result.strip()) == 0:
|
|
714
|
+
return render_generic_tool_result("(no content)")
|
|
715
|
+
return render_generic_tool_result(e.result)
|
|
540
716
|
case tools.TODO_WRITE | tools.UPDATE_PLAN:
|
|
541
717
|
return render_todo(e)
|
|
542
718
|
case tools.MERMAID:
|
|
543
719
|
return render_mermaid_tool_result(e)
|
|
544
|
-
case
|
|
545
|
-
if e.
|
|
720
|
+
case tools.BASH:
|
|
721
|
+
if e.result.startswith("diff --git"):
|
|
546
722
|
return r_diffs.render_diff_panel(e.result, show_file_name=True)
|
|
547
|
-
if e.
|
|
548
|
-
return
|
|
723
|
+
if len(e.result.strip()) == 0:
|
|
724
|
+
return render_generic_tool_result("(no content)")
|
|
725
|
+
return render_generic_tool_result(e.result)
|
|
726
|
+
case _:
|
|
549
727
|
if len(e.result.strip()) == 0:
|
|
550
728
|
return render_generic_tool_result("(no content)")
|
|
551
729
|
return render_generic_tool_result(e.result)
|