klaude-code 2.3.0__py3-none-any.whl → 2.4.1__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 (54) hide show
  1. klaude_code/cli/list_model.py +3 -3
  2. klaude_code/cli/main.py +2 -2
  3. klaude_code/config/assets/builtin_config.yaml +165 -307
  4. klaude_code/config/config.py +17 -17
  5. klaude_code/config/{select_model.py → model_matcher.py} +7 -7
  6. klaude_code/config/sub_agent_model_helper.py +1 -10
  7. klaude_code/config/thinking.py +2 -2
  8. klaude_code/core/agent_profile.py +9 -23
  9. klaude_code/core/executor.py +72 -70
  10. klaude_code/core/tool/file/diff_builder.py +25 -18
  11. klaude_code/llm/anthropic/client.py +5 -5
  12. klaude_code/llm/client.py +1 -1
  13. klaude_code/llm/codex/client.py +2 -2
  14. klaude_code/llm/google/client.py +6 -6
  15. klaude_code/llm/input_common.py +2 -2
  16. klaude_code/llm/openai_compatible/client.py +3 -3
  17. klaude_code/llm/openai_compatible/stream.py +1 -1
  18. klaude_code/llm/openrouter/client.py +4 -4
  19. klaude_code/llm/openrouter/input.py +1 -3
  20. klaude_code/llm/responses/client.py +5 -5
  21. klaude_code/protocol/events/__init__.py +7 -1
  22. klaude_code/protocol/events/chat.py +10 -0
  23. klaude_code/protocol/llm_param.py +1 -1
  24. klaude_code/protocol/model.py +0 -26
  25. klaude_code/protocol/op.py +0 -5
  26. klaude_code/session/session.py +4 -2
  27. klaude_code/tui/command/clear_cmd.py +0 -1
  28. klaude_code/tui/command/command_abc.py +6 -4
  29. klaude_code/tui/command/copy_cmd.py +10 -10
  30. klaude_code/tui/command/debug_cmd.py +11 -10
  31. klaude_code/tui/command/export_online_cmd.py +18 -23
  32. klaude_code/tui/command/fork_session_cmd.py +39 -43
  33. klaude_code/tui/command/model_cmd.py +5 -7
  34. klaude_code/tui/command/{model_select.py → model_picker.py} +3 -5
  35. klaude_code/tui/command/refresh_cmd.py +0 -1
  36. klaude_code/tui/command/registry.py +15 -21
  37. klaude_code/tui/command/resume_cmd.py +10 -16
  38. klaude_code/tui/command/status_cmd.py +8 -12
  39. klaude_code/tui/command/sub_agent_model_cmd.py +11 -16
  40. klaude_code/tui/command/terminal_setup_cmd.py +8 -11
  41. klaude_code/tui/command/thinking_cmd.py +4 -6
  42. klaude_code/tui/commands.py +5 -0
  43. klaude_code/tui/components/command_output.py +96 -0
  44. klaude_code/tui/components/developer.py +3 -110
  45. klaude_code/tui/components/welcome.py +2 -2
  46. klaude_code/tui/input/prompt_toolkit.py +6 -8
  47. klaude_code/tui/machine.py +5 -0
  48. klaude_code/tui/renderer.py +5 -5
  49. klaude_code/tui/runner.py +0 -6
  50. klaude_code/tui/terminal/selector.py +7 -8
  51. {klaude_code-2.3.0.dist-info → klaude_code-2.4.1.dist-info}/METADATA +21 -74
  52. {klaude_code-2.3.0.dist-info → klaude_code-2.4.1.dist-info}/RECORD +54 -53
  53. {klaude_code-2.3.0.dist-info → klaude_code-2.4.1.dist-info}/WHEEL +0 -0
  54. {klaude_code-2.3.0.dist-info → klaude_code-2.4.1.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,7 @@ from importlib.resources import files
2
2
  from typing import TYPE_CHECKING
3
3
 
4
4
  from klaude_code.log import log_debug
5
- from klaude_code.protocol import commands, events, message, model, op
5
+ from klaude_code.protocol import commands, events, message, op
6
6
 
7
7
  from .command_abc import Agent, CommandResult
8
8
  from .prompt_command import PromptCommand
@@ -179,30 +179,24 @@ async def dispatch_command(user_input: message.UserInputPayload, agent: Agent, *
179
179
  result.operations = ops
180
180
  return result
181
181
  except Exception as e:
182
- command_output = (
183
- model.CommandOutput(command_name=command_identifier, is_error=True)
184
- if isinstance(command_identifier, commands.CommandName)
185
- else None
186
- )
187
- ui_extra = (
188
- model.build_command_output_extra(
189
- command_output.command_name,
190
- ui_extra=command_output.ui_extra,
191
- is_error=command_output.is_error,
182
+ error_content = f"Command {command_identifier} error: [{e.__class__.__name__}] {e!s}"
183
+ if isinstance(command_identifier, commands.CommandName):
184
+ return CommandResult(
185
+ events=[
186
+ events.CommandOutputEvent(
187
+ session_id=agent.session.id,
188
+ command_name=command_identifier,
189
+ content=error_content,
190
+ is_error=True,
191
+ )
192
+ ]
192
193
  )
193
- if command_output is not None
194
- else None
195
- )
196
194
  return CommandResult(
197
195
  events=[
198
- events.DeveloperMessageEvent(
196
+ events.ErrorEvent(
199
197
  session_id=agent.session.id,
200
- item=message.DeveloperMessage(
201
- parts=message.text_parts_from_str(
202
- f"Command {command_identifier} error: [{e.__class__.__name__}] {e!s}"
203
- ),
204
- ui_extra=ui_extra,
205
- ),
198
+ error_message=error_content,
199
+ can_retry=False,
206
200
  )
207
201
  ]
208
202
  )
@@ -3,7 +3,7 @@ import asyncio
3
3
  from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.log import log
6
- from klaude_code.protocol import commands, events, message, model, op
6
+ from klaude_code.protocol import commands, events, message, op
7
7
  from klaude_code.session.selector import build_session_select_options, format_user_messages_display
8
8
  from klaude_code.tui.terminal.selector import SelectItem, select_one
9
9
 
@@ -87,29 +87,23 @@ class ResumeCommand(CommandABC):
87
87
  del user_input # unused
88
88
 
89
89
  if agent.session.messages_count > 0:
90
- event = events.DeveloperMessageEvent(
90
+ event = events.CommandOutputEvent(
91
91
  session_id=agent.session.id,
92
- item=message.DeveloperMessage(
93
- parts=message.text_parts_from_str(
94
- "Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection."
95
- ),
96
- ui_extra=model.build_command_output_extra(self.name, is_error=True),
97
- ),
92
+ command_name=self.name,
93
+ content="Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection.",
94
+ is_error=True,
98
95
  )
99
- return CommandResult(events=[event], persist=False)
96
+ return CommandResult(events=[event])
100
97
 
101
98
  selected_session_id = await asyncio.to_thread(select_session_sync)
102
99
  if selected_session_id is None:
103
- event = events.DeveloperMessageEvent(
100
+ event = events.CommandOutputEvent(
104
101
  session_id=agent.session.id,
105
- item=message.DeveloperMessage(
106
- parts=message.text_parts_from_str("(no session selected)"),
107
- ui_extra=model.build_command_output_extra(self.name),
108
- ),
102
+ command_name=self.name,
103
+ content="(no session selected)",
109
104
  )
110
- return CommandResult(events=[event], persist=False)
105
+ return CommandResult(events=[event])
111
106
 
112
107
  return CommandResult(
113
108
  operations=[op.ResumeSessionOperation(target_session_id=selected_session_id)],
114
- persist=False,
115
109
  )
@@ -138,19 +138,15 @@ class StatusCommand(CommandABC):
138
138
  session = agent.session
139
139
  aggregated = accumulate_session_usage(session)
140
140
 
141
- event = events.DeveloperMessageEvent(
141
+ event = events.CommandOutputEvent(
142
142
  session_id=session.id,
143
- item=message.DeveloperMessage(
144
- parts=message.text_parts_from_str(format_status_content(aggregated)),
145
- ui_extra=model.build_command_output_extra(
146
- self.name,
147
- ui_extra=model.SessionStatusUIExtra(
148
- usage=aggregated.total,
149
- task_count=aggregated.task_count,
150
- by_model=aggregated.by_model,
151
- ),
152
- ),
143
+ command_name=self.name,
144
+ content=format_status_content(aggregated),
145
+ ui_extra=model.SessionStatusUIExtra(
146
+ usage=aggregated.total,
147
+ task_count=aggregated.task_count,
148
+ by_model=aggregated.by_model,
153
149
  ),
154
150
  )
155
151
 
156
- return CommandResult(events=[event], persist=False)
152
+ return CommandResult(events=[event])
@@ -8,7 +8,7 @@ from prompt_toolkit.styles import Style
8
8
 
9
9
  from klaude_code.config.config import load_config
10
10
  from klaude_code.config.sub_agent_model_helper import SubAgentModelHelper, SubAgentModelInfo
11
- from klaude_code.protocol import commands, events, message, model, op
11
+ from klaude_code.protocol import commands, events, message, op
12
12
  from klaude_code.tui.terminal.selector import SelectItem, build_model_select_items, select_one
13
13
 
14
14
  from .command_abc import Agent, CommandABC, CommandResult
@@ -136,12 +136,11 @@ class SubAgentModelCommand(CommandABC):
136
136
  if not sub_agents:
137
137
  return CommandResult(
138
138
  events=[
139
- events.DeveloperMessageEvent(
139
+ events.CommandOutputEvent(
140
140
  session_id=agent.session.id,
141
- item=message.DeveloperMessage(
142
- parts=message.text_parts_from_str("No sub-agents available"),
143
- ui_extra=model.build_command_output_extra(self.name),
144
- ),
141
+ command_name=self.name,
142
+ content="No sub-agents available",
143
+ is_error=True,
145
144
  )
146
145
  ]
147
146
  )
@@ -150,12 +149,10 @@ class SubAgentModelCommand(CommandABC):
150
149
  if selected_sub_agent is None:
151
150
  return CommandResult(
152
151
  events=[
153
- events.DeveloperMessageEvent(
152
+ events.CommandOutputEvent(
154
153
  session_id=agent.session.id,
155
- item=message.DeveloperMessage(
156
- parts=message.text_parts_from_str("(cancelled)"),
157
- ui_extra=model.build_command_output_extra(self.name),
158
- ),
154
+ command_name=self.name,
155
+ content="(cancelled)",
159
156
  )
160
157
  ]
161
158
  )
@@ -166,12 +163,10 @@ class SubAgentModelCommand(CommandABC):
166
163
  if selected_model is None:
167
164
  return CommandResult(
168
165
  events=[
169
- events.DeveloperMessageEvent(
166
+ events.CommandOutputEvent(
170
167
  session_id=agent.session.id,
171
- item=message.DeveloperMessage(
172
- parts=message.text_parts_from_str("(cancelled)"),
173
- ui_extra=model.build_command_output_extra(self.name),
174
- ),
168
+ command_name=self.name,
169
+ content="(cancelled)",
175
170
  )
176
171
  ]
177
172
  )
@@ -2,7 +2,7 @@ import os
2
2
  import subprocess
3
3
  from pathlib import Path
4
4
 
5
- from klaude_code.protocol import commands, events, message, model
5
+ from klaude_code.protocol import commands, events, message
6
6
 
7
7
  from .command_abc import Agent, CommandABC, CommandResult
8
8
 
@@ -226,12 +226,10 @@ class TerminalSetupCommand(CommandABC):
226
226
  """Create success result"""
227
227
  return CommandResult(
228
228
  events=[
229
- events.DeveloperMessageEvent(
229
+ events.CommandOutputEvent(
230
230
  session_id=agent.session.id,
231
- item=message.DeveloperMessage(
232
- parts=message.text_parts_from_str(msg),
233
- ui_extra=model.build_command_output_extra(self.name),
234
- ),
231
+ command_name=self.name,
232
+ content=msg,
235
233
  )
236
234
  ]
237
235
  )
@@ -240,12 +238,11 @@ class TerminalSetupCommand(CommandABC):
240
238
  """Create error result"""
241
239
  return CommandResult(
242
240
  events=[
243
- events.DeveloperMessageEvent(
241
+ events.CommandOutputEvent(
244
242
  session_id=agent.session.id,
245
- item=message.DeveloperMessage(
246
- parts=message.text_parts_from_str(msg),
247
- ui_extra=model.build_command_output_extra(self.name, is_error=True),
248
- ),
243
+ command_name=self.name,
244
+ content=msg,
245
+ is_error=True,
249
246
  )
250
247
  ]
251
248
  )
@@ -3,7 +3,7 @@ import asyncio
3
3
  from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.config.thinking import get_thinking_picker_data, parse_thinking_value
6
- from klaude_code.protocol import commands, events, llm_param, message, model, op
6
+ from klaude_code.protocol import commands, events, llm_param, message, op
7
7
  from klaude_code.tui.terminal.selector import SelectItem, select_one
8
8
 
9
9
  from .command_abc import Agent, CommandABC, CommandResult
@@ -79,12 +79,10 @@ class ThinkingCommand(CommandABC):
79
79
  if new_thinking is None:
80
80
  return CommandResult(
81
81
  events=[
82
- events.DeveloperMessageEvent(
82
+ events.CommandOutputEvent(
83
83
  session_id=agent.session.id,
84
- item=message.DeveloperMessage(
85
- parts=message.text_parts_from_str("(no change)"),
86
- ui_extra=model.build_command_output_extra(self.name),
87
- ),
84
+ command_name=self.name,
85
+ content="(no change)",
88
86
  )
89
87
  ]
90
88
  )
@@ -38,6 +38,11 @@ class RenderDeveloperMessage(RenderCommand):
38
38
  event: events.DeveloperMessageEvent
39
39
 
40
40
 
41
+ @dataclass(frozen=True, slots=True)
42
+ class RenderCommandOutput(RenderCommand):
43
+ event: events.CommandOutputEvent
44
+
45
+
41
46
  @dataclass(frozen=True, slots=True)
42
47
  class RenderTurnStart(RenderCommand):
43
48
  event: events.TurnStartEvent
@@ -0,0 +1,96 @@
1
+ from rich.console import RenderableType
2
+ from rich.padding import Padding
3
+ from rich.table import Table
4
+ from rich.text import Text
5
+
6
+ from klaude_code.protocol import events, model
7
+ from klaude_code.tui.components.common import truncate_middle
8
+ from klaude_code.tui.components.rich.theme import ThemeKey
9
+
10
+
11
+ def render_command_output(e: events.CommandOutputEvent) -> RenderableType:
12
+ """Render command output content."""
13
+ match e.command_name:
14
+ case "status":
15
+ return _render_status_output(e)
16
+ case "fork-session":
17
+ return _render_fork_session_output(e)
18
+ case _:
19
+ content = e.content or "(no content)"
20
+ style = ThemeKey.TOOL_RESULT if not e.is_error else ThemeKey.ERROR
21
+ return Padding.indent(truncate_middle(content, base_style=style), level=2)
22
+
23
+
24
+ def _format_tokens(tokens: int) -> str:
25
+ """Format token count with K/M suffix for readability."""
26
+ if tokens >= 1_000_000:
27
+ return f"{tokens / 1_000_000:.2f}M"
28
+ if tokens >= 1_000:
29
+ return f"{tokens / 1_000:.1f}K"
30
+ return str(tokens)
31
+
32
+
33
+ def _format_cost(cost: float | None, currency: str = "USD") -> str:
34
+ """Format cost with currency symbol."""
35
+ if cost is None:
36
+ return "-"
37
+ symbol = "Y" if currency == "CNY" else "$"
38
+ if cost < 0.01:
39
+ return f"{symbol}{cost:.4f}"
40
+ return f"{symbol}{cost:.2f}"
41
+
42
+
43
+ def _render_fork_session_output(e: events.CommandOutputEvent) -> RenderableType:
44
+ """Render fork session output with usage instructions."""
45
+ if not isinstance(e.ui_extra, model.SessionIdUIExtra):
46
+ return Padding.indent(Text(e.content, style=ThemeKey.TOOL_RESULT), level=2)
47
+
48
+ grid = Table.grid(padding=(0, 1))
49
+ session_id = e.ui_extra.session_id
50
+ grid.add_column(style=ThemeKey.TOOL_RESULT, overflow="fold")
51
+
52
+ grid.add_row(Text("Session forked. Resume command copied to clipboard:", style=ThemeKey.TOOL_RESULT))
53
+ grid.add_row(Text(f" klaude --resume-by-id {session_id}", style=ThemeKey.TOOL_RESULT_BOLD))
54
+
55
+ return Padding.indent(grid, level=2)
56
+
57
+
58
+ def _render_status_output(e: events.CommandOutputEvent) -> RenderableType:
59
+ """Render session status with total cost and per-model breakdown."""
60
+ if not isinstance(e.ui_extra, model.SessionStatusUIExtra):
61
+ return Text("(no status data)", style=ThemeKey.TOOL_RESULT)
62
+
63
+ status = e.ui_extra
64
+ usage = status.usage
65
+
66
+ table = Table.grid(padding=(0, 2))
67
+ table.add_column(style=ThemeKey.TOOL_RESULT, overflow="fold")
68
+ table.add_column(style=ThemeKey.TOOL_RESULT, overflow="fold")
69
+
70
+ # Total cost line
71
+ table.add_row(
72
+ Text("Total cost:", style=ThemeKey.TOOL_RESULT_BOLD),
73
+ Text(_format_cost(usage.total_cost, usage.currency), style=ThemeKey.TOOL_RESULT_BOLD),
74
+ )
75
+
76
+ # Per-model breakdown
77
+ if status.by_model:
78
+ table.add_row(Text("Usage by model:", style=ThemeKey.TOOL_RESULT_BOLD), "")
79
+ for meta in status.by_model:
80
+ model_label = meta.model_name
81
+ if meta.provider:
82
+ model_label = f"{meta.model_name} ({meta.provider.lower().replace(' ', '-')})"
83
+
84
+ if meta.usage:
85
+ usage_detail = (
86
+ f"{_format_tokens(meta.usage.input_tokens)} input, "
87
+ f"{_format_tokens(meta.usage.output_tokens)} output, "
88
+ f"{_format_tokens(meta.usage.cached_tokens)} cache read, "
89
+ f"{_format_tokens(meta.usage.reasoning_tokens)} thinking, "
90
+ f"({_format_cost(meta.usage.total_cost, meta.usage.currency)})"
91
+ )
92
+ else:
93
+ usage_detail = "(no usage data)"
94
+ table.add_row(f"{model_label}:", usage_detail)
95
+
96
+ return Padding.indent(table, level=2)
@@ -1,29 +1,18 @@
1
1
  from rich.console import Group, RenderableType
2
- from rich.padding import Padding
3
- from rich.table import Table
4
2
  from rich.text import Text
5
3
 
6
- from klaude_code.protocol import commands, events, message, model
7
- from klaude_code.tui.components.common import create_grid, truncate_middle
4
+ from klaude_code.protocol import events, model
5
+ from klaude_code.tui.components.common import create_grid
8
6
  from klaude_code.tui.components.rich.theme import ThemeKey
9
7
  from klaude_code.tui.components.tools import render_path
10
8
 
11
9
  REMINDER_BULLET = " ⧉"
12
10
 
13
11
 
14
- def get_command_output(item: message.DeveloperMessage) -> model.CommandOutput | None:
15
- if not item.ui_extra:
16
- return None
17
- for ui_item in item.ui_extra.items:
18
- if isinstance(ui_item, model.CommandOutputUIItem):
19
- return ui_item.output
20
- return None
21
-
22
-
23
12
  def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
24
13
  if not e.item.ui_extra:
25
14
  return False
26
- return any(not isinstance(ui_item, model.CommandOutputUIItem) for ui_item in e.item.ui_extra.items)
15
+ return len(e.item.ui_extra.items) > 0
27
16
 
28
17
 
29
18
  def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
@@ -126,101 +115,5 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
126
115
  ),
127
116
  )
128
117
  parts.append(grid)
129
- case model.CommandOutputUIItem():
130
- # Rendered via render_command_output
131
- pass
132
118
 
133
119
  return Group(*parts) if parts else Text("")
134
-
135
-
136
- def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
137
- """Render developer command output content."""
138
- command_output = get_command_output(e.item)
139
- if not command_output:
140
- return Text("")
141
-
142
- content = message.join_text_parts(e.item.parts)
143
- match command_output.command_name:
144
- case commands.CommandName.STATUS:
145
- return _render_status_output(command_output)
146
- case commands.CommandName.FORK_SESSION:
147
- return _render_fork_session_output(command_output)
148
- case _:
149
- content = content or "(no content)"
150
- style = ThemeKey.TOOL_RESULT if not command_output.is_error else ThemeKey.ERROR
151
- return Padding.indent(truncate_middle(content, base_style=style), level=2)
152
-
153
-
154
- def _format_tokens(tokens: int) -> str:
155
- """Format token count with K/M suffix for readability."""
156
- if tokens >= 1_000_000:
157
- return f"{tokens / 1_000_000:.2f}M"
158
- if tokens >= 1_000:
159
- return f"{tokens / 1_000:.1f}K"
160
- return str(tokens)
161
-
162
-
163
- def _format_cost(cost: float | None, currency: str = "USD") -> str:
164
- """Format cost with currency symbol."""
165
- if cost is None:
166
- return "-"
167
- symbol = "¥" if currency == "CNY" else "$"
168
- if cost < 0.01:
169
- return f"{symbol}{cost:.4f}"
170
- return f"{symbol}{cost:.2f}"
171
-
172
-
173
- def _render_fork_session_output(command_output: model.CommandOutput) -> RenderableType:
174
- """Render fork session output with usage instructions."""
175
- if not isinstance(command_output.ui_extra, model.SessionIdUIExtra):
176
- return Padding.indent(Text("(no session id)", style=ThemeKey.TOOL_RESULT), level=2)
177
-
178
- grid = Table.grid(padding=(0, 1))
179
- session_id = command_output.ui_extra.session_id
180
- grid.add_column(style=ThemeKey.TOOL_RESULT, overflow="fold")
181
-
182
- grid.add_row(Text("Session forked. Resume command copied to clipboard:", style=ThemeKey.TOOL_RESULT))
183
- grid.add_row(Text(f" klaude --resume-by-id {session_id}", style=ThemeKey.TOOL_RESULT_BOLD))
184
-
185
- return Padding.indent(grid, level=2)
186
-
187
-
188
- def _render_status_output(command_output: model.CommandOutput) -> RenderableType:
189
- """Render session status with total cost and per-model breakdown."""
190
- if not isinstance(command_output.ui_extra, model.SessionStatusUIExtra):
191
- return Text("(no status data)", style=ThemeKey.TOOL_RESULT)
192
-
193
- status = command_output.ui_extra
194
- usage = status.usage
195
-
196
- table = Table.grid(padding=(0, 2))
197
- table.add_column(style=ThemeKey.TOOL_RESULT, overflow="fold")
198
- table.add_column(style=ThemeKey.TOOL_RESULT, overflow="fold")
199
-
200
- # Total cost line
201
- table.add_row(
202
- Text("Total cost:", style=ThemeKey.TOOL_RESULT_BOLD),
203
- Text(_format_cost(usage.total_cost, usage.currency), style=ThemeKey.TOOL_RESULT_BOLD),
204
- )
205
-
206
- # Per-model breakdown
207
- if status.by_model:
208
- table.add_row(Text("Usage by model:", style=ThemeKey.TOOL_RESULT_BOLD), "")
209
- for meta in status.by_model:
210
- model_label = meta.model_name
211
- if meta.provider:
212
- model_label = f"{meta.model_name} ({meta.provider.lower().replace(' ', '-')})"
213
-
214
- if meta.usage:
215
- usage_detail = (
216
- f"{_format_tokens(meta.usage.input_tokens)} input, "
217
- f"{_format_tokens(meta.usage.output_tokens)} output, "
218
- f"{_format_tokens(meta.usage.cached_tokens)} cache read, "
219
- f"{_format_tokens(meta.usage.reasoning_tokens)} thinking, "
220
- f"({_format_cost(meta.usage.total_cost, meta.usage.currency)})"
221
- )
222
- else:
223
- usage_detail = "(no usage data)"
224
- table.add_row(f"{model_label}:", usage_detail)
225
-
226
- return Padding.indent(table, level=2)
@@ -38,7 +38,7 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
38
38
  # Model line: model @ provider · params...
39
39
  panel_content.append_text(
40
40
  Text.assemble(
41
- (str(e.llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
41
+ (str(e.llm_config.model_id), ThemeKey.WELCOME_HIGHLIGHT),
42
42
  (" @ ", ThemeKey.WELCOME_INFO),
43
43
  (e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
44
44
  )
@@ -84,7 +84,7 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
84
84
  (prefix, ThemeKey.LINES),
85
85
  (sub_agent_type.lower().ljust(max_type_len), ThemeKey.WELCOME_INFO),
86
86
  (": ", ThemeKey.LINES),
87
- (str(sub_llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
87
+ (str(sub_llm_config.model_id), ThemeKey.WELCOME_HIGHLIGHT),
88
88
  (" @ ", ThemeKey.WELCOME_INFO),
89
89
  (sub_llm_config.provider_name, ThemeKey.WELCOME_INFO),
90
90
  )
@@ -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,23 +452,21 @@ 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(), m.provider.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
471
468
  if isinstance(initial, str) and initial and "@" not in initial:
469
+ config = load_config()
472
470
  try:
473
471
  resolved = config.resolve_model_location_prefer_available(initial) or config.resolve_model_location(
474
472
  initial
@@ -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
@@ -471,7 +473,6 @@ class TUICommandRenderer:
471
473
  self.print()
472
474
  case events.DeveloperMessageEvent() as e:
473
475
  self.display_developer_message(e)
474
- self.display_command_output(e)
475
476
  case events.UserMessageEvent() as e:
476
477
  if is_sub_agent:
477
478
  continue
@@ -504,11 +505,9 @@ class TUICommandRenderer:
504
505
  with self.session_print_context(e.session_id):
505
506
  self.print(c_developer.render_developer_message(e))
506
507
 
507
- def display_command_output(self, e: events.DeveloperMessageEvent) -> None:
508
- if not c_developer.get_command_output(e.item):
509
- return
508
+ def display_command_output(self, e: events.CommandOutputEvent) -> None:
510
509
  with self.session_print_context(e.session_id):
511
- self.print(c_developer.render_command_output(e))
510
+ self.print(c_command_output.render_command_output(e))
512
511
  self.print()
513
512
 
514
513
  def display_welcome(self, event: events.WelcomeEvent) -> None:
@@ -627,6 +626,7 @@ class TUICommandRenderer:
627
626
  self.display_task_start(event)
628
627
  case RenderDeveloperMessage(event=event):
629
628
  self.display_developer_message(event)
629
+ case RenderCommandOutput(event=event):
630
630
  self.display_command_output(event)
631
631
  case RenderTurnStart(event=event):
632
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] = []