klaude-code 2.2.0__py3-none-any.whl → 2.4.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.
Files changed (82) hide show
  1. klaude_code/app/runtime.py +2 -15
  2. klaude_code/cli/list_model.py +30 -13
  3. klaude_code/cli/main.py +26 -10
  4. klaude_code/config/assets/builtin_config.yaml +177 -310
  5. klaude_code/config/config.py +158 -21
  6. klaude_code/config/{select_model.py → model_matcher.py} +41 -16
  7. klaude_code/config/sub_agent_model_helper.py +217 -0
  8. klaude_code/config/thinking.py +2 -2
  9. klaude_code/const.py +1 -1
  10. klaude_code/core/agent_profile.py +43 -5
  11. klaude_code/core/executor.py +129 -47
  12. klaude_code/core/manager/llm_clients_builder.py +17 -11
  13. klaude_code/core/prompts/prompt-nano-banana.md +1 -1
  14. klaude_code/core/tool/file/diff_builder.py +25 -18
  15. klaude_code/core/tool/sub_agent_tool.py +2 -1
  16. klaude_code/llm/anthropic/client.py +12 -9
  17. klaude_code/llm/anthropic/input.py +54 -29
  18. klaude_code/llm/client.py +1 -1
  19. klaude_code/llm/codex/client.py +2 -2
  20. klaude_code/llm/google/client.py +7 -7
  21. klaude_code/llm/google/input.py +23 -2
  22. klaude_code/llm/input_common.py +2 -2
  23. klaude_code/llm/openai_compatible/client.py +3 -3
  24. klaude_code/llm/openai_compatible/input.py +22 -13
  25. klaude_code/llm/openai_compatible/stream.py +1 -1
  26. klaude_code/llm/openrouter/client.py +4 -4
  27. klaude_code/llm/openrouter/input.py +35 -25
  28. klaude_code/llm/responses/client.py +5 -5
  29. klaude_code/llm/responses/input.py +96 -57
  30. klaude_code/protocol/commands.py +1 -2
  31. klaude_code/protocol/events/__init__.py +7 -1
  32. klaude_code/protocol/events/chat.py +10 -0
  33. klaude_code/protocol/events/system.py +4 -0
  34. klaude_code/protocol/llm_param.py +1 -1
  35. klaude_code/protocol/model.py +0 -26
  36. klaude_code/protocol/op.py +17 -5
  37. klaude_code/protocol/op_handler.py +5 -0
  38. klaude_code/protocol/sub_agent/AGENTS.md +28 -0
  39. klaude_code/protocol/sub_agent/__init__.py +10 -14
  40. klaude_code/protocol/sub_agent/image_gen.py +2 -1
  41. klaude_code/session/codec.py +2 -6
  42. klaude_code/session/session.py +13 -3
  43. klaude_code/skill/assets/create-plan/SKILL.md +3 -5
  44. klaude_code/tui/command/__init__.py +3 -6
  45. klaude_code/tui/command/clear_cmd.py +0 -1
  46. klaude_code/tui/command/command_abc.py +6 -4
  47. klaude_code/tui/command/copy_cmd.py +10 -10
  48. klaude_code/tui/command/debug_cmd.py +11 -10
  49. klaude_code/tui/command/export_online_cmd.py +18 -23
  50. klaude_code/tui/command/fork_session_cmd.py +39 -43
  51. klaude_code/tui/command/model_cmd.py +10 -49
  52. klaude_code/tui/command/model_picker.py +142 -0
  53. klaude_code/tui/command/refresh_cmd.py +0 -1
  54. klaude_code/tui/command/registry.py +15 -21
  55. klaude_code/tui/command/resume_cmd.py +10 -16
  56. klaude_code/tui/command/status_cmd.py +8 -12
  57. klaude_code/tui/command/sub_agent_model_cmd.py +185 -0
  58. klaude_code/tui/command/terminal_setup_cmd.py +8 -11
  59. klaude_code/tui/command/thinking_cmd.py +4 -6
  60. klaude_code/tui/commands.py +5 -0
  61. klaude_code/tui/components/bash_syntax.py +1 -1
  62. klaude_code/tui/components/command_output.py +96 -0
  63. klaude_code/tui/components/common.py +1 -1
  64. klaude_code/tui/components/developer.py +3 -115
  65. klaude_code/tui/components/metadata.py +1 -63
  66. klaude_code/tui/components/rich/cjk_wrap.py +3 -2
  67. klaude_code/tui/components/rich/status.py +49 -3
  68. klaude_code/tui/components/rich/theme.py +2 -0
  69. klaude_code/tui/components/sub_agent.py +25 -46
  70. klaude_code/tui/components/welcome.py +99 -0
  71. klaude_code/tui/input/prompt_toolkit.py +19 -8
  72. klaude_code/tui/machine.py +5 -0
  73. klaude_code/tui/renderer.py +7 -8
  74. klaude_code/tui/runner.py +0 -6
  75. klaude_code/tui/terminal/selector.py +8 -6
  76. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/METADATA +21 -74
  77. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/RECORD +79 -76
  78. klaude_code/tui/command/help_cmd.py +0 -51
  79. klaude_code/tui/command/model_select.py +0 -84
  80. klaude_code/tui/command/release_notes_cmd.py +0 -85
  81. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/WHEEL +0 -0
  82. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/entry_points.txt +0 -0
@@ -1,24 +1,12 @@
1
- from importlib.metadata import PackageNotFoundError, version
2
-
3
1
  from rich.console import Group, RenderableType
4
2
  from rich.padding import Padding
5
3
  from rich.text import Text
6
4
 
7
5
  from klaude_code.const import DEFAULT_MAX_TOKENS
8
- from klaude_code.log import is_debug_enabled
9
6
  from klaude_code.protocol import events, model
10
7
  from klaude_code.tui.components.common import create_grid
11
- from klaude_code.tui.components.rich.quote import Quote
12
8
  from klaude_code.tui.components.rich.theme import ThemeKey
13
- from klaude_code.ui.common import format_model_params, format_number
14
-
15
-
16
- def _get_version() -> str:
17
- """Get the current version of klaude-code."""
18
- try:
19
- return version("klaude-code")
20
- except PackageNotFoundError:
21
- return "unknown"
9
+ from klaude_code.ui.common import format_number
22
10
 
23
11
 
24
12
  def _render_task_metadata_block(
@@ -195,53 +183,3 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
195
183
  renderables.append(Padding(grid, (0, 0, 0, 2)))
196
184
 
197
185
  return Group(*renderables)
198
-
199
-
200
- def render_welcome(e: events.WelcomeEvent) -> RenderableType:
201
- """Render the welcome panel with model info and settings.
202
-
203
- Args:
204
- e: The welcome event.
205
- """
206
- debug_mode = is_debug_enabled()
207
-
208
- panel_content = Text()
209
-
210
- if e.show_klaude_code_info:
211
- # First line: Klaude Code version
212
- klaude_code_style = ThemeKey.WELCOME_DEBUG_TITLE if debug_mode else ThemeKey.WELCOME_HIGHLIGHT_BOLD
213
- panel_content.append_text(Text("Klaude Code", style=klaude_code_style))
214
- panel_content.append_text(Text(f" v{_get_version()}", style=ThemeKey.WELCOME_INFO))
215
- panel_content.append_text(Text("\n"))
216
-
217
- # Model line: model @ provider · params...
218
- panel_content.append_text(
219
- Text.assemble(
220
- (str(e.llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
221
- (" @ ", ThemeKey.WELCOME_INFO),
222
- (e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
223
- )
224
- )
225
-
226
- # Use format_model_params for consistent formatting
227
- param_strings = format_model_params(e.llm_config)
228
-
229
- # Render config items with tree-style prefixes
230
- for i, param_str in enumerate(param_strings):
231
- is_last = i == len(param_strings) - 1
232
- prefix = "└─ " if is_last else "├─ "
233
- panel_content.append_text(
234
- Text.assemble(
235
- ("\n", ThemeKey.WELCOME_INFO),
236
- (prefix, ThemeKey.LINES),
237
- (param_str, ThemeKey.WELCOME_INFO),
238
- )
239
- )
240
-
241
- border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
242
-
243
- if e.show_klaude_code_info:
244
- groups = ["", Quote(panel_content, style=border_style, prefix="▌ "), ""]
245
- else:
246
- groups = [Quote(panel_content, style=border_style, prefix="▌ "), ""]
247
- return Group(*groups)
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import unicodedata
6
6
  from collections.abc import Callable
7
+ from typing import Any, cast
7
8
 
8
9
 
9
10
  def _is_cjk_char(ch: str) -> bool:
@@ -222,7 +223,7 @@ def install_rich_cjk_wrap_patch() -> bool:
222
223
 
223
224
  return break_positions
224
225
 
225
- _wrap.divide_line = divide_line_patched # pyright: ignore[reportPrivateImportUsage]
226
- _text.divide_line = divide_line_patched # pyright: ignore[reportPrivateImportUsage]
226
+ cast(Any, _wrap).divide_line = divide_line_patched # pyright: ignore[reportPrivateImportUsage]
227
+ cast(Any, _text).divide_line = divide_line_patched # pyright: ignore[reportPrivateImportUsage]
227
228
  _rich_cjk_wrap_patch_installed = True
228
229
  return True
@@ -263,6 +263,53 @@ def _breathing_style(console: Console, base_style: Style, intensity: float) -> S
263
263
  return base_style + Style(color=breathing_color)
264
264
 
265
265
 
266
+ def truncate_left(text: Text, max_cells: int, *, console: Console, ellipsis: str = "…") -> Text:
267
+ """Left-truncate Text to fit within max_cells.
268
+
269
+ Keeps the rightmost part of the text and prepends an ellipsis when truncation occurs.
270
+ Uses cell width so wide characters are handled reasonably.
271
+ """
272
+
273
+ max_cells = max(0, int(max_cells))
274
+ if max_cells == 0:
275
+ return Text("")
276
+
277
+ if cell_len(text.plain) <= max_cells:
278
+ return text
279
+
280
+ ellipsis_cells = cell_len(ellipsis)
281
+ if max_cells <= ellipsis_cells:
282
+ # Not enough space to show any meaningful suffix.
283
+ clipped = Text(ellipsis, style=text.style)
284
+ clipped.truncate(max_cells, overflow="crop", pad=False)
285
+ return clipped
286
+
287
+ suffix_budget = max_cells - ellipsis_cells
288
+ plain = text.plain
289
+
290
+ suffix_cells = 0
291
+ start_index = len(plain)
292
+ for i in range(len(plain) - 1, -1, -1):
293
+ ch_cells = cell_len(plain[i])
294
+ if suffix_cells + ch_cells > suffix_budget:
295
+ break
296
+ suffix_cells += ch_cells
297
+ start_index = i
298
+ if suffix_cells == suffix_budget:
299
+ break
300
+
301
+ if start_index >= len(plain):
302
+ return Text(ellipsis, style=text.style)
303
+
304
+ suffix = text[start_index:]
305
+ try:
306
+ ellipsis_style = suffix.get_style_at_offset(console, 0)
307
+ except Exception:
308
+ ellipsis_style = suffix.style or text.style
309
+
310
+ return Text.assemble(Text(ellipsis, style=ellipsis_style), suffix)
311
+
312
+
266
313
  class ShimmerStatusText:
267
314
  """Renderable status line with shimmer effect on the main text and hint.
268
315
 
@@ -322,12 +369,11 @@ class _StatusLeftText:
322
369
  # If the hint itself can't fit, fall back to truncating the combined text.
323
370
  if max_width <= hint_cells:
324
371
  combined = Text.assemble(main_text, hint_text)
325
- combined.truncate(max(1, max_width), overflow="ellipsis", pad=False)
326
- yield combined
372
+ yield truncate_left(combined, max(1, max_width), console=console)
327
373
  return
328
374
 
329
375
  main_budget = max_width - hint_cells
330
- main_text.truncate(max(1, main_budget), overflow="ellipsis", pad=False)
376
+ main_text = truncate_left(main_text, max(1, main_budget), console=console)
331
377
  yield Text.assemble(main_text, hint_text)
332
378
 
333
379
 
@@ -191,6 +191,7 @@ class ThemeKey(str, Enum):
191
191
  WELCOME_HIGHLIGHT_BOLD = "welcome.highlight.bold"
192
192
  WELCOME_HIGHLIGHT = "welcome.highlight"
193
193
  WELCOME_INFO = "welcome.info"
194
+ WELCOME_INFO_BOLD = "welcome.info.bold"
194
195
  # WELCOME DEBUG
195
196
  WELCOME_DEBUG_TITLE = "welcome.debug.title"
196
197
  WELCOME_DEBUG_BORDER = "welcome.debug.border"
@@ -307,6 +308,7 @@ def get_theme(theme: str | None = None) -> Themes:
307
308
  ThemeKey.WELCOME_HIGHLIGHT_BOLD.value: "bold",
308
309
  ThemeKey.WELCOME_HIGHLIGHT.value: palette.blue,
309
310
  ThemeKey.WELCOME_INFO.value: palette.grey1,
311
+ ThemeKey.WELCOME_INFO_BOLD.value: "bold " + palette.grey1,
310
312
  # WELCOME DEBUG
311
313
  ThemeKey.WELCOME_DEBUG_TITLE.value: "bold " + palette.red,
312
314
  ThemeKey.WELCOME_DEBUG_BORDER.value: palette.red,
@@ -3,7 +3,7 @@ from typing import Any, cast
3
3
 
4
4
  from rich.console import Group, RenderableType
5
5
  from rich.json import JSON
6
- from rich.style import Style, StyleType
6
+ from rich.style import Style
7
7
  from rich.text import Text
8
8
 
9
9
  from klaude_code.const import SUB_AGENT_RESULT_MAX_LINES
@@ -79,65 +79,44 @@ def _extract_agent_id_footer(text: str) -> tuple[str, str | None]:
79
79
  def render_sub_agent_result(
80
80
  result: str,
81
81
  *,
82
- code_theme: str,
83
- style: StyleType | None = None,
84
82
  has_structured_output: bool = False,
85
83
  description: str | None = None,
86
84
  ) -> RenderableType:
87
85
  stripped_result = result.strip()
88
-
89
- # Extract agentId footer for separate styling
90
86
  main_content, agent_id_footer = _extract_agent_id_footer(stripped_result)
91
87
  stripped_result = main_content.strip()
92
88
 
93
- # Use rich JSON for structured output
89
+ elements: list[RenderableType] = []
90
+ if description:
91
+ elements.append(Text(f"---\n{description}", style=ThemeKey.TOOL_RESULT))
92
+
93
+ # Try structured JSON output first
94
+ use_text_rendering = True
94
95
  if has_structured_output:
95
96
  try:
96
- group_elements: list[RenderableType] = [
97
- Text(
98
- "use /export to view full output",
99
- style=ThemeKey.TOOL_RESULT,
100
- ),
101
- JSON(stripped_result),
102
- ]
103
- if description:
104
- group_elements.insert(0, Text(f"\n{description}", style=style or ""))
105
- if agent_id_footer:
106
- group_elements.append(Text(agent_id_footer, style=ThemeKey.SUB_AGENT_FOOTER))
107
- return Group(*group_elements)
97
+ elements.append(Text("use /export to view full output", style=ThemeKey.TOOL_RESULT_TRUNCATED))
98
+ elements.append(JSON(stripped_result))
99
+ use_text_rendering = False
108
100
  except json.JSONDecodeError:
109
- # Fall back to markdown if not valid JSON
110
101
  pass
111
102
 
112
- if not stripped_result:
113
- return Text()
114
-
115
- lines = stripped_result.splitlines()
116
- if len(lines) > SUB_AGENT_RESULT_MAX_LINES:
117
- hidden_count = len(lines) - SUB_AGENT_RESULT_MAX_LINES
118
- tail_text = "\n".join(lines[-SUB_AGENT_RESULT_MAX_LINES:])
119
- truncated_elements: list[RenderableType] = []
120
- # Add description heading separately so it won't be truncated
121
- if description:
122
- truncated_elements.append(Text(f"---\n{description}", style=style or ""))
123
- truncated_elements.append(
124
- Text(
125
- f"( … more {hidden_count} lines)",
126
- style=ThemeKey.TOOL_RESULT_TRUNCATED,
127
- )
128
- )
129
- truncated_elements.append(Text(tail_text, style=style or ""))
130
- if agent_id_footer:
131
- truncated_elements.append(Text(agent_id_footer, style=ThemeKey.SUB_AGENT_FOOTER))
132
- return Group(*truncated_elements)
103
+ # Text rendering (either fallback or non-structured)
104
+ if use_text_rendering:
105
+ if not stripped_result:
106
+ return Text()
107
+
108
+ lines = stripped_result.splitlines()
109
+ if len(lines) > SUB_AGENT_RESULT_MAX_LINES:
110
+ hidden_count = len(lines) - SUB_AGENT_RESULT_MAX_LINES
111
+ elements.append(Text(f"( ... more {hidden_count} lines)", style=ThemeKey.TOOL_RESULT_TRUNCATED))
112
+ elements.append(Text("\n".join(lines[-SUB_AGENT_RESULT_MAX_LINES:]), style=ThemeKey.TOOL_RESULT))
113
+ else:
114
+ elements.append(Text(stripped_result, style=ThemeKey.TOOL_RESULT))
133
115
 
134
- # No truncation needed - add description heading if provided
135
- if description:
136
- stripped_result = f"\n# {description}\n\n{stripped_result}"
137
- normal_elements: list[RenderableType] = [Text(stripped_result)]
138
116
  if agent_id_footer:
139
- normal_elements.append(Text(agent_id_footer, style=ThemeKey.SUB_AGENT_FOOTER))
140
- return Group(*normal_elements)
117
+ elements.append(Text(agent_id_footer, style=ThemeKey.SUB_AGENT_FOOTER))
118
+
119
+ return Group(*elements)
141
120
 
142
121
 
143
122
  def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAgentState | None:
@@ -0,0 +1,99 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ from rich.console import Group, RenderableType
4
+ from rich.text import Text
5
+
6
+ from klaude_code.log import is_debug_enabled
7
+ from klaude_code.protocol import events
8
+ from klaude_code.tui.components.rich.quote import Quote
9
+ from klaude_code.tui.components.rich.theme import ThemeKey
10
+ from klaude_code.ui.common import format_model_params
11
+
12
+
13
+ def _get_version() -> str:
14
+ """Get the current version of klaude-code."""
15
+ try:
16
+ return version("klaude-code")
17
+ except PackageNotFoundError:
18
+ return "unknown"
19
+
20
+
21
+ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
22
+ """Render the welcome panel with model info and settings.
23
+
24
+ Args:
25
+ e: The welcome event.
26
+ """
27
+ debug_mode = is_debug_enabled()
28
+
29
+ panel_content = Text()
30
+
31
+ if e.show_klaude_code_info:
32
+ # First line: Klaude Code version
33
+ klaude_code_style = ThemeKey.WELCOME_DEBUG_TITLE if debug_mode else ThemeKey.WELCOME_HIGHLIGHT_BOLD
34
+ panel_content.append_text(Text("Klaude Code", style=klaude_code_style))
35
+ panel_content.append_text(Text(f" v{_get_version()}", style=ThemeKey.WELCOME_INFO))
36
+ panel_content.append_text(Text("\n"))
37
+
38
+ # Model line: model @ provider · params...
39
+ panel_content.append_text(
40
+ Text.assemble(
41
+ (str(e.llm_config.model_id), ThemeKey.WELCOME_HIGHLIGHT),
42
+ (" @ ", ThemeKey.WELCOME_INFO),
43
+ (e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
44
+ )
45
+ )
46
+
47
+ # Use format_model_params for consistent formatting
48
+ param_strings = format_model_params(e.llm_config)
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
+ # Render config items with tree-style prefixes
54
+ for i, param_str in enumerate(param_strings):
55
+ is_last = i == len(param_strings) - 1 and not has_sub_agents
56
+ prefix = "└─ " if is_last else "├─ "
57
+ panel_content.append_text(
58
+ Text.assemble(
59
+ ("\n", ThemeKey.WELCOME_INFO),
60
+ (prefix, ThemeKey.LINES),
61
+ (param_str, ThemeKey.WELCOME_INFO),
62
+ )
63
+ )
64
+
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
+ border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
94
+
95
+ if e.show_klaude_code_info:
96
+ groups = ["", Quote(panel_content, style=border_style, prefix="▌ "), ""]
97
+ else:
98
+ groups = [Quote(panel_content, style=border_style, prefix="▌ "), ""]
99
+ return Group(*groups)
@@ -26,7 +26,7 @@ from prompt_toolkit.styles import Style
26
26
  from prompt_toolkit.utils import get_cwidth
27
27
 
28
28
  from klaude_code.config import load_config
29
- from klaude_code.config.config import ModelEntry
29
+ from klaude_code.config.model_matcher import match_model_from_config
30
30
  from klaude_code.config.thinking import (
31
31
  format_current_thinking,
32
32
  get_thinking_picker_data,
@@ -452,22 +452,29 @@ class PromptToolkitInput(InputProviderABC):
452
452
  # -------------------------------------------------------------------------
453
453
 
454
454
  def _build_model_picker_items(self) -> tuple[list[SelectItem[str]], str | None]:
455
- config = load_config()
456
- models: list[ModelEntry] = sorted(
457
- config.iter_model_entries(only_available=True),
458
- key=lambda m: m.model_name.lower(),
459
- )
460
- if not models:
455
+ result = match_model_from_config()
456
+ if result.error_message or not result.filtered_models:
461
457
  return [], None
462
458
 
463
- items = build_model_select_items(models)
459
+ items = build_model_select_items(result.filtered_models)
464
460
 
465
461
  initial = None
466
462
  if self._get_current_model_config_name is not None:
467
463
  with contextlib.suppress(Exception):
468
464
  initial = self._get_current_model_config_name()
469
465
  if initial is None:
466
+ config = load_config()
470
467
  initial = config.main_model
468
+ if isinstance(initial, str) and initial and "@" not in initial:
469
+ config = load_config()
470
+ try:
471
+ resolved = config.resolve_model_location_prefer_available(initial) or config.resolve_model_location(
472
+ initial
473
+ )
474
+ except ValueError:
475
+ resolved = None
476
+ if resolved is not None:
477
+ initial = f"{resolved[0]}@{resolved[1]}"
471
478
  return items, initial
472
479
 
473
480
  def _open_model_picker(self) -> None:
@@ -608,6 +615,10 @@ class PromptToolkitInput(InputProviderABC):
608
615
  (symbol_style, " ctrl-t "),
609
616
  (text_style, " "),
610
617
  (text_style, "think"),
618
+ (text_style, " "),
619
+ (symbol_style, " ctrl-v "),
620
+ (text_style, " "),
621
+ (text_style, "paste image"),
611
622
  ]
612
623
  )
613
624
 
@@ -21,6 +21,7 @@ from klaude_code.tui.commands import (
21
21
  PrintRuleLine,
22
22
  RenderAssistantImage,
23
23
  RenderCommand,
24
+ RenderCommandOutput,
24
25
  RenderDeveloperMessage,
25
26
  RenderError,
26
27
  RenderInterrupt,
@@ -365,6 +366,10 @@ class DisplayStateMachine:
365
366
  cmds.append(RenderDeveloperMessage(e))
366
367
  return cmds
367
368
 
369
+ case events.CommandOutputEvent() as e:
370
+ cmds.append(RenderCommandOutput(e))
371
+ return cmds
372
+
368
373
  case events.TurnStartEvent() as e:
369
374
  cmds.append(RenderTurnStart(e))
370
375
  self._spinner.clear_for_new_turn()
@@ -31,6 +31,7 @@ from klaude_code.tui.commands import (
31
31
  PrintRuleLine,
32
32
  RenderAssistantImage,
33
33
  RenderCommand,
34
+ RenderCommandOutput,
34
35
  RenderDeveloperMessage,
35
36
  RenderError,
36
37
  RenderInterrupt,
@@ -53,6 +54,7 @@ from klaude_code.tui.commands import (
53
54
  TaskClockStart,
54
55
  )
55
56
  from klaude_code.tui.components import assistant as c_assistant
57
+ from klaude_code.tui.components import command_output as c_command_output
56
58
  from klaude_code.tui.components import developer as c_developer
57
59
  from klaude_code.tui.components import errors as c_errors
58
60
  from klaude_code.tui.components import mermaid_viewer as c_mermaid_viewer
@@ -61,6 +63,7 @@ from klaude_code.tui.components import sub_agent as c_sub_agent
61
63
  from klaude_code.tui.components import thinking as c_thinking
62
64
  from klaude_code.tui.components import tools as c_tools
63
65
  from klaude_code.tui.components import user_input as c_user_input
66
+ from klaude_code.tui.components import welcome as c_welcome
64
67
  from klaude_code.tui.components.common import truncate_head, truncate_middle
65
68
  from klaude_code.tui.components.rich import status as r_status
66
69
  from klaude_code.tui.components.rich.live import CropAboveLive, SingleLine
@@ -470,7 +473,6 @@ class TUICommandRenderer:
470
473
  self.print()
471
474
  case events.DeveloperMessageEvent() as e:
472
475
  self.display_developer_message(e)
473
- self.display_command_output(e)
474
476
  case events.UserMessageEvent() as e:
475
477
  if is_sub_agent:
476
478
  continue
@@ -503,15 +505,13 @@ class TUICommandRenderer:
503
505
  with self.session_print_context(e.session_id):
504
506
  self.print(c_developer.render_developer_message(e))
505
507
 
506
- def display_command_output(self, e: events.DeveloperMessageEvent) -> None:
507
- if not c_developer.get_command_output(e.item):
508
- return
508
+ def display_command_output(self, e: events.CommandOutputEvent) -> None:
509
509
  with self.session_print_context(e.session_id):
510
- self.print(c_developer.render_command_output(e))
510
+ self.print(c_command_output.render_command_output(e))
511
511
  self.print()
512
512
 
513
513
  def display_welcome(self, event: events.WelcomeEvent) -> None:
514
- self.print(c_metadata.render_welcome(event))
514
+ self.print(c_welcome.render_welcome(event))
515
515
 
516
516
  def display_user_message(self, event: events.UserMessageEvent) -> None:
517
517
  self.print(c_user_input.render_user_input(event.content))
@@ -567,10 +567,8 @@ class TUICommandRenderer:
567
567
  self.print(
568
568
  c_sub_agent.render_sub_agent_result(
569
569
  event.task_result,
570
- code_theme=self.themes.code_theme,
571
570
  has_structured_output=event.has_structured_output,
572
571
  description=description,
573
- style=ThemeKey.TOOL_RESULT,
574
572
  )
575
573
  )
576
574
 
@@ -628,6 +626,7 @@ class TUICommandRenderer:
628
626
  self.display_task_start(event)
629
627
  case RenderDeveloperMessage(event=event):
630
628
  self.display_developer_message(event)
629
+ case RenderCommandOutput(event=event):
631
630
  self.display_command_output(event)
632
631
  case RenderTurnStart(event=event):
633
632
  self.display_turn_start(event)
klaude_code/tui/runner.py CHANGED
@@ -76,14 +76,8 @@ async def submit_user_input_payload(
76
76
  if len(run_ops) > 1:
77
77
  raise ValueError("Multiple RunAgentOperation results are not supported")
78
78
 
79
- for run_op in run_ops:
80
- run_op.persist_user_input = cmd_result.persist
81
- run_op.emit_user_message_event = False
82
-
83
79
  if cmd_result.events:
84
80
  for evt in cmd_result.events:
85
- if cmd_result.persist and isinstance(evt, events.DeveloperMessageEvent):
86
- agent.session.append_history([evt.item])
87
81
  await executor.context.emit_event(evt)
88
82
 
89
83
  submitted_ids: list[str] = []
@@ -53,19 +53,21 @@ def build_model_select_items(models: list[Any]) -> list[SelectItem[str]]:
53
53
 
54
54
  items: list[SelectItem[str]] = []
55
55
  for idx, m in enumerate(models, 1):
56
- model_id = m.model_params.model or "N/A"
57
- first_line_prefix = f"{m.model_name:<{max_model_name_length}} "
56
+ model_id_str = m.model_id or "N/A"
57
+ # Display the base model name only; provider stays in the meta section.
58
+ display_name = m.model_name
59
+ first_line_prefix = f"{display_name:<{max_model_name_length}} → "
58
60
  meta_parts: list[str] = [m.provider]
59
- meta_parts.extend(format_model_params(m.model_params))
61
+ meta_parts.extend(format_model_params(m))
60
62
  meta_str = " · ".join(meta_parts)
61
63
  title = [
62
64
  ("class:meta", f"{idx:>{num_width}}. "),
63
65
  ("class:msg", first_line_prefix),
64
- ("class:msg bold", model_id),
66
+ ("class:msg bold", model_id_str),
65
67
  ("class:meta", f" {meta_str}\n"),
66
68
  ]
67
- search_text = f"{m.model_name} {model_id} {m.provider}"
68
- items.append(SelectItem(title=title, value=m.model_name, search_text=search_text))
69
+ search_text = f"{m.selector} {m.model_name} {model_id_str} {m.provider}"
70
+ items.append(SelectItem(title=title, value=m.selector, search_text=search_text))
69
71
 
70
72
  return items
71
73