klaude-code 1.2.8__py3-none-any.whl → 1.2.9__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/codex/__init__.py +1 -1
- klaude_code/command/__init__.py +2 -0
- klaude_code/command/prompt-deslop.md +14 -0
- klaude_code/command/release_notes_cmd.py +86 -0
- klaude_code/command/status_cmd.py +92 -54
- klaude_code/core/agent.py +13 -19
- klaude_code/core/manager/sub_agent_manager.py +5 -1
- klaude_code/core/prompt.py +38 -28
- klaude_code/core/reminders.py +4 -4
- klaude_code/core/task.py +59 -40
- klaude_code/core/tool/__init__.py +2 -0
- klaude_code/core/tool/file/apply_patch_tool.py +1 -1
- klaude_code/core/tool/file/edit_tool.py +1 -1
- klaude_code/core/tool/file/multi_edit_tool.py +1 -1
- klaude_code/core/tool/file/write_tool.py +1 -1
- klaude_code/core/tool/memory/memory_tool.py +2 -2
- klaude_code/core/tool/sub_agent_tool.py +2 -1
- klaude_code/core/tool/todo/todo_write_tool.py +1 -1
- klaude_code/core/tool/todo/update_plan_tool.py +1 -1
- klaude_code/core/tool/tool_context.py +21 -4
- klaude_code/core/tool/tool_runner.py +5 -8
- klaude_code/core/tool/web/mermaid_tool.py +1 -4
- klaude_code/core/turn.py +40 -37
- klaude_code/llm/anthropic/client.py +13 -44
- klaude_code/llm/client.py +1 -1
- klaude_code/llm/codex/client.py +4 -3
- klaude_code/llm/input_common.py +0 -6
- klaude_code/llm/openai_compatible/client.py +28 -72
- klaude_code/llm/openai_compatible/input.py +6 -4
- klaude_code/llm/openai_compatible/stream_processor.py +82 -0
- klaude_code/llm/openrouter/client.py +29 -59
- klaude_code/llm/openrouter/input.py +4 -27
- klaude_code/llm/responses/client.py +15 -48
- klaude_code/llm/usage.py +51 -10
- klaude_code/protocol/commands.py +1 -0
- klaude_code/protocol/events.py +11 -2
- klaude_code/protocol/model.py +142 -24
- klaude_code/protocol/sub_agent.py +5 -1
- klaude_code/session/export.py +51 -27
- klaude_code/session/session.py +28 -16
- klaude_code/session/templates/export_session.html +4 -1
- klaude_code/ui/modes/repl/__init__.py +1 -5
- klaude_code/ui/modes/repl/event_handler.py +153 -54
- klaude_code/ui/modes/repl/renderer.py +4 -4
- klaude_code/ui/renderers/developer.py +35 -25
- klaude_code/ui/renderers/metadata.py +68 -30
- klaude_code/ui/renderers/tools.py +53 -87
- klaude_code/ui/rich/markdown.py +5 -5
- {klaude_code-1.2.8.dist-info → klaude_code-1.2.9.dist-info}/METADATA +1 -1
- {klaude_code-1.2.8.dist-info → klaude_code-1.2.9.dist-info}/RECORD +52 -49
- {klaude_code-1.2.8.dist-info → klaude_code-1.2.9.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.8.dist-info → klaude_code-1.2.9.dist-info}/entry_points.txt +0 -0
|
@@ -7,6 +7,7 @@ from klaude_code.protocol import commands, events, model
|
|
|
7
7
|
from klaude_code.ui.renderers import diffs as r_diffs
|
|
8
8
|
from klaude_code.ui.renderers.common import create_grid
|
|
9
9
|
from klaude_code.ui.renderers.tools import render_path
|
|
10
|
+
from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
10
11
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
11
12
|
from klaude_code.ui.utils.common import truncate_display
|
|
12
13
|
|
|
@@ -100,6 +101,8 @@ def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
100
101
|
return Padding.indent(Text.from_markup(e.item.content or ""), level=2)
|
|
101
102
|
case commands.CommandName.STATUS:
|
|
102
103
|
return _render_status_output(e.item.command_output)
|
|
104
|
+
case commands.CommandName.RELEASE_NOTES:
|
|
105
|
+
return Padding.indent(NoInsetMarkdown(e.item.content or ""), level=2)
|
|
103
106
|
case _:
|
|
104
107
|
content = e.item.content or "(no content)"
|
|
105
108
|
style = ThemeKey.TOOL_RESULT if not e.item.command_output.is_error else ThemeKey.ERROR
|
|
@@ -126,34 +129,41 @@ def _format_cost(cost: float | None, currency: str = "USD") -> str:
|
|
|
126
129
|
|
|
127
130
|
|
|
128
131
|
def _render_status_output(command_output: model.CommandOutput) -> RenderableType:
|
|
129
|
-
"""Render session status
|
|
130
|
-
if not command_output.ui_extra
|
|
131
|
-
return Text("(no status data)", style=ThemeKey.
|
|
132
|
+
"""Render session status with total cost and per-model breakdown."""
|
|
133
|
+
if not isinstance(command_output.ui_extra, model.SessionStatusUIExtra):
|
|
134
|
+
return Text("(no status data)", style=ThemeKey.METADATA)
|
|
132
135
|
|
|
133
|
-
status = command_output.ui_extra
|
|
136
|
+
status = command_output.ui_extra
|
|
134
137
|
usage = status.usage
|
|
135
138
|
|
|
136
139
|
table = Table.grid(padding=(0, 2))
|
|
137
|
-
table.add_column(style=ThemeKey.
|
|
138
|
-
table.add_column(style=ThemeKey.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
table.add_row(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
140
|
+
table.add_column(style=ThemeKey.METADATA, overflow="fold")
|
|
141
|
+
table.add_column(style=ThemeKey.METADATA, overflow="fold")
|
|
142
|
+
|
|
143
|
+
# Total cost line
|
|
144
|
+
table.add_row(
|
|
145
|
+
Text("Total cost:", style=ThemeKey.METADATA_BOLD),
|
|
146
|
+
Text(_format_cost(usage.total_cost, usage.currency), style=ThemeKey.METADATA_BOLD),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Per-model breakdown
|
|
150
|
+
if status.by_model:
|
|
151
|
+
table.add_row(Text("Usage by model:", style=ThemeKey.METADATA_BOLD), "")
|
|
152
|
+
for meta in status.by_model:
|
|
153
|
+
model_label = meta.model_name
|
|
154
|
+
if meta.provider:
|
|
155
|
+
model_label = f"{meta.model_name} ({meta.provider.lower().replace(' ', '-')})"
|
|
156
|
+
|
|
157
|
+
if meta.usage:
|
|
158
|
+
usage_detail = (
|
|
159
|
+
f"{_format_tokens(meta.usage.input_tokens)} input, "
|
|
160
|
+
f"{_format_tokens(meta.usage.output_tokens)} output, "
|
|
161
|
+
f"{_format_tokens(meta.usage.cached_tokens)} cache read, "
|
|
162
|
+
f"{_format_tokens(meta.usage.reasoning_tokens)} thinking, "
|
|
163
|
+
f"({_format_cost(meta.usage.total_cost, meta.usage.currency)})"
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
usage_detail = "(no usage data)"
|
|
167
|
+
table.add_row(f"{model_label}:", usage_detail)
|
|
158
168
|
|
|
159
169
|
return Padding.indent(table, level=2)
|
|
@@ -7,7 +7,7 @@ from rich.padding import Padding
|
|
|
7
7
|
from rich.panel import Panel
|
|
8
8
|
from rich.text import Text
|
|
9
9
|
|
|
10
|
-
from klaude_code.protocol import events
|
|
10
|
+
from klaude_code.protocol import events, model
|
|
11
11
|
from klaude_code.trace import is_debug_enabled
|
|
12
12
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
13
13
|
from klaude_code.ui.utils.common import format_number
|
|
@@ -21,18 +21,34 @@ def _get_version() -> str:
|
|
|
21
21
|
return "unknown"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
metadata
|
|
24
|
+
def _render_task_metadata_block(
|
|
25
|
+
metadata: model.TaskMetadata,
|
|
26
|
+
*,
|
|
27
|
+
indent: int = 0,
|
|
28
|
+
show_context_and_time: bool = True,
|
|
29
|
+
) -> list[RenderableType]:
|
|
30
|
+
"""Render a single TaskMetadata block.
|
|
26
31
|
|
|
32
|
+
Args:
|
|
33
|
+
metadata: The TaskMetadata to render.
|
|
34
|
+
indent: Number of spaces to indent (0 for main, 2 for sub-agents).
|
|
35
|
+
show_context_and_time: Whether to show context usage percent and time.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
List of renderables for this metadata block.
|
|
39
|
+
"""
|
|
27
40
|
# Get currency symbol
|
|
28
41
|
currency = metadata.usage.currency if metadata.usage else "USD"
|
|
29
42
|
currency_symbol = "¥" if currency == "CNY" else "$"
|
|
30
43
|
|
|
31
44
|
# Line 1: Model and Provider
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
prefix = (
|
|
46
|
+
Text(" " * indent + "• ", style=ThemeKey.METADATA_BOLD)
|
|
47
|
+
if indent == 0
|
|
48
|
+
else Text(" " * indent + "└ ", style=ThemeKey.METADATA_DIM)
|
|
35
49
|
)
|
|
50
|
+
model_text = Text()
|
|
51
|
+
model_text.append_text(prefix).append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
|
|
36
52
|
if metadata.provider is not None:
|
|
37
53
|
model_text.append_text(Text("@", style=ThemeKey.METADATA)).append_text(
|
|
38
54
|
Text(metadata.provider.lower().replace(" ", "-"), style=ThemeKey.METADATA)
|
|
@@ -41,7 +57,7 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
41
57
|
renderables: list[RenderableType] = [model_text]
|
|
42
58
|
|
|
43
59
|
# Line 2: Token consumption, Context, TPS, Cost
|
|
44
|
-
|
|
60
|
+
parts2: list[Text] = []
|
|
45
61
|
|
|
46
62
|
if metadata.usage is not None:
|
|
47
63
|
# Input
|
|
@@ -51,7 +67,7 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
51
67
|
]
|
|
52
68
|
if metadata.usage.input_cost is not None:
|
|
53
69
|
input_parts.append((f"({currency_symbol}{metadata.usage.input_cost:.4f})", ThemeKey.METADATA_DIM))
|
|
54
|
-
|
|
70
|
+
parts2.append(Text.assemble(*input_parts))
|
|
55
71
|
|
|
56
72
|
# Cached
|
|
57
73
|
if metadata.usage.cached_tokens > 0:
|
|
@@ -61,7 +77,7 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
61
77
|
]
|
|
62
78
|
if metadata.usage.cache_read_cost is not None:
|
|
63
79
|
cached_parts.append((f"({currency_symbol}{metadata.usage.cache_read_cost:.4f})", ThemeKey.METADATA_DIM))
|
|
64
|
-
|
|
80
|
+
parts2.append(Text.assemble(*cached_parts))
|
|
65
81
|
|
|
66
82
|
# Output
|
|
67
83
|
output_parts: list[tuple[str, str]] = [
|
|
@@ -70,11 +86,11 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
70
86
|
]
|
|
71
87
|
if metadata.usage.output_cost is not None:
|
|
72
88
|
output_parts.append((f"({currency_symbol}{metadata.usage.output_cost:.4f})", ThemeKey.METADATA_DIM))
|
|
73
|
-
|
|
89
|
+
parts2.append(Text.assemble(*output_parts))
|
|
74
90
|
|
|
75
91
|
# Reasoning
|
|
76
92
|
if metadata.usage.reasoning_tokens > 0:
|
|
77
|
-
|
|
93
|
+
parts2.append(
|
|
78
94
|
Text.assemble(
|
|
79
95
|
("thinking", ThemeKey.METADATA_DIM),
|
|
80
96
|
(":", ThemeKey.METADATA_DIM),
|
|
@@ -85,14 +101,30 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
85
101
|
)
|
|
86
102
|
)
|
|
87
103
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
# Cost
|
|
105
|
+
if metadata.usage is not None and metadata.usage.total_cost is not None:
|
|
106
|
+
parts2.append(
|
|
107
|
+
Text.assemble(
|
|
108
|
+
("cost", ThemeKey.METADATA_DIM),
|
|
109
|
+
(":", ThemeKey.METADATA_DIM),
|
|
110
|
+
(f"{currency_symbol}{metadata.usage.total_cost:.4f}", ThemeKey.METADATA_DIM),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
if parts2:
|
|
114
|
+
line2 = Text(" / ", style=ThemeKey.METADATA_DIM).join(parts2)
|
|
115
|
+
renderables.append(Padding(line2, (0, 0, 0, indent + 2)))
|
|
116
|
+
|
|
117
|
+
parts3: list[Text] = []
|
|
118
|
+
if metadata.usage is not None:
|
|
119
|
+
# Context (only for main agent)
|
|
120
|
+
if show_context_and_time and metadata.usage.context_usage_percent is not None:
|
|
121
|
+
context_size = format_number(metadata.usage.context_window_size or 0)
|
|
122
|
+
parts3.append(
|
|
91
123
|
Text.assemble(
|
|
92
124
|
("context", ThemeKey.METADATA_DIM),
|
|
93
125
|
(":", ThemeKey.METADATA_DIM),
|
|
94
126
|
(
|
|
95
|
-
f"{metadata.usage.context_usage_percent:.1f}%",
|
|
127
|
+
f"{context_size}({metadata.usage.context_usage_percent:.1f}%)",
|
|
96
128
|
ThemeKey.METADATA_DIM,
|
|
97
129
|
),
|
|
98
130
|
)
|
|
@@ -100,7 +132,7 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
100
132
|
|
|
101
133
|
# TPS
|
|
102
134
|
if metadata.usage.throughput_tps is not None:
|
|
103
|
-
|
|
135
|
+
parts3.append(
|
|
104
136
|
Text.assemble(
|
|
105
137
|
("tps", ThemeKey.METADATA_DIM),
|
|
106
138
|
(":", ThemeKey.METADATA_DIM),
|
|
@@ -109,8 +141,8 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
109
141
|
)
|
|
110
142
|
|
|
111
143
|
# Duration
|
|
112
|
-
if metadata.task_duration_s is not None:
|
|
113
|
-
|
|
144
|
+
if show_context_and_time and metadata.task_duration_s is not None:
|
|
145
|
+
parts3.append(
|
|
114
146
|
Text.assemble(
|
|
115
147
|
("time", ThemeKey.METADATA_DIM),
|
|
116
148
|
(":", ThemeKey.METADATA_DIM),
|
|
@@ -118,19 +150,25 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
118
150
|
)
|
|
119
151
|
)
|
|
120
152
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
if parts3:
|
|
154
|
+
line2 = Text(" / ", style=ThemeKey.METADATA_DIM).join(parts3)
|
|
155
|
+
renderables.append(Padding(line2, (0, 0, 0, indent + 2)))
|
|
156
|
+
|
|
157
|
+
return renderables
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
161
|
+
"""Render task metadata including main agent and sub-agents, aggregated by model+provider."""
|
|
162
|
+
renderables: list[RenderableType] = []
|
|
163
|
+
|
|
164
|
+
renderables.extend(_render_task_metadata_block(e.metadata.main, indent=0, show_context_and_time=True))
|
|
165
|
+
|
|
166
|
+
# Aggregate by (model_name, provider), sorted by total_cost descending
|
|
167
|
+
sorted_items = model.TaskMetadata.aggregate_by_model(e.metadata.sub_agent_task_metadata)
|
|
130
168
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
renderables.
|
|
169
|
+
# Render each aggregated model block
|
|
170
|
+
for meta in sorted_items:
|
|
171
|
+
renderables.extend(_render_task_metadata_block(meta, indent=2, show_context_and_time=False))
|
|
134
172
|
|
|
135
173
|
return Group(*renderables)
|
|
136
174
|
|
|
@@ -121,32 +121,24 @@ def render_read_tool_call(arguments: str) -> RenderableType:
|
|
|
121
121
|
return grid
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def render_edit_tool_call(arguments: str) ->
|
|
125
|
-
|
|
124
|
+
def render_edit_tool_call(arguments: str) -> RenderableType:
|
|
125
|
+
grid = create_grid()
|
|
126
|
+
tool_name_column = Text.assemble(("→", ThemeKey.TOOL_MARK), " ", ("Edit", ThemeKey.TOOL_NAME))
|
|
126
127
|
try:
|
|
127
128
|
json_dict = json.loads(arguments)
|
|
128
129
|
file_path = json_dict.get("file_path")
|
|
129
|
-
|
|
130
|
-
render_result.append_text(Text("Edit", ThemeKey.TOOL_NAME))
|
|
131
|
-
.append_text(Text(" "))
|
|
132
|
-
.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
133
|
-
)
|
|
130
|
+
arguments_column = render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
134
131
|
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
|
-
)
|
|
132
|
+
arguments_column = Text(
|
|
133
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
134
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
144
135
|
)
|
|
145
|
-
|
|
136
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
137
|
+
return grid
|
|
146
138
|
|
|
147
139
|
|
|
148
|
-
def render_write_tool_call(arguments: str) ->
|
|
149
|
-
|
|
140
|
+
def render_write_tool_call(arguments: str) -> RenderableType:
|
|
141
|
+
grid = create_grid()
|
|
150
142
|
try:
|
|
151
143
|
json_dict = json.loads(arguments)
|
|
152
144
|
file_path = json_dict.get("file_path")
|
|
@@ -157,97 +149,77 @@ def render_write_tool_call(arguments: str) -> Text:
|
|
|
157
149
|
abs_path = (Path().cwd() / abs_path).resolve()
|
|
158
150
|
if abs_path.exists():
|
|
159
151
|
op_label = "Overwrite"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.append_text(Text(" "))
|
|
163
|
-
.append_text(render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH))
|
|
164
|
-
)
|
|
152
|
+
tool_name_column = Text.assemble(("→", ThemeKey.TOOL_MARK), " ", (op_label, ThemeKey.TOOL_NAME))
|
|
153
|
+
arguments_column = render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
|
|
165
154
|
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
|
-
)
|
|
155
|
+
tool_name_column = Text.assemble(("→", ThemeKey.TOOL_MARK), " ", ("Write", ThemeKey.TOOL_NAME))
|
|
156
|
+
arguments_column = Text(
|
|
157
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
158
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
175
159
|
)
|
|
176
|
-
|
|
160
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
161
|
+
return grid
|
|
177
162
|
|
|
178
163
|
|
|
179
|
-
def render_multi_edit_tool_call(arguments: str) ->
|
|
180
|
-
|
|
164
|
+
def render_multi_edit_tool_call(arguments: str) -> RenderableType:
|
|
165
|
+
grid = create_grid()
|
|
166
|
+
tool_name_column = Text.assemble(("→", ThemeKey.TOOL_MARK), " ", ("MultiEdit", ThemeKey.TOOL_NAME))
|
|
181
167
|
try:
|
|
182
168
|
json_dict = json.loads(arguments)
|
|
183
169
|
file_path = json_dict.get("file_path")
|
|
184
170
|
edits = json_dict.get("edits", [])
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
171
|
+
arguments_column = Text.assemble(
|
|
172
|
+
render_path(file_path, ThemeKey.TOOL_PARAM_FILE_PATH),
|
|
173
|
+
Text(" - "),
|
|
174
|
+
Text(f"{len(edits)}", ThemeKey.TOOL_PARAM_BOLD),
|
|
175
|
+
Text(" updates", ThemeKey.TOOL_PARAM_FILE_PATH),
|
|
190
176
|
)
|
|
191
177
|
except json.JSONDecodeError:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
196
|
-
)
|
|
178
|
+
arguments_column = Text(
|
|
179
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
180
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
197
181
|
)
|
|
198
|
-
|
|
182
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
183
|
+
return grid
|
|
199
184
|
|
|
200
185
|
|
|
201
186
|
def render_apply_patch_tool_call(arguments: str) -> RenderableType:
|
|
187
|
+
grid = create_grid()
|
|
188
|
+
tool_name_column = Text.assemble(("→", ThemeKey.TOOL_MARK), " ", ("Apply Patch", ThemeKey.TOOL_NAME))
|
|
189
|
+
|
|
202
190
|
try:
|
|
203
191
|
payload = json.loads(arguments)
|
|
204
192
|
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
|
-
),
|
|
193
|
+
arguments_column = Text(
|
|
194
|
+
arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
195
|
+
style=ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
213
196
|
)
|
|
197
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
198
|
+
return grid
|
|
214
199
|
|
|
215
200
|
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)
|
|
201
|
+
arguments_column = Text("", ThemeKey.TOOL_PARAM)
|
|
220
202
|
|
|
221
203
|
if isinstance(patch_content, str):
|
|
222
204
|
lines = [line for line in patch_content.splitlines() if line and not line.startswith("*** Begin Patch")]
|
|
223
205
|
if lines:
|
|
224
|
-
|
|
206
|
+
arguments_column = Text(lines[0][: const.INVALID_TOOL_CALL_MAX_LENGTH], ThemeKey.TOOL_PARAM)
|
|
225
207
|
else:
|
|
226
|
-
|
|
208
|
+
arguments_column = Text(
|
|
227
209
|
str(patch_content)[: const.INVALID_TOOL_CALL_MAX_LENGTH],
|
|
228
210
|
ThemeKey.INVALID_TOOL_CALL_ARGS,
|
|
229
211
|
)
|
|
230
212
|
|
|
231
|
-
|
|
232
|
-
grid.add_row(header, summary)
|
|
233
|
-
else:
|
|
234
|
-
grid.add_row(header, Text("", ThemeKey.TOOL_PARAM))
|
|
235
|
-
|
|
213
|
+
grid.add_row(tool_name_column, arguments_column)
|
|
236
214
|
return grid
|
|
237
215
|
|
|
238
216
|
|
|
239
217
|
def render_todo(tr: events.ToolResultEvent) -> RenderableType:
|
|
240
|
-
if tr.ui_extra
|
|
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:
|
|
218
|
+
if not isinstance(tr.ui_extra, model.TodoListUIExtra):
|
|
247
219
|
return Text.assemble(
|
|
248
220
|
(" ✘", ThemeKey.ERROR_BOLD),
|
|
249
221
|
" ",
|
|
250
|
-
Text("(invalid ui_extra)", style=ThemeKey.ERROR),
|
|
222
|
+
Text("(no content)" if tr.ui_extra is None else "(invalid ui_extra)", style=ThemeKey.ERROR),
|
|
251
223
|
)
|
|
252
224
|
|
|
253
225
|
ui_extra = tr.ui_extra.todo_list
|
|
@@ -283,11 +255,9 @@ def render_generic_tool_result(result: str, *, is_error: bool = False) -> Render
|
|
|
283
255
|
def _extract_mermaid_link(
|
|
284
256
|
ui_extra: model.ToolResultUIExtra | None,
|
|
285
257
|
) -> model.MermaidLinkUIExtra | None:
|
|
286
|
-
if ui_extra
|
|
287
|
-
return
|
|
288
|
-
|
|
289
|
-
return None
|
|
290
|
-
return ui_extra.mermaid_link
|
|
258
|
+
if isinstance(ui_extra, model.MermaidLinkUIExtra):
|
|
259
|
+
return ui_extra
|
|
260
|
+
return None
|
|
291
261
|
|
|
292
262
|
|
|
293
263
|
def render_memory_tool_call(arguments: str) -> RenderableType:
|
|
@@ -380,11 +350,9 @@ def render_mermaid_tool_result(tr: events.ToolResultEvent) -> RenderableType:
|
|
|
380
350
|
def _extract_truncation(
|
|
381
351
|
ui_extra: model.ToolResultUIExtra | None,
|
|
382
352
|
) -> model.TruncationUIExtra | None:
|
|
383
|
-
if ui_extra
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
return None
|
|
387
|
-
return ui_extra.truncation
|
|
353
|
+
if isinstance(ui_extra, model.TruncationUIExtra):
|
|
354
|
+
return ui_extra
|
|
355
|
+
return None
|
|
388
356
|
|
|
389
357
|
|
|
390
358
|
def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
|
|
@@ -496,9 +464,7 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
|
|
|
496
464
|
|
|
497
465
|
|
|
498
466
|
def _extract_diff_text(ui_extra: model.ToolResultUIExtra | None) -> str | None:
|
|
499
|
-
if ui_extra
|
|
500
|
-
return None
|
|
501
|
-
if ui_extra.type == model.ToolResultUIExtraType.DIFF_TEXT:
|
|
467
|
+
if isinstance(ui_extra, model.DiffTextUIExtra):
|
|
502
468
|
return ui_extra.diff_text
|
|
503
469
|
return None
|
|
504
470
|
|
klaude_code/ui/rich/markdown.py
CHANGED
|
@@ -102,7 +102,6 @@ class MarkdownStream:
|
|
|
102
102
|
|
|
103
103
|
# Defer Live creation until the first update.
|
|
104
104
|
self.live: Live | None = None
|
|
105
|
-
self._live_started: bool = False
|
|
106
105
|
|
|
107
106
|
# Streaming control
|
|
108
107
|
self.when: float = 0.0 # Timestamp of last update
|
|
@@ -119,9 +118,10 @@ class MarkdownStream:
|
|
|
119
118
|
self.mark: str | None = mark
|
|
120
119
|
self.indent: int = max(indent, 0)
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
@property
|
|
122
|
+
def _live_started(self) -> bool:
|
|
123
|
+
"""Check if Live display has been started (derived from self.live)."""
|
|
124
|
+
return self.live is not None
|
|
125
125
|
|
|
126
126
|
def _render_markdown_to_lines(self, text: str) -> list[str]:
|
|
127
127
|
"""Render markdown text to a list of lines.
|
|
@@ -214,7 +214,7 @@ class MarkdownStream:
|
|
|
214
214
|
console=self.console,
|
|
215
215
|
)
|
|
216
216
|
self.live.start()
|
|
217
|
-
self._live_started
|
|
217
|
+
# Note: self._live_started is now a property derived from self.live
|
|
218
218
|
|
|
219
219
|
# If live rendering isn't available (e.g., after a final update), stop.
|
|
220
220
|
if self.live is None:
|