klaude-code 1.9.0__py3-none-any.whl → 2.0.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/base.py +2 -6
- klaude_code/cli/auth_cmd.py +4 -4
- klaude_code/cli/list_model.py +1 -1
- klaude_code/cli/main.py +1 -1
- klaude_code/cli/runtime.py +7 -5
- klaude_code/cli/self_update.py +1 -1
- klaude_code/cli/session_cmd.py +1 -1
- klaude_code/command/clear_cmd.py +6 -2
- klaude_code/command/command_abc.py +2 -2
- klaude_code/command/debug_cmd.py +4 -4
- klaude_code/command/export_cmd.py +2 -2
- klaude_code/command/export_online_cmd.py +12 -12
- klaude_code/command/fork_session_cmd.py +29 -23
- klaude_code/command/help_cmd.py +4 -4
- klaude_code/command/model_cmd.py +4 -4
- klaude_code/command/model_select.py +1 -1
- klaude_code/command/prompt-commit.md +11 -2
- klaude_code/command/prompt_command.py +3 -3
- klaude_code/command/refresh_cmd.py +2 -2
- klaude_code/command/registry.py +7 -5
- klaude_code/command/release_notes_cmd.py +4 -4
- klaude_code/command/resume_cmd.py +15 -11
- klaude_code/command/status_cmd.py +4 -4
- klaude_code/command/terminal_setup_cmd.py +8 -8
- klaude_code/command/thinking_cmd.py +4 -4
- klaude_code/config/assets/builtin_config.yaml +16 -0
- klaude_code/config/builtin_config.py +16 -5
- klaude_code/config/config.py +7 -2
- klaude_code/const.py +146 -91
- klaude_code/core/agent.py +3 -12
- klaude_code/core/executor.py +21 -13
- klaude_code/core/manager/sub_agent_manager.py +71 -7
- klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
- klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
- klaude_code/core/reminders.py +88 -69
- klaude_code/core/task.py +44 -45
- klaude_code/core/tool/file/apply_patch_tool.py +9 -9
- klaude_code/core/tool/file/diff_builder.py +3 -5
- klaude_code/core/tool/file/edit_tool.py +23 -23
- klaude_code/core/tool/file/move_tool.py +43 -43
- klaude_code/core/tool/file/read_tool.py +44 -39
- klaude_code/core/tool/file/write_tool.py +14 -14
- klaude_code/core/tool/report_back_tool.py +4 -4
- klaude_code/core/tool/shell/bash_tool.py +23 -23
- klaude_code/core/tool/skill/skill_tool.py +7 -7
- klaude_code/core/tool/sub_agent_tool.py +38 -9
- klaude_code/core/tool/todo/todo_write_tool.py +8 -8
- klaude_code/core/tool/todo/update_plan_tool.py +6 -6
- klaude_code/core/tool/tool_abc.py +2 -2
- klaude_code/core/tool/tool_context.py +27 -0
- klaude_code/core/tool/tool_runner.py +88 -42
- klaude_code/core/tool/truncation.py +38 -20
- klaude_code/core/tool/web/mermaid_tool.py +6 -7
- klaude_code/core/tool/web/web_fetch_tool.py +68 -30
- klaude_code/core/tool/web/web_search_tool.py +15 -17
- klaude_code/core/turn.py +120 -73
- klaude_code/llm/anthropic/client.py +79 -44
- klaude_code/llm/anthropic/input.py +116 -108
- klaude_code/llm/bedrock/client.py +8 -5
- klaude_code/llm/claude/client.py +18 -8
- klaude_code/llm/client.py +4 -3
- klaude_code/llm/codex/client.py +15 -9
- klaude_code/llm/google/client.py +122 -60
- klaude_code/llm/google/input.py +94 -108
- klaude_code/llm/image.py +123 -0
- klaude_code/llm/input_common.py +136 -189
- klaude_code/llm/openai_compatible/client.py +17 -7
- klaude_code/llm/openai_compatible/input.py +36 -66
- klaude_code/llm/openai_compatible/stream.py +119 -67
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
- klaude_code/llm/openrouter/client.py +34 -9
- klaude_code/llm/openrouter/input.py +63 -64
- klaude_code/llm/openrouter/reasoning.py +22 -24
- klaude_code/llm/registry.py +20 -17
- klaude_code/llm/responses/client.py +107 -45
- klaude_code/llm/responses/input.py +115 -98
- klaude_code/llm/usage.py +52 -25
- klaude_code/protocol/__init__.py +1 -0
- klaude_code/protocol/events.py +16 -12
- klaude_code/protocol/llm_param.py +20 -2
- klaude_code/protocol/message.py +250 -0
- klaude_code/protocol/model.py +94 -281
- klaude_code/protocol/op.py +2 -2
- klaude_code/protocol/sub_agent/__init__.py +1 -0
- klaude_code/protocol/sub_agent/explore.py +10 -0
- klaude_code/protocol/sub_agent/image_gen.py +119 -0
- klaude_code/protocol/sub_agent/task.py +10 -0
- klaude_code/protocol/sub_agent/web.py +10 -0
- klaude_code/session/codec.py +6 -6
- klaude_code/session/export.py +261 -62
- klaude_code/session/selector.py +7 -24
- klaude_code/session/session.py +126 -54
- klaude_code/session/store.py +5 -32
- klaude_code/session/templates/export_session.html +1 -1
- klaude_code/session/templates/mermaid_viewer.html +1 -1
- klaude_code/trace/log.py +11 -6
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +1 -8
- klaude_code/ui/modes/debug/display.py +2 -2
- klaude_code/ui/modes/repl/clipboard.py +2 -2
- klaude_code/ui/modes/repl/completers.py +18 -10
- klaude_code/ui/modes/repl/event_handler.py +136 -127
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
- klaude_code/ui/modes/repl/key_bindings.py +1 -1
- klaude_code/ui/modes/repl/renderer.py +107 -15
- klaude_code/ui/renderers/assistant.py +2 -2
- klaude_code/ui/renderers/common.py +65 -7
- klaude_code/ui/renderers/developer.py +7 -6
- klaude_code/ui/renderers/diffs.py +11 -11
- klaude_code/ui/renderers/mermaid_viewer.py +49 -2
- klaude_code/ui/renderers/metadata.py +33 -5
- klaude_code/ui/renderers/sub_agent.py +57 -16
- klaude_code/ui/renderers/thinking.py +37 -2
- klaude_code/ui/renderers/tools.py +180 -165
- klaude_code/ui/rich/live.py +3 -1
- klaude_code/ui/rich/markdown.py +39 -7
- klaude_code/ui/rich/quote.py +76 -1
- klaude_code/ui/rich/status.py +14 -8
- klaude_code/ui/rich/theme.py +8 -2
- klaude_code/ui/terminal/image.py +34 -0
- klaude_code/ui/terminal/notifier.py +2 -1
- klaude_code/ui/terminal/progress_bar.py +4 -4
- klaude_code/ui/terminal/selector.py +22 -4
- klaude_code/ui/utils/common.py +11 -2
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/METADATA +4 -2
- klaude_code-2.0.0.dist-info/RECORD +229 -0
- klaude_code-1.9.0.dist-info/RECORD +0 -224
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -4,20 +4,26 @@ from typing import Any, cast
|
|
|
4
4
|
|
|
5
5
|
from rich import box
|
|
6
6
|
from rich.console import Group, RenderableType
|
|
7
|
-
from rich.padding import Padding
|
|
8
7
|
from rich.panel import Panel
|
|
9
8
|
from rich.style import Style
|
|
10
9
|
from rich.text import Text
|
|
11
10
|
|
|
12
|
-
from klaude_code import
|
|
11
|
+
from klaude_code.const import (
|
|
12
|
+
BASH_OUTPUT_PANEL_THRESHOLD,
|
|
13
|
+
INVALID_TOOL_CALL_MAX_LENGTH,
|
|
14
|
+
QUERY_DISPLAY_TRUNCATE_LENGTH,
|
|
15
|
+
URL_TRUNCATE_MAX_LENGTH,
|
|
16
|
+
WEB_SEARCH_DEFAULT_MAX_RESULTS,
|
|
17
|
+
)
|
|
13
18
|
from klaude_code.protocol import events, model, tools
|
|
14
19
|
from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
|
|
15
20
|
from klaude_code.ui.renderers import diffs as r_diffs
|
|
16
21
|
from klaude_code.ui.renderers import mermaid_viewer as r_mermaid_viewer
|
|
17
22
|
from klaude_code.ui.renderers.bash_syntax import highlight_bash_command
|
|
18
|
-
from klaude_code.ui.renderers.common import create_grid,
|
|
23
|
+
from klaude_code.ui.renderers.common import create_grid, truncate_middle
|
|
19
24
|
from klaude_code.ui.rich.code_panel import CodePanel
|
|
20
25
|
from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
26
|
+
from klaude_code.ui.rich.quote import TreeQuote
|
|
21
27
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
22
28
|
|
|
23
29
|
# Tool markers (Unicode symbols for UI display)
|
|
@@ -56,55 +62,76 @@ def render_path(path: str, style: str, is_directory: bool = False) -> Text:
|
|
|
56
62
|
return Text(path, style=style)
|
|
57
63
|
|
|
58
64
|
|
|
59
|
-
def
|
|
65
|
+
def _render_tool_call_tree(
|
|
66
|
+
*,
|
|
67
|
+
mark: str,
|
|
68
|
+
tool_name: str,
|
|
69
|
+
details: RenderableType | None,
|
|
70
|
+
) -> RenderableType:
|
|
71
|
+
# Keep the original 2-column layout (tool name on the left, details on the right),
|
|
72
|
+
# but move the tool mark into the tree prefix so it can connect to the tool result.
|
|
60
73
|
grid = create_grid()
|
|
74
|
+
grid.add_row(
|
|
75
|
+
Text(tool_name, style=ThemeKey.TOOL_NAME),
|
|
76
|
+
details if details is not None else Text(""),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return TreeQuote.for_tool_call(
|
|
80
|
+
grid,
|
|
81
|
+
mark=mark,
|
|
82
|
+
style=ThemeKey.TOOL_RESULT_TREE_PREFIX,
|
|
83
|
+
style_first=ThemeKey.TOOL_MARK,
|
|
84
|
+
)
|
|
61
85
|
|
|
62
|
-
|
|
63
|
-
|
|
86
|
+
|
|
87
|
+
def render_generic_tool_call(tool_name: str, arguments: str, markup: str = MARK_GENERIC) -> RenderableType:
|
|
64
88
|
if not arguments:
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
return _render_tool_call_tree(mark=markup, tool_name=tool_name, details=None)
|
|
90
|
+
|
|
91
|
+
details: RenderableType
|
|
67
92
|
try:
|
|
68
|
-
|
|
69
|
-
if len(json_dict) == 0:
|
|
70
|
-
arguments_column = Text("", ThemeKey.TOOL_PARAM)
|
|
71
|
-
elif len(json_dict) == 1:
|
|
72
|
-
arguments_column = Text(str(next(iter(json_dict.values()))), ThemeKey.TOOL_PARAM)
|
|
73
|
-
else:
|
|
74
|
-
arguments_column = Text(
|
|
75
|
-
", ".join([f"{k}: {v}" for k, v in json_dict.items()]),
|
|
76
|
-
ThemeKey.TOOL_PARAM,
|
|
77
|
-
)
|
|
93
|
+
payload = json.loads(arguments)
|
|
78
94
|
except json.JSONDecodeError:
|
|
79
|
-
|
|
80
|
-
arguments.strip()[:
|
|
95
|
+
details = Text(
|
|
96
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
81
97
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
82
98
|
)
|
|
83
|
-
|
|
84
|
-
|
|
99
|
+
else:
|
|
100
|
+
if isinstance(payload, dict):
|
|
101
|
+
payload_dict = cast(dict[str, Any], payload)
|
|
102
|
+
if len(payload_dict) == 0:
|
|
103
|
+
details = Text("", ThemeKey.TOOL_PARAM)
|
|
104
|
+
elif len(payload_dict) == 1:
|
|
105
|
+
details = Text(str(next(iter(payload_dict.values()))), ThemeKey.TOOL_PARAM)
|
|
106
|
+
else:
|
|
107
|
+
details = Text(
|
|
108
|
+
", ".join([f"{k}: {v}" for k, v in payload_dict.items()]),
|
|
109
|
+
ThemeKey.TOOL_PARAM,
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
details = Text(str(payload)[:INVALID_TOOL_CALL_MAX_LENGTH], style=ThemeKey.INVALID_TOOL_CALL_ARGS)
|
|
113
|
+
|
|
114
|
+
return _render_tool_call_tree(mark=markup, tool_name=tool_name, details=details)
|
|
85
115
|
|
|
86
116
|
|
|
87
117
|
def render_bash_tool_call(arguments: str) -> RenderableType:
|
|
88
|
-
|
|
89
|
-
tool_name_column = Text.assemble((MARK_BASH, ThemeKey.TOOL_MARK), " ", ("Bash", ThemeKey.TOOL_NAME))
|
|
118
|
+
tool_name = "Bash"
|
|
90
119
|
|
|
91
120
|
try:
|
|
92
121
|
payload_raw: Any = json.loads(arguments) if arguments else {}
|
|
93
122
|
except json.JSONDecodeError:
|
|
94
|
-
|
|
95
|
-
arguments.strip()[:
|
|
123
|
+
details: RenderableType = Text(
|
|
124
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
96
125
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
97
126
|
)
|
|
98
|
-
|
|
99
|
-
return grid
|
|
127
|
+
return _render_tool_call_tree(mark=MARK_BASH, tool_name=tool_name, details=details)
|
|
100
128
|
|
|
101
129
|
if not isinstance(payload_raw, dict):
|
|
102
|
-
|
|
103
|
-
str(payload_raw)[:
|
|
130
|
+
details = Text(
|
|
131
|
+
str(payload_raw)[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
104
132
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
105
133
|
)
|
|
106
|
-
|
|
107
|
-
return grid
|
|
134
|
+
return _render_tool_call_tree(mark=MARK_BASH, tool_name=tool_name, details=details)
|
|
108
135
|
|
|
109
136
|
payload: dict[str, object] = cast(dict[str, object], payload_raw)
|
|
110
137
|
|
|
@@ -118,24 +145,27 @@ def render_bash_tool_call(arguments: str) -> RenderableType:
|
|
|
118
145
|
|
|
119
146
|
highlighted = highlight_bash_command(cmd_str)
|
|
120
147
|
|
|
121
|
-
# For commands >
|
|
122
|
-
if line_count >
|
|
148
|
+
# For commands > threshold lines, use CodePanel for better display
|
|
149
|
+
if line_count > BASH_OUTPUT_PANEL_THRESHOLD:
|
|
123
150
|
code_panel = CodePanel(highlighted, border_style=ThemeKey.LINES)
|
|
124
151
|
if isinstance(timeout_ms, int):
|
|
125
152
|
if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
|
|
126
153
|
timeout_text = Text(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
127
154
|
else:
|
|
128
155
|
timeout_text = Text(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
|
|
129
|
-
|
|
156
|
+
return _render_tool_call_tree(
|
|
157
|
+
mark=MARK_BASH,
|
|
158
|
+
tool_name=tool_name,
|
|
159
|
+
details=Group(code_panel, timeout_text),
|
|
160
|
+
)
|
|
130
161
|
else:
|
|
131
|
-
|
|
132
|
-
return grid
|
|
162
|
+
return _render_tool_call_tree(mark=MARK_BASH, tool_name=tool_name, details=code_panel)
|
|
133
163
|
if isinstance(timeout_ms, int):
|
|
134
164
|
if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
|
|
135
165
|
highlighted.append(f" {timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
136
166
|
else:
|
|
137
167
|
highlighted.append(f" {timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
|
|
138
|
-
|
|
168
|
+
return _render_tool_call_tree(mark=MARK_BASH, tool_name=tool_name, details=highlighted)
|
|
139
169
|
else:
|
|
140
170
|
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
141
171
|
if isinstance(timeout_ms, int):
|
|
@@ -143,77 +173,73 @@ def render_bash_tool_call(arguments: str) -> RenderableType:
|
|
|
143
173
|
summary.append(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
|
|
144
174
|
else:
|
|
145
175
|
summary.append(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return grid
|
|
176
|
+
bash_details: RenderableType | None = summary if summary.plain else None
|
|
177
|
+
return _render_tool_call_tree(mark=MARK_BASH, tool_name=tool_name, details=bash_details)
|
|
149
178
|
|
|
150
179
|
|
|
151
180
|
def render_update_plan_tool_call(arguments: str) -> RenderableType:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
explanation_column = Text("")
|
|
181
|
+
tool_name = "Update Plan"
|
|
182
|
+
details: RenderableType | None = None
|
|
155
183
|
|
|
156
184
|
if arguments:
|
|
157
185
|
try:
|
|
158
186
|
payload = json.loads(arguments)
|
|
159
187
|
except json.JSONDecodeError:
|
|
160
|
-
|
|
161
|
-
arguments.strip()[:
|
|
188
|
+
details = Text(
|
|
189
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
162
190
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
163
191
|
)
|
|
164
192
|
else:
|
|
165
193
|
explanation = payload.get("explanation")
|
|
166
194
|
if isinstance(explanation, str) and explanation.strip():
|
|
167
|
-
|
|
195
|
+
details = Text(explanation.strip(), style=ThemeKey.TODO_EXPLANATION)
|
|
168
196
|
|
|
169
|
-
|
|
170
|
-
return grid
|
|
197
|
+
return _render_tool_call_tree(mark=MARK_PLAN, tool_name=tool_name, details=details)
|
|
171
198
|
|
|
172
199
|
|
|
173
200
|
def render_read_tool_call(arguments: str) -> RenderableType:
|
|
174
|
-
|
|
175
|
-
|
|
201
|
+
tool_name = "Read"
|
|
202
|
+
details = Text("", ThemeKey.TOOL_PARAM)
|
|
176
203
|
try:
|
|
177
204
|
json_dict = json.loads(arguments)
|
|
178
205
|
file_path = json_dict.get("file_path")
|
|
179
206
|
limit = json_dict.get("limit", None)
|
|
180
207
|
offset = json_dict.get("offset", None)
|
|
181
|
-
|
|
208
|
+
if isinstance(file_path, str) and file_path:
|
|
209
|
+
details.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
210
|
+
else:
|
|
211
|
+
details.append("(no file_path)", style=ThemeKey.TOOL_PARAM)
|
|
182
212
|
if limit is not None and offset is not None:
|
|
183
|
-
|
|
184
|
-
|
|
213
|
+
details = (
|
|
214
|
+
details.append_text(Text(" "))
|
|
185
215
|
.append_text(Text(str(offset), ThemeKey.TOOL_PARAM_BOLD))
|
|
186
216
|
.append_text(Text(":", ThemeKey.TOOL_PARAM))
|
|
187
217
|
.append_text(Text(str(offset + limit - 1), ThemeKey.TOOL_PARAM_BOLD))
|
|
188
218
|
)
|
|
189
219
|
elif limit is not None:
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
details = (
|
|
221
|
+
details.append_text(Text(" "))
|
|
192
222
|
.append_text(Text("1", ThemeKey.TOOL_PARAM_BOLD))
|
|
193
223
|
.append_text(Text(":", ThemeKey.TOOL_PARAM))
|
|
194
224
|
.append_text(Text(str(limit), ThemeKey.TOOL_PARAM_BOLD))
|
|
195
225
|
)
|
|
196
226
|
elif offset is not None:
|
|
197
|
-
|
|
198
|
-
|
|
227
|
+
details = (
|
|
228
|
+
details.append_text(Text(" "))
|
|
199
229
|
.append_text(Text(str(offset), ThemeKey.TOOL_PARAM_BOLD))
|
|
200
230
|
.append_text(Text(":", ThemeKey.TOOL_PARAM))
|
|
201
231
|
.append_text(Text("-", ThemeKey.TOOL_PARAM_BOLD))
|
|
202
232
|
)
|
|
203
233
|
except json.JSONDecodeError:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
208
|
-
)
|
|
234
|
+
details = Text(
|
|
235
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
236
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
209
237
|
)
|
|
210
|
-
|
|
211
|
-
return grid
|
|
238
|
+
return _render_tool_call_tree(mark=MARK_READ, tool_name=tool_name, details=details)
|
|
212
239
|
|
|
213
240
|
|
|
214
241
|
def render_edit_tool_call(arguments: str) -> RenderableType:
|
|
215
|
-
|
|
216
|
-
tool_name_column = Text.assemble((MARK_EDIT, ThemeKey.TOOL_MARK), " ", ("Edit", ThemeKey.TOOL_NAME))
|
|
242
|
+
tool_name = "Edit"
|
|
217
243
|
try:
|
|
218
244
|
json_dict = json.loads(arguments)
|
|
219
245
|
file_path = json_dict.get("file_path")
|
|
@@ -226,52 +252,46 @@ def render_edit_tool_call(arguments: str) -> RenderableType:
|
|
|
226
252
|
replace_info.append(old_string, ThemeKey.BASH_STRING)
|
|
227
253
|
replace_info.append(" → ", ThemeKey.BASH_OPERATOR)
|
|
228
254
|
replace_info.append(new_string, ThemeKey.BASH_STRING)
|
|
229
|
-
|
|
255
|
+
details: RenderableType = Group(path_text, replace_info)
|
|
230
256
|
else:
|
|
231
|
-
|
|
257
|
+
details = path_text
|
|
232
258
|
except json.JSONDecodeError:
|
|
233
|
-
|
|
234
|
-
arguments.strip()[:
|
|
259
|
+
details = Text(
|
|
260
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
235
261
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
236
262
|
)
|
|
237
|
-
|
|
238
|
-
return grid
|
|
263
|
+
return _render_tool_call_tree(mark=MARK_EDIT, tool_name=tool_name, details=details)
|
|
239
264
|
|
|
240
265
|
|
|
241
266
|
def render_write_tool_call(arguments: str) -> RenderableType:
|
|
242
|
-
|
|
267
|
+
tool_name = "Write"
|
|
243
268
|
try:
|
|
244
269
|
json_dict = json.loads(arguments)
|
|
245
270
|
file_path = json_dict.get("file_path", "")
|
|
246
|
-
tool_name_column = Text.assemble((MARK_WRITE, ThemeKey.TOOL_MARK), " ", ("Write", ThemeKey.TOOL_NAME))
|
|
247
271
|
# Markdown files show path in result panel, skip here to avoid duplication
|
|
248
272
|
if file_path.endswith(".md"):
|
|
249
|
-
|
|
273
|
+
details: RenderableType | None = None
|
|
250
274
|
else:
|
|
251
|
-
|
|
275
|
+
details = render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
252
276
|
except json.JSONDecodeError:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
277
|
+
details = Text(
|
|
278
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
256
279
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
257
280
|
)
|
|
258
|
-
|
|
259
|
-
return grid
|
|
281
|
+
return _render_tool_call_tree(mark=MARK_WRITE, tool_name=tool_name, details=details)
|
|
260
282
|
|
|
261
283
|
|
|
262
284
|
def render_move_tool_call(arguments: str) -> RenderableType:
|
|
263
|
-
|
|
264
|
-
tool_name_column = Text.assemble((MARK_MOVE, ThemeKey.TOOL_MARK), " ", ("Move", ThemeKey.TOOL_NAME))
|
|
285
|
+
tool_name = "Move"
|
|
265
286
|
|
|
266
287
|
try:
|
|
267
288
|
payload = json.loads(arguments)
|
|
268
289
|
except json.JSONDecodeError:
|
|
269
|
-
|
|
270
|
-
arguments.strip()[:
|
|
290
|
+
details = Text(
|
|
291
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
271
292
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
272
293
|
)
|
|
273
|
-
|
|
274
|
-
return grid
|
|
294
|
+
return _render_tool_call_tree(mark=MARK_MOVE, tool_name=tool_name, details=details)
|
|
275
295
|
|
|
276
296
|
source_path = payload.get("source_file_path", "")
|
|
277
297
|
target_path = payload.get("target_file_path", "")
|
|
@@ -288,26 +308,23 @@ def render_move_tool_call(arguments: str) -> RenderableType:
|
|
|
288
308
|
if target_path:
|
|
289
309
|
parts.append_text(render_path(target_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
290
310
|
|
|
291
|
-
|
|
292
|
-
return grid
|
|
311
|
+
return _render_tool_call_tree(mark=MARK_MOVE, tool_name=tool_name, details=parts)
|
|
293
312
|
|
|
294
313
|
|
|
295
314
|
def render_apply_patch_tool_call(arguments: str) -> RenderableType:
|
|
296
|
-
|
|
297
|
-
tool_name_column = Text.assemble((MARK_EDIT, ThemeKey.TOOL_MARK), " ", ("Apply Patch", ThemeKey.TOOL_NAME))
|
|
315
|
+
tool_name = "Apply Patch"
|
|
298
316
|
|
|
299
317
|
try:
|
|
300
318
|
payload = json.loads(arguments)
|
|
301
319
|
except json.JSONDecodeError:
|
|
302
|
-
|
|
303
|
-
arguments.strip()[:
|
|
320
|
+
details = Text(
|
|
321
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
304
322
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
305
323
|
)
|
|
306
|
-
|
|
307
|
-
return grid
|
|
324
|
+
return _render_tool_call_tree(mark=MARK_EDIT, tool_name=tool_name, details=details)
|
|
308
325
|
|
|
309
326
|
patch_content = payload.get("patch", "")
|
|
310
|
-
|
|
327
|
+
details = Text("", ThemeKey.TOOL_PARAM)
|
|
311
328
|
|
|
312
329
|
if isinstance(patch_content, str):
|
|
313
330
|
update_count = 0
|
|
@@ -330,15 +347,14 @@ def render_apply_patch_tool_call(arguments: str) -> RenderableType:
|
|
|
330
347
|
parts.append(f"Delete File × {delete_count}" if delete_count > 1 else "Delete File")
|
|
331
348
|
|
|
332
349
|
if parts:
|
|
333
|
-
|
|
350
|
+
details = Text(", ".join(parts), ThemeKey.TOOL_PARAM)
|
|
334
351
|
else:
|
|
335
|
-
|
|
336
|
-
str(patch_content)[:
|
|
352
|
+
details = Text(
|
|
353
|
+
str(patch_content)[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
337
354
|
ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
338
355
|
)
|
|
339
356
|
|
|
340
|
-
|
|
341
|
-
return grid
|
|
357
|
+
return _render_tool_call_tree(mark=MARK_EDIT, tool_name=tool_name, details=details)
|
|
342
358
|
|
|
343
359
|
|
|
344
360
|
def render_todo(tr: events.ToolResultEvent) -> RenderableType:
|
|
@@ -364,13 +380,13 @@ def render_todo(tr: events.ToolResultEvent) -> RenderableType:
|
|
|
364
380
|
text.stylize(text_style)
|
|
365
381
|
todo_grid.add_row(Text(mark, style=mark_style), text)
|
|
366
382
|
|
|
367
|
-
return
|
|
383
|
+
return todo_grid
|
|
368
384
|
|
|
369
385
|
|
|
370
386
|
def render_generic_tool_result(result: str, *, is_error: bool = False) -> RenderableType:
|
|
371
|
-
"""Render a generic tool result as
|
|
387
|
+
"""Render a generic tool result as truncated text."""
|
|
372
388
|
style = ThemeKey.ERROR if is_error else ThemeKey.TOOL_RESULT
|
|
373
|
-
return
|
|
389
|
+
return truncate_middle(result, base_style=style)
|
|
374
390
|
|
|
375
391
|
|
|
376
392
|
def _extract_mermaid_link(
|
|
@@ -382,15 +398,14 @@ def _extract_mermaid_link(
|
|
|
382
398
|
|
|
383
399
|
|
|
384
400
|
def render_mermaid_tool_call(arguments: str) -> RenderableType:
|
|
385
|
-
|
|
386
|
-
tool_name_column = Text.assemble((MARK_MERMAID, ThemeKey.TOOL_MARK), " ", ("Mermaid", ThemeKey.TOOL_NAME))
|
|
401
|
+
tool_name = "Mermaid"
|
|
387
402
|
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
388
403
|
|
|
389
404
|
try:
|
|
390
405
|
payload: dict[str, str] = json.loads(arguments)
|
|
391
406
|
except json.JSONDecodeError:
|
|
392
407
|
summary = Text(
|
|
393
|
-
arguments.strip()[:
|
|
408
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
394
409
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
395
410
|
)
|
|
396
411
|
else:
|
|
@@ -401,11 +416,10 @@ def render_mermaid_tool_call(arguments: str) -> RenderableType:
|
|
|
401
416
|
else:
|
|
402
417
|
summary = Text("0 lines", ThemeKey.TOOL_PARAM)
|
|
403
418
|
|
|
404
|
-
|
|
405
|
-
return grid
|
|
419
|
+
return _render_tool_call_tree(mark=MARK_MERMAID, tool_name=tool_name, details=summary)
|
|
406
420
|
|
|
407
421
|
|
|
408
|
-
def _truncate_url(url: str, max_length: int =
|
|
422
|
+
def _truncate_url(url: str, max_length: int = URL_TRUNCATE_MAX_LENGTH) -> str:
|
|
409
423
|
"""Truncate URL for display, preserving domain and path structure."""
|
|
410
424
|
if len(url) <= max_length:
|
|
411
425
|
return url
|
|
@@ -418,7 +432,7 @@ def _truncate_url(url: str, max_length: int = 400) -> str:
|
|
|
418
432
|
if len(display_url) <= max_length:
|
|
419
433
|
return display_url
|
|
420
434
|
# Truncate with ellipsis
|
|
421
|
-
return display_url[: max_length -
|
|
435
|
+
return display_url[: max_length - 1] + "…"
|
|
422
436
|
|
|
423
437
|
|
|
424
438
|
def _render_mermaid_viewer_link(
|
|
@@ -440,7 +454,7 @@ def _render_mermaid_viewer_link(
|
|
|
440
454
|
except ValueError:
|
|
441
455
|
file_url = f"file://{viewer_path.as_posix()}"
|
|
442
456
|
|
|
443
|
-
rendered = Text.assemble(("
|
|
457
|
+
rendered = Text.assemble(("View diagram in ", ThemeKey.TOOL_RESULT), " ")
|
|
444
458
|
start = len(rendered)
|
|
445
459
|
rendered.append(display_path, ThemeKey.TOOL_RESULT_MERMAID)
|
|
446
460
|
end = len(rendered)
|
|
@@ -452,39 +466,34 @@ def _render_mermaid_viewer_link(
|
|
|
452
466
|
|
|
453
467
|
|
|
454
468
|
def render_web_fetch_tool_call(arguments: str) -> RenderableType:
|
|
455
|
-
|
|
456
|
-
tool_name_column = Text.assemble((MARK_WEB_FETCH, ThemeKey.TOOL_MARK), " ", ("Fetch", ThemeKey.TOOL_NAME))
|
|
469
|
+
tool_name = "Fetch"
|
|
457
470
|
|
|
458
471
|
try:
|
|
459
472
|
payload: dict[str, str] = json.loads(arguments)
|
|
460
473
|
except json.JSONDecodeError:
|
|
461
474
|
summary = Text(
|
|
462
|
-
arguments.strip()[:
|
|
475
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
463
476
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
464
477
|
)
|
|
465
|
-
|
|
466
|
-
return grid
|
|
478
|
+
return _render_tool_call_tree(mark=MARK_WEB_FETCH, tool_name=tool_name, details=summary)
|
|
467
479
|
|
|
468
480
|
url = payload.get("url", "")
|
|
469
481
|
summary = Text(_truncate_url(url), ThemeKey.TOOL_PARAM_FILE_PATH) if url else Text("(no url)", ThemeKey.TOOL_PARAM)
|
|
470
482
|
|
|
471
|
-
|
|
472
|
-
return grid
|
|
483
|
+
return _render_tool_call_tree(mark=MARK_WEB_FETCH, tool_name=tool_name, details=summary)
|
|
473
484
|
|
|
474
485
|
|
|
475
486
|
def render_web_search_tool_call(arguments: str) -> RenderableType:
|
|
476
|
-
|
|
477
|
-
tool_name_column = Text.assemble((MARK_WEB_SEARCH, ThemeKey.TOOL_MARK), " ", ("Web Search", ThemeKey.TOOL_NAME))
|
|
487
|
+
tool_name = "Web Search"
|
|
478
488
|
|
|
479
489
|
try:
|
|
480
490
|
payload: dict[str, Any] = json.loads(arguments)
|
|
481
491
|
except json.JSONDecodeError:
|
|
482
492
|
summary = Text(
|
|
483
|
-
arguments.strip()[:
|
|
493
|
+
arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
|
|
484
494
|
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
485
495
|
)
|
|
486
|
-
|
|
487
|
-
return grid
|
|
496
|
+
return _render_tool_call_tree(mark=MARK_WEB_SEARCH, tool_name=tool_name, details=summary)
|
|
488
497
|
|
|
489
498
|
query = payload.get("query", "")
|
|
490
499
|
max_results = payload.get("max_results")
|
|
@@ -492,19 +501,24 @@ def render_web_search_tool_call(arguments: str) -> RenderableType:
|
|
|
492
501
|
summary = Text("", ThemeKey.TOOL_PARAM)
|
|
493
502
|
if query:
|
|
494
503
|
# Truncate long queries
|
|
495
|
-
display_query =
|
|
504
|
+
display_query = (
|
|
505
|
+
query if len(query) <= QUERY_DISPLAY_TRUNCATE_LENGTH else query[: QUERY_DISPLAY_TRUNCATE_LENGTH - 1] + "…"
|
|
506
|
+
)
|
|
496
507
|
summary.append(display_query, ThemeKey.TOOL_PARAM)
|
|
497
508
|
else:
|
|
498
509
|
summary.append("(no query)", ThemeKey.TOOL_PARAM)
|
|
499
510
|
|
|
500
|
-
if isinstance(max_results, int) and max_results !=
|
|
511
|
+
if isinstance(max_results, int) and max_results != WEB_SEARCH_DEFAULT_MAX_RESULTS:
|
|
501
512
|
summary.append(f" (max {max_results})", ThemeKey.TOOL_TIMEOUT)
|
|
502
513
|
|
|
503
|
-
|
|
504
|
-
return grid
|
|
514
|
+
return _render_tool_call_tree(mark=MARK_WEB_SEARCH, tool_name=tool_name, details=summary)
|
|
505
515
|
|
|
506
516
|
|
|
507
|
-
def render_mermaid_tool_result(
|
|
517
|
+
def render_mermaid_tool_result(
|
|
518
|
+
tr: events.ToolResultEvent,
|
|
519
|
+
*,
|
|
520
|
+
session_id: str | None = None,
|
|
521
|
+
) -> RenderableType:
|
|
508
522
|
from klaude_code.ui.terminal import supports_osc8_hyperlinks
|
|
509
523
|
|
|
510
524
|
link_info = _extract_mermaid_link(tr.ui_extra)
|
|
@@ -513,7 +527,8 @@ def render_mermaid_tool_result(tr: events.ToolResultEvent) -> RenderableType:
|
|
|
513
527
|
|
|
514
528
|
use_osc8 = supports_osc8_hyperlinks()
|
|
515
529
|
viewer = _render_mermaid_viewer_link(tr, link_info, use_osc8=use_osc8)
|
|
516
|
-
|
|
530
|
+
|
|
531
|
+
return viewer
|
|
517
532
|
|
|
518
533
|
|
|
519
534
|
def _extract_truncation(
|
|
@@ -533,7 +548,7 @@ def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
|
|
|
533
548
|
(ui_extra.saved_file_path, ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
534
549
|
(f", {truncated_kb:.1f}KB truncated", ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
535
550
|
)
|
|
536
|
-
return
|
|
551
|
+
return text
|
|
537
552
|
|
|
538
553
|
|
|
539
554
|
def get_truncation_info(tr: events.ToolResultEvent) -> model.TruncationUIExtra | None:
|
|
@@ -542,10 +557,7 @@ def get_truncation_info(tr: events.ToolResultEvent) -> model.TruncationUIExtra |
|
|
|
542
557
|
|
|
543
558
|
|
|
544
559
|
def render_report_back_tool_call() -> RenderableType:
|
|
545
|
-
|
|
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
|
|
560
|
+
return _render_tool_call_tree(mark=MARK_DONE, tool_name="Report Back", details=None)
|
|
549
561
|
|
|
550
562
|
|
|
551
563
|
# Tool name to active form mapping (for spinner status)
|
|
@@ -655,75 +667,78 @@ def render_markdown_doc(md_ui: model.MarkdownDocUIExtra, *, code_theme: str) ->
|
|
|
655
667
|
)
|
|
656
668
|
|
|
657
669
|
|
|
658
|
-
def render_tool_result(
|
|
670
|
+
def render_tool_result(
|
|
671
|
+
e: events.ToolResultEvent,
|
|
672
|
+
*,
|
|
673
|
+
code_theme: str = "monokai",
|
|
674
|
+
session_id: str | None = None,
|
|
675
|
+
) -> RenderableType | None:
|
|
659
676
|
"""Unified entry point for rendering tool results.
|
|
660
677
|
|
|
661
678
|
Returns a Rich Renderable or None if the tool result should not be rendered.
|
|
662
679
|
"""
|
|
663
|
-
from klaude_code.ui.renderers import errors as r_errors
|
|
664
|
-
|
|
665
680
|
if is_sub_agent_tool(e.tool_name):
|
|
666
681
|
return None
|
|
667
682
|
|
|
683
|
+
def wrap(content: RenderableType) -> TreeQuote:
|
|
684
|
+
return TreeQuote.for_tool_result(content, is_last=e.is_last_in_turn)
|
|
685
|
+
|
|
668
686
|
# Handle error case
|
|
669
687
|
if e.status == "error" and e.ui_extra is None:
|
|
670
|
-
|
|
671
|
-
return r_errors.render_tool_error(error_msg)
|
|
688
|
+
return wrap(truncate_middle(e.result, base_style=ThemeKey.ERROR))
|
|
672
689
|
|
|
673
690
|
# Render multiple ui blocks if present
|
|
674
691
|
if isinstance(e.ui_extra, model.MultiUIExtra) and e.ui_extra.items:
|
|
675
692
|
rendered: list[RenderableType] = []
|
|
676
693
|
for item in e.ui_extra.items:
|
|
677
694
|
if isinstance(item, model.MarkdownDocUIExtra):
|
|
678
|
-
rendered.append(
|
|
695
|
+
rendered.append(render_markdown_doc(item, code_theme=code_theme))
|
|
679
696
|
elif isinstance(item, model.DiffUIExtra):
|
|
680
697
|
show_file_name = e.tool_name in (tools.APPLY_PATCH, tools.MOVE)
|
|
681
|
-
rendered.append(
|
|
682
|
-
|
|
683
|
-
)
|
|
684
|
-
return Group(*rendered) if rendered else None
|
|
698
|
+
rendered.append(r_diffs.render_structured_diff(item, show_file_name=show_file_name))
|
|
699
|
+
return wrap(Group(*rendered)) if rendered else None
|
|
685
700
|
|
|
686
701
|
# Show truncation info if output was truncated and saved to file
|
|
687
702
|
truncation_info = get_truncation_info(e)
|
|
688
703
|
if truncation_info:
|
|
689
|
-
|
|
704
|
+
result = render_generic_tool_result(e.result, is_error=e.status == "error")
|
|
705
|
+
return wrap(Group(render_truncation_info(truncation_info), result))
|
|
690
706
|
|
|
691
707
|
diff_ui = _extract_diff(e.ui_extra)
|
|
692
708
|
md_ui = _extract_markdown_doc(e.ui_extra)
|
|
693
709
|
|
|
710
|
+
def _render_fallback() -> TreeQuote:
|
|
711
|
+
if len(e.result.strip()) == 0:
|
|
712
|
+
return wrap(render_generic_tool_result("(no content)"))
|
|
713
|
+
return wrap(render_generic_tool_result(e.result, is_error=e.status == "error"))
|
|
714
|
+
|
|
694
715
|
match e.tool_name:
|
|
695
716
|
case tools.READ:
|
|
696
717
|
return None
|
|
697
718
|
case tools.EDIT:
|
|
698
|
-
return
|
|
719
|
+
return wrap(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""))
|
|
699
720
|
case tools.WRITE:
|
|
700
721
|
if md_ui:
|
|
701
|
-
return
|
|
702
|
-
return
|
|
722
|
+
return wrap(render_markdown_doc(md_ui, code_theme=code_theme))
|
|
723
|
+
return wrap(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""))
|
|
703
724
|
case tools.MOVE:
|
|
704
725
|
# Same-file move returns single DiffUIExtra, cross-file returns MultiUIExtra (handled above)
|
|
705
726
|
if diff_ui:
|
|
706
|
-
return
|
|
727
|
+
return wrap(r_diffs.render_structured_diff(diff_ui, show_file_name=True))
|
|
707
728
|
return None
|
|
708
729
|
case tools.APPLY_PATCH:
|
|
709
730
|
if md_ui:
|
|
710
|
-
return
|
|
731
|
+
return wrap(render_markdown_doc(md_ui, code_theme=code_theme))
|
|
711
732
|
if diff_ui:
|
|
712
|
-
return
|
|
713
|
-
|
|
714
|
-
return render_generic_tool_result("(no content)")
|
|
715
|
-
return render_generic_tool_result(e.result)
|
|
733
|
+
return wrap(r_diffs.render_structured_diff(diff_ui, show_file_name=True))
|
|
734
|
+
return _render_fallback()
|
|
716
735
|
case tools.TODO_WRITE | tools.UPDATE_PLAN:
|
|
717
|
-
return render_todo(e)
|
|
736
|
+
return wrap(render_todo(e))
|
|
718
737
|
case tools.MERMAID:
|
|
719
|
-
return render_mermaid_tool_result(e)
|
|
738
|
+
return wrap(render_mermaid_tool_result(e, session_id=session_id))
|
|
720
739
|
case tools.BASH:
|
|
721
740
|
if e.result.startswith("diff --git"):
|
|
722
|
-
return r_diffs.render_diff_panel(e.result, show_file_name=True)
|
|
723
|
-
|
|
724
|
-
return render_generic_tool_result("(no content)")
|
|
725
|
-
return render_generic_tool_result(e.result)
|
|
741
|
+
return wrap(r_diffs.render_diff_panel(e.result, show_file_name=True))
|
|
742
|
+
return _render_fallback()
|
|
726
743
|
case _:
|
|
727
|
-
|
|
728
|
-
return render_generic_tool_result("(no content)")
|
|
729
|
-
return render_generic_tool_result(e.result)
|
|
744
|
+
return _render_fallback()
|