klaude-code 2.4.1__py3-none-any.whl → 2.5.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/app/runtime.py +2 -6
- klaude_code/cli/main.py +0 -1
- klaude_code/config/assets/builtin_config.yaml +7 -0
- klaude_code/const.py +7 -4
- klaude_code/core/agent.py +10 -1
- klaude_code/core/agent_profile.py +47 -35
- klaude_code/core/executor.py +6 -21
- klaude_code/core/manager/sub_agent_manager.py +17 -1
- klaude_code/core/prompts/prompt-sub-agent-web.md +4 -4
- klaude_code/core/task.py +65 -4
- klaude_code/core/tool/__init__.py +0 -5
- klaude_code/core/tool/context.py +12 -1
- klaude_code/core/tool/offload.py +311 -0
- klaude_code/core/tool/shell/bash_tool.md +1 -43
- klaude_code/core/tool/sub_agent_tool.py +1 -0
- klaude_code/core/tool/todo/todo_write_tool.md +0 -23
- klaude_code/core/tool/tool_runner.py +14 -9
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +14 -39
- klaude_code/core/turn.py +128 -138
- klaude_code/llm/anthropic/client.py +176 -82
- klaude_code/llm/bedrock/client.py +8 -12
- klaude_code/llm/claude/client.py +11 -15
- klaude_code/llm/client.py +31 -4
- klaude_code/llm/codex/client.py +7 -11
- klaude_code/llm/google/client.py +150 -69
- klaude_code/llm/openai_compatible/client.py +10 -15
- klaude_code/llm/openai_compatible/stream.py +68 -6
- klaude_code/llm/openrouter/client.py +9 -15
- klaude_code/llm/partial_message.py +35 -0
- klaude_code/llm/responses/client.py +134 -68
- klaude_code/llm/usage.py +30 -0
- klaude_code/protocol/commands.py +0 -4
- klaude_code/protocol/events/metadata.py +1 -0
- klaude_code/protocol/events/streaming.py +1 -0
- klaude_code/protocol/events/system.py +0 -4
- klaude_code/protocol/model.py +2 -15
- klaude_code/protocol/sub_agent/explore.py +0 -10
- klaude_code/protocol/sub_agent/image_gen.py +0 -7
- klaude_code/protocol/sub_agent/task.py +0 -10
- klaude_code/protocol/sub_agent/web.py +4 -12
- klaude_code/session/templates/export_session.html +4 -4
- klaude_code/skill/manager.py +2 -1
- klaude_code/tui/components/metadata.py +41 -49
- klaude_code/tui/components/rich/markdown.py +1 -3
- klaude_code/tui/components/rich/theme.py +2 -2
- klaude_code/tui/components/sub_agent.py +9 -1
- klaude_code/tui/components/tools.py +0 -31
- klaude_code/tui/components/welcome.py +1 -32
- klaude_code/tui/input/prompt_toolkit.py +25 -9
- klaude_code/tui/machine.py +40 -8
- klaude_code/tui/renderer.py +1 -0
- {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/METADATA +2 -2
- {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/RECORD +56 -56
- klaude_code/core/prompts/prompt-nano-banana.md +0 -1
- klaude_code/core/tool/truncation.py +0 -203
- {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.4.1.dist-info → klaude_code-2.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -32,51 +32,40 @@ def _render_task_metadata_block(
|
|
|
32
32
|
currency_symbol = "¥" if currency == "CNY" else "$"
|
|
33
33
|
|
|
34
34
|
# First column: mark only
|
|
35
|
-
mark = Text("└", style=ThemeKey.METADATA_DIM) if is_sub_agent else Text("
|
|
35
|
+
mark = Text("└", style=ThemeKey.METADATA_DIM) if is_sub_agent else Text("◆", style=ThemeKey.METADATA)
|
|
36
36
|
|
|
37
|
-
# Second column: model@provider / tokens / cost / …
|
|
37
|
+
# Second column: model@provider description / tokens / cost / …
|
|
38
38
|
content = Text()
|
|
39
39
|
content.append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
|
|
40
40
|
if metadata.provider is not None:
|
|
41
41
|
content.append_text(Text("@", style=ThemeKey.METADATA)).append_text(
|
|
42
42
|
Text(metadata.provider.lower().replace(" ", "-"), style=ThemeKey.METADATA)
|
|
43
43
|
)
|
|
44
|
+
if metadata.description:
|
|
45
|
+
content.append_text(Text(" ", style=ThemeKey.METADATA)).append_text(
|
|
46
|
+
Text(metadata.description, style=ThemeKey.METADATA_DIM)
|
|
47
|
+
)
|
|
44
48
|
|
|
45
49
|
# All info parts (tokens, cost, context, etc.)
|
|
46
50
|
parts: list[Text] = []
|
|
47
51
|
|
|
48
52
|
if metadata.usage is not None:
|
|
49
|
-
# Tokens: ↑
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
# Tokens: ↑37k ◎5k ↓907 ∿45k ⌗ 100
|
|
54
|
+
token_text = Text()
|
|
55
|
+
token_text.append("↑", style=ThemeKey.METADATA_DIM)
|
|
56
|
+
token_text.append(format_number(metadata.usage.input_tokens), style=ThemeKey.METADATA)
|
|
53
57
|
if metadata.usage.cached_tokens > 0:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
)
|
|
60
|
-
token_parts.append(
|
|
61
|
-
Text.assemble(
|
|
62
|
-
("↓", ThemeKey.METADATA_DIM), (format_number(metadata.usage.output_tokens), ThemeKey.METADATA)
|
|
63
|
-
)
|
|
64
|
-
)
|
|
58
|
+
token_text.append(" ◎", style=ThemeKey.METADATA_DIM)
|
|
59
|
+
token_text.append(format_number(metadata.usage.cached_tokens), style=ThemeKey.METADATA)
|
|
60
|
+
token_text.append(" ↓", style=ThemeKey.METADATA_DIM)
|
|
61
|
+
token_text.append(format_number(metadata.usage.output_tokens), style=ThemeKey.METADATA)
|
|
65
62
|
if metadata.usage.reasoning_tokens > 0:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
("think ", ThemeKey.METADATA_DIM),
|
|
69
|
-
(format_number(metadata.usage.reasoning_tokens), ThemeKey.METADATA),
|
|
70
|
-
)
|
|
71
|
-
)
|
|
63
|
+
token_text.append(" ∿", style=ThemeKey.METADATA_DIM)
|
|
64
|
+
token_text.append(format_number(metadata.usage.reasoning_tokens), style=ThemeKey.METADATA)
|
|
72
65
|
if metadata.usage.image_tokens > 0:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(format_number(metadata.usage.image_tokens), ThemeKey.METADATA),
|
|
77
|
-
)
|
|
78
|
-
)
|
|
79
|
-
parts.append(Text(" · ").join(token_parts))
|
|
66
|
+
token_text.append(" ⌗ ", style=ThemeKey.METADATA_DIM)
|
|
67
|
+
token_text.append(format_number(metadata.usage.image_tokens), style=ThemeKey.METADATA)
|
|
68
|
+
parts.append(token_text)
|
|
80
69
|
|
|
81
70
|
# Cost
|
|
82
71
|
if metadata.usage is not None and metadata.usage.total_cost is not None:
|
|
@@ -87,41 +76,41 @@ def _render_task_metadata_block(
|
|
|
87
76
|
)
|
|
88
77
|
)
|
|
89
78
|
if metadata.usage is not None:
|
|
90
|
-
# Context usage
|
|
79
|
+
# Context usage: 31k/168k(18.4%)
|
|
91
80
|
if show_context_and_time and metadata.usage.context_usage_percent is not None:
|
|
92
81
|
context_size = format_number(metadata.usage.context_size or 0)
|
|
93
|
-
# Calculate effective limit (same as Usage.context_usage_percent)
|
|
94
82
|
effective_limit = (metadata.usage.context_limit or 0) - (metadata.usage.max_tokens or DEFAULT_MAX_TOKENS)
|
|
95
83
|
effective_limit_str = format_number(effective_limit) if effective_limit > 0 else "?"
|
|
96
84
|
parts.append(
|
|
97
85
|
Text.assemble(
|
|
98
|
-
("context ", ThemeKey.METADATA_DIM),
|
|
99
86
|
(context_size, ThemeKey.METADATA),
|
|
100
87
|
("/", ThemeKey.METADATA_DIM),
|
|
101
88
|
(effective_limit_str, ThemeKey.METADATA),
|
|
102
|
-
(f"
|
|
89
|
+
(f"({metadata.usage.context_usage_percent:.1f}%)", ThemeKey.METADATA_DIM),
|
|
103
90
|
)
|
|
104
91
|
)
|
|
105
92
|
|
|
106
|
-
# TPS
|
|
93
|
+
# TPS: 45.2tps
|
|
107
94
|
if metadata.usage.throughput_tps is not None:
|
|
108
95
|
parts.append(
|
|
109
96
|
Text.assemble(
|
|
110
|
-
(f"{metadata.usage.throughput_tps:.1f}
|
|
111
|
-
("
|
|
97
|
+
(f"{metadata.usage.throughput_tps:.1f}", ThemeKey.METADATA),
|
|
98
|
+
("tps", ThemeKey.METADATA_DIM),
|
|
112
99
|
)
|
|
113
100
|
)
|
|
114
101
|
|
|
115
|
-
# First token latency
|
|
102
|
+
# First token latency: 100ms-ftl / 2.1s-ftl
|
|
116
103
|
if metadata.usage.first_token_latency_ms is not None:
|
|
104
|
+
ftl_ms = metadata.usage.first_token_latency_ms
|
|
105
|
+
ftl_str = f"{ftl_ms / 1000:.1f}s" if ftl_ms >= 1000 else f"{ftl_ms:.0f}ms"
|
|
117
106
|
parts.append(
|
|
118
107
|
Text.assemble(
|
|
119
|
-
(
|
|
120
|
-
("
|
|
108
|
+
(ftl_str, ThemeKey.METADATA),
|
|
109
|
+
("-ftl", ThemeKey.METADATA_DIM),
|
|
121
110
|
)
|
|
122
111
|
)
|
|
123
112
|
|
|
124
|
-
# Duration
|
|
113
|
+
# Duration: 12.5s
|
|
125
114
|
if show_context_and_time and metadata.task_duration_s is not None:
|
|
126
115
|
parts.append(
|
|
127
116
|
Text.assemble(
|
|
@@ -130,18 +119,19 @@ def _render_task_metadata_block(
|
|
|
130
119
|
)
|
|
131
120
|
)
|
|
132
121
|
|
|
133
|
-
# Turn count
|
|
122
|
+
# Turn count: 1step / 3steps
|
|
134
123
|
if show_context_and_time and metadata.turn_count > 0:
|
|
124
|
+
suffix = "step" if metadata.turn_count == 1 else "steps"
|
|
135
125
|
parts.append(
|
|
136
126
|
Text.assemble(
|
|
137
127
|
(str(metadata.turn_count), ThemeKey.METADATA),
|
|
138
|
-
(
|
|
128
|
+
(suffix, ThemeKey.METADATA_DIM),
|
|
139
129
|
)
|
|
140
130
|
)
|
|
141
131
|
|
|
142
132
|
if parts:
|
|
143
|
-
content.append_text(Text("
|
|
144
|
-
content.append_text(Text("
|
|
133
|
+
content.append_text(Text(" ", style=ThemeKey.METADATA_DIM))
|
|
134
|
+
content.append_text(Text(" ", style=ThemeKey.METADATA_DIM).join(parts))
|
|
145
135
|
|
|
146
136
|
grid.add_row(mark, content)
|
|
147
137
|
return grid if not is_sub_agent else Padding(grid, (0, 0, 0, 2))
|
|
@@ -151,6 +141,9 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
|
151
141
|
"""Render task metadata including main agent and sub-agents."""
|
|
152
142
|
renderables: list[RenderableType] = []
|
|
153
143
|
|
|
144
|
+
if e.cancelled:
|
|
145
|
+
renderables.append(Text())
|
|
146
|
+
|
|
154
147
|
renderables.append(
|
|
155
148
|
_render_task_metadata_block(e.metadata.main_agent, is_sub_agent=False, show_context_and_time=True)
|
|
156
149
|
)
|
|
@@ -176,10 +169,9 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
|
176
169
|
("Σ ", ThemeKey.METADATA_DIM),
|
|
177
170
|
("total ", ThemeKey.METADATA_DIM),
|
|
178
171
|
(currency_symbol, ThemeKey.METADATA_DIM),
|
|
179
|
-
(f"{total_cost:.4f}", ThemeKey.
|
|
172
|
+
(f"{total_cost:.4f}", ThemeKey.METADATA_DIM),
|
|
180
173
|
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
renderables.append(Padding(grid, (0, 0, 0, 2)))
|
|
174
|
+
|
|
175
|
+
renderables.append(Padding(total_line, (0, 0, 0, 2)))
|
|
184
176
|
|
|
185
177
|
return Group(*renderables)
|
|
@@ -61,10 +61,8 @@ class Divider(MarkdownElement):
|
|
|
61
61
|
|
|
62
62
|
class MarkdownTable(TableElement):
|
|
63
63
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
64
|
-
# rich.box.MARKDOWN intentionally includes a blank top/bottom edge row. Rather than
|
|
65
|
-
# post-processing rendered segments, disable outer edges to avoid emitting those rows.
|
|
66
64
|
table = Table(
|
|
67
|
-
box=box.
|
|
65
|
+
box=box.MINIMAL,
|
|
68
66
|
show_edge=False,
|
|
69
67
|
border_style=console.get_style("markdown.table.border"),
|
|
70
68
|
)
|
|
@@ -54,7 +54,7 @@ LIGHT_PALETTE = Palette(
|
|
|
54
54
|
grey3="#c4ced4",
|
|
55
55
|
grey_green="#96a096",
|
|
56
56
|
purple="#5f5fb7",
|
|
57
|
-
lavender="#
|
|
57
|
+
lavender="#7878b0",
|
|
58
58
|
diff_add="#2e5a32 on #dafbe1",
|
|
59
59
|
diff_add_char="#2e5a32 on #aceebb",
|
|
60
60
|
diff_remove="#82071e on #ffecec",
|
|
@@ -276,7 +276,7 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
276
276
|
ThemeKey.TOOL_PARAM.value: palette.green,
|
|
277
277
|
ThemeKey.TOOL_PARAM_BOLD.value: "bold " + palette.green,
|
|
278
278
|
ThemeKey.TOOL_RESULT.value: palette.grey_green,
|
|
279
|
-
ThemeKey.TOOL_RESULT_TREE_PREFIX.value: palette.grey3
|
|
279
|
+
ThemeKey.TOOL_RESULT_TREE_PREFIX.value: palette.grey3,
|
|
280
280
|
ThemeKey.TOOL_RESULT_BOLD.value: "bold " + palette.grey_green,
|
|
281
281
|
ThemeKey.TOOL_RESULT_TRUNCATED.value: palette.grey1 + " dim",
|
|
282
282
|
ThemeKey.TOOL_MARK.value: "bold",
|
|
@@ -81,6 +81,7 @@ def render_sub_agent_result(
|
|
|
81
81
|
*,
|
|
82
82
|
has_structured_output: bool = False,
|
|
83
83
|
description: str | None = None,
|
|
84
|
+
sub_agent_color: Style | None = None,
|
|
84
85
|
) -> RenderableType:
|
|
85
86
|
stripped_result = result.strip()
|
|
86
87
|
main_content, agent_id_footer = _extract_agent_id_footer(stripped_result)
|
|
@@ -88,7 +89,14 @@ def render_sub_agent_result(
|
|
|
88
89
|
|
|
89
90
|
elements: list[RenderableType] = []
|
|
90
91
|
if description:
|
|
91
|
-
elements.append(
|
|
92
|
+
elements.append(
|
|
93
|
+
Text(
|
|
94
|
+
f"---\n{description}",
|
|
95
|
+
style=Style(bold=True, color=sub_agent_color.color, frame=True)
|
|
96
|
+
if sub_agent_color
|
|
97
|
+
else ThemeKey.TOOL_RESULT_BOLD,
|
|
98
|
+
)
|
|
99
|
+
)
|
|
92
100
|
|
|
93
101
|
# Try structured JSON output first
|
|
94
102
|
use_text_rendering = True
|
|
@@ -498,31 +498,6 @@ def render_mermaid_tool_result(
|
|
|
498
498
|
return viewer
|
|
499
499
|
|
|
500
500
|
|
|
501
|
-
def _extract_truncation(
|
|
502
|
-
ui_extra: model.ToolResultUIExtra | None,
|
|
503
|
-
) -> model.TruncationUIExtra | None:
|
|
504
|
-
return ui_extra if isinstance(ui_extra, model.TruncationUIExtra) else None
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
|
|
508
|
-
"""Render truncation info for the user."""
|
|
509
|
-
truncated_kb = ui_extra.truncated_length / 1024
|
|
510
|
-
|
|
511
|
-
text = Text.assemble(
|
|
512
|
-
("Offload context to ", ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
513
|
-
(ui_extra.saved_file_path, ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
514
|
-
(f", {truncated_kb:.1f}KB truncated", ThemeKey.TOOL_RESULT_TRUNCATED),
|
|
515
|
-
)
|
|
516
|
-
text.no_wrap = True
|
|
517
|
-
text.overflow = "ellipsis"
|
|
518
|
-
return text
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
def get_truncation_info(tr: events.ToolResultEvent) -> model.TruncationUIExtra | None:
|
|
522
|
-
"""Extract truncation info from a tool result event."""
|
|
523
|
-
return _extract_truncation(tr.ui_extra)
|
|
524
|
-
|
|
525
|
-
|
|
526
501
|
def render_report_back_tool_call() -> RenderableType:
|
|
527
502
|
return _render_tool_call_tree(mark=MARK_DONE, tool_name="Report Back", details=None)
|
|
528
503
|
|
|
@@ -659,12 +634,6 @@ def render_tool_result(
|
|
|
659
634
|
rendered.append(r_diffs.render_structured_diff(item, show_file_name=show_file_name))
|
|
660
635
|
return wrap(Group(*rendered)) if rendered else None
|
|
661
636
|
|
|
662
|
-
# Show truncation info if output was truncated and saved to file
|
|
663
|
-
truncation_info = get_truncation_info(e)
|
|
664
|
-
if truncation_info:
|
|
665
|
-
result = render_generic_tool_result(e.result, is_error=e.is_error)
|
|
666
|
-
return wrap(Group(render_truncation_info(truncation_info), result))
|
|
667
|
-
|
|
668
637
|
diff_ui = _extract_diff(e.ui_extra)
|
|
669
638
|
md_ui = _extract_markdown_doc(e.ui_extra)
|
|
670
639
|
|
|
@@ -47,12 +47,9 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
|
|
|
47
47
|
# Use format_model_params for consistent formatting
|
|
48
48
|
param_strings = format_model_params(e.llm_config)
|
|
49
49
|
|
|
50
|
-
# Check if we have sub-agent models to show
|
|
51
|
-
has_sub_agents = e.show_sub_agent_models and e.sub_agent_models
|
|
52
|
-
|
|
53
50
|
# Render config items with tree-style prefixes
|
|
54
51
|
for i, param_str in enumerate(param_strings):
|
|
55
|
-
is_last = i == len(param_strings) - 1
|
|
52
|
+
is_last = i == len(param_strings) - 1
|
|
56
53
|
prefix = "└─ " if is_last else "├─ "
|
|
57
54
|
panel_content.append_text(
|
|
58
55
|
Text.assemble(
|
|
@@ -62,34 +59,6 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
|
|
|
62
59
|
)
|
|
63
60
|
)
|
|
64
61
|
|
|
65
|
-
# Render sub-agent models
|
|
66
|
-
if has_sub_agents:
|
|
67
|
-
# Add sub-agents header with tree prefix
|
|
68
|
-
panel_content.append_text(
|
|
69
|
-
Text.assemble(
|
|
70
|
-
("\n", ThemeKey.WELCOME_INFO),
|
|
71
|
-
("└─ ", ThemeKey.LINES),
|
|
72
|
-
("sub-agents:", ThemeKey.WELCOME_INFO),
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
sub_agent_items = list(e.sub_agent_models.items())
|
|
76
|
-
max_type_len = max(len(t) for t in e.sub_agent_models)
|
|
77
|
-
for i, (sub_agent_type, sub_llm_config) in enumerate(sub_agent_items):
|
|
78
|
-
is_last = i == len(sub_agent_items) - 1
|
|
79
|
-
prefix = "└─ " if is_last else "├─ "
|
|
80
|
-
panel_content.append_text(
|
|
81
|
-
Text.assemble(
|
|
82
|
-
("\n", ThemeKey.WELCOME_INFO),
|
|
83
|
-
(" ", ThemeKey.WELCOME_INFO), # Indentation for sub-items
|
|
84
|
-
(prefix, ThemeKey.LINES),
|
|
85
|
-
(sub_agent_type.lower().ljust(max_type_len), ThemeKey.WELCOME_INFO),
|
|
86
|
-
(": ", ThemeKey.LINES),
|
|
87
|
-
(str(sub_llm_config.model_id), ThemeKey.WELCOME_HIGHLIGHT),
|
|
88
|
-
(" @ ", ThemeKey.WELCOME_INFO),
|
|
89
|
-
(sub_llm_config.provider_name, ThemeKey.WELCOME_INFO),
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
|
|
93
62
|
border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
|
|
94
63
|
|
|
95
64
|
if e.show_klaude_code_info:
|
|
@@ -394,17 +394,14 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
394
394
|
with contextlib.suppress(Exception):
|
|
395
395
|
_patch_completion_menu_controls(self._session.app.layout.container)
|
|
396
396
|
|
|
397
|
-
# Reserve more vertical space while
|
|
397
|
+
# Reserve more vertical space while overlays (selector, completion menu) are open.
|
|
398
398
|
# prompt_toolkit's default multiline prompt caps out at ~9 lines.
|
|
399
|
-
self.
|
|
399
|
+
self._patch_prompt_height_for_overlays()
|
|
400
400
|
|
|
401
401
|
# Ensure completion menu has default selection
|
|
402
402
|
self._session.default_buffer.on_completions_changed += self._select_first_completion_on_open # pyright: ignore[reportUnknownMemberType]
|
|
403
403
|
|
|
404
|
-
def
|
|
405
|
-
if self._model_picker is None and self._thinking_picker is None:
|
|
406
|
-
return
|
|
407
|
-
|
|
404
|
+
def _patch_prompt_height_for_overlays(self) -> None:
|
|
408
405
|
with contextlib.suppress(Exception):
|
|
409
406
|
root = self._session.app.layout.container
|
|
410
407
|
input_window = _find_window_for_buffer(root, self._session.default_buffer)
|
|
@@ -417,14 +414,33 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
417
414
|
picker_open = (self._model_picker is not None and self._model_picker.is_open) or (
|
|
418
415
|
self._thinking_picker is not None and self._thinking_picker.is_open
|
|
419
416
|
)
|
|
420
|
-
|
|
421
|
-
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
complete_state = self._session.default_buffer.complete_state
|
|
420
|
+
completion_open = complete_state is not None and bool(complete_state.completions)
|
|
421
|
+
except Exception:
|
|
422
|
+
completion_open = False
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
original_height_value = original_height() if callable(original_height) else original_height
|
|
426
|
+
except Exception:
|
|
427
|
+
original_height_value = None
|
|
428
|
+
original_height_int = original_height_value if isinstance(original_height_value, int) else None
|
|
429
|
+
|
|
430
|
+
if picker_open or completion_open:
|
|
431
|
+
target_rows = 20 if picker_open else 14
|
|
432
|
+
|
|
433
|
+
# Cap to the current terminal size.
|
|
422
434
|
# Leave a small buffer to avoid triggering "Window too small".
|
|
423
435
|
try:
|
|
424
436
|
rows = get_app().output.get_size().rows
|
|
425
437
|
except Exception:
|
|
426
438
|
rows = 0
|
|
427
|
-
|
|
439
|
+
|
|
440
|
+
expanded = max(3, min(target_rows, rows - 2))
|
|
441
|
+
if original_height_int is not None:
|
|
442
|
+
expanded = max(original_height_int, expanded)
|
|
443
|
+
return expanded
|
|
428
444
|
|
|
429
445
|
if callable(original_height):
|
|
430
446
|
return original_height()
|
klaude_code/tui/machine.py
CHANGED
|
@@ -8,9 +8,10 @@ from klaude_code.const import (
|
|
|
8
8
|
SIGINT_DOUBLE_PRESS_EXIT_TEXT,
|
|
9
9
|
STATUS_COMPOSING_TEXT,
|
|
10
10
|
STATUS_DEFAULT_TEXT,
|
|
11
|
+
STATUS_SHOW_BUFFER_LENGTH,
|
|
11
12
|
STATUS_THINKING_TEXT,
|
|
12
13
|
)
|
|
13
|
-
from klaude_code.protocol import events, model
|
|
14
|
+
from klaude_code.protocol import events, model, tools
|
|
14
15
|
from klaude_code.tui.commands import (
|
|
15
16
|
AppendAssistant,
|
|
16
17
|
AppendThinking,
|
|
@@ -48,6 +49,33 @@ from klaude_code.tui.components.rich.theme import ThemeKey
|
|
|
48
49
|
from klaude_code.tui.components.thinking import extract_last_bold_header, normalize_thinking_content
|
|
49
50
|
from klaude_code.tui.components.tools import get_tool_active_form, is_sub_agent_tool
|
|
50
51
|
|
|
52
|
+
# Tools that complete quickly and don't benefit from streaming activity display.
|
|
53
|
+
# For models without fine-grained tool JSON streaming (e.g., Gemini), showing these
|
|
54
|
+
# in the activity state causes a flash-and-disappear effect.
|
|
55
|
+
FAST_TOOLS: frozenset[str] = frozenset(
|
|
56
|
+
{
|
|
57
|
+
tools.READ,
|
|
58
|
+
tools.EDIT,
|
|
59
|
+
tools.WRITE,
|
|
60
|
+
tools.BASH,
|
|
61
|
+
tools.TODO_WRITE,
|
|
62
|
+
tools.UPDATE_PLAN,
|
|
63
|
+
tools.APPLY_PATCH,
|
|
64
|
+
tools.REPORT_BACK,
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _should_skip_tool_activity(tool_name: str, model_id: str | None) -> bool:
|
|
70
|
+
"""Check if tool activity should be skipped for non-streaming models."""
|
|
71
|
+
if model_id is None:
|
|
72
|
+
return False
|
|
73
|
+
if tool_name not in FAST_TOOLS:
|
|
74
|
+
return False
|
|
75
|
+
# Gemini and Grok models don't stream tool JSON at fine granularity
|
|
76
|
+
model_lower = model_id.lower()
|
|
77
|
+
return "gemini" in model_lower or "grok" in model_lower
|
|
78
|
+
|
|
51
79
|
|
|
52
80
|
@dataclass
|
|
53
81
|
class SubAgentThinkingHeaderState:
|
|
@@ -142,7 +170,8 @@ class ActivityState:
|
|
|
142
170
|
|
|
143
171
|
if self._sub_agent_tool_calls:
|
|
144
172
|
_append_counts(self._sub_agent_tool_calls)
|
|
145
|
-
|
|
173
|
+
if self._tool_calls:
|
|
174
|
+
activity_text.append(" , ")
|
|
146
175
|
|
|
147
176
|
if self._tool_calls:
|
|
148
177
|
_append_counts(self._tool_calls)
|
|
@@ -152,7 +181,7 @@ class ActivityState:
|
|
|
152
181
|
if self._composing:
|
|
153
182
|
text = Text()
|
|
154
183
|
text.append(STATUS_COMPOSING_TEXT, style=ThemeKey.STATUS_TEXT)
|
|
155
|
-
if self._buffer_length > 0:
|
|
184
|
+
if STATUS_SHOW_BUFFER_LENGTH and self._buffer_length > 0:
|
|
156
185
|
text.append(f" ({self._buffer_length:,})", style=ThemeKey.STATUS_TEXT)
|
|
157
186
|
return text
|
|
158
187
|
|
|
@@ -497,11 +526,14 @@ class DisplayStateMachine:
|
|
|
497
526
|
|
|
498
527
|
self._spinner.set_composing(False)
|
|
499
528
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
529
|
+
# Skip activity state for fast tools on non-streaming models (e.g., Gemini)
|
|
530
|
+
# to avoid flash-and-disappear effect
|
|
531
|
+
if not _should_skip_tool_activity(e.tool_name, e.model_id):
|
|
532
|
+
tool_active_form = get_tool_active_form(e.tool_name)
|
|
533
|
+
if is_sub_agent_tool(e.tool_name):
|
|
534
|
+
self._spinner.add_sub_agent_tool_call(e.tool_call_id, tool_active_form)
|
|
535
|
+
else:
|
|
536
|
+
self._spinner.add_tool_call(tool_active_form)
|
|
505
537
|
|
|
506
538
|
cmds.extend(self._spinner_update_commands())
|
|
507
539
|
return cmds
|
klaude_code/tui/renderer.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -17,7 +17,7 @@ Requires-Dist: rich>=14.1.0
|
|
|
17
17
|
Requires-Dist: term-image>=0.7.2
|
|
18
18
|
Requires-Dist: trafilatura>=2.0.0
|
|
19
19
|
Requires-Dist: typer>=0.17.3
|
|
20
|
-
Requires-Python: >=3.13
|
|
20
|
+
Requires-Python: >=3.13, <3.14
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
# Klaude Code
|