klaude-code 2.3.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.
- klaude_code/cli/list_model.py +3 -3
- klaude_code/cli/main.py +2 -2
- klaude_code/config/assets/builtin_config.yaml +165 -307
- klaude_code/config/config.py +17 -17
- klaude_code/config/{select_model.py → model_matcher.py} +7 -7
- klaude_code/config/sub_agent_model_helper.py +1 -1
- klaude_code/config/thinking.py +2 -2
- klaude_code/core/executor.py +59 -52
- klaude_code/core/tool/file/diff_builder.py +25 -18
- klaude_code/llm/anthropic/client.py +5 -5
- klaude_code/llm/client.py +1 -1
- klaude_code/llm/codex/client.py +2 -2
- klaude_code/llm/google/client.py +6 -6
- klaude_code/llm/input_common.py +2 -2
- klaude_code/llm/openai_compatible/client.py +3 -3
- klaude_code/llm/openai_compatible/stream.py +1 -1
- klaude_code/llm/openrouter/client.py +4 -4
- klaude_code/llm/openrouter/input.py +1 -3
- klaude_code/llm/responses/client.py +5 -5
- klaude_code/protocol/events/__init__.py +7 -1
- klaude_code/protocol/events/chat.py +10 -0
- klaude_code/protocol/llm_param.py +1 -1
- klaude_code/protocol/model.py +0 -26
- klaude_code/protocol/op.py +0 -5
- klaude_code/session/session.py +4 -2
- klaude_code/tui/command/clear_cmd.py +0 -1
- klaude_code/tui/command/command_abc.py +6 -4
- klaude_code/tui/command/copy_cmd.py +10 -10
- klaude_code/tui/command/debug_cmd.py +11 -10
- klaude_code/tui/command/export_online_cmd.py +18 -23
- klaude_code/tui/command/fork_session_cmd.py +39 -43
- klaude_code/tui/command/model_cmd.py +5 -7
- klaude_code/tui/command/{model_select.py → model_picker.py} +3 -5
- klaude_code/tui/command/refresh_cmd.py +0 -1
- klaude_code/tui/command/registry.py +15 -21
- klaude_code/tui/command/resume_cmd.py +10 -16
- klaude_code/tui/command/status_cmd.py +8 -12
- klaude_code/tui/command/sub_agent_model_cmd.py +11 -16
- klaude_code/tui/command/terminal_setup_cmd.py +8 -11
- klaude_code/tui/command/thinking_cmd.py +4 -6
- klaude_code/tui/commands.py +5 -0
- klaude_code/tui/components/command_output.py +96 -0
- klaude_code/tui/components/developer.py +3 -110
- klaude_code/tui/components/welcome.py +2 -2
- klaude_code/tui/input/prompt_toolkit.py +6 -8
- klaude_code/tui/machine.py +5 -0
- klaude_code/tui/renderer.py +5 -5
- klaude_code/tui/runner.py +0 -6
- klaude_code/tui/terminal/selector.py +4 -4
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/METADATA +21 -74
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/RECORD +53 -52
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -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,
|
|
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.
|
|
139
|
+
events.CommandOutputEvent(
|
|
140
140
|
session_id=agent.session.id,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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.
|
|
152
|
+
events.CommandOutputEvent(
|
|
154
153
|
session_id=agent.session.id,
|
|
155
|
-
|
|
156
|
-
|
|
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.
|
|
166
|
+
events.CommandOutputEvent(
|
|
170
167
|
session_id=agent.session.id,
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
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.
|
|
229
|
+
events.CommandOutputEvent(
|
|
230
230
|
session_id=agent.session.id,
|
|
231
|
-
|
|
232
|
-
|
|
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.
|
|
241
|
+
events.CommandOutputEvent(
|
|
244
242
|
session_id=agent.session.id,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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,
|
|
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.
|
|
82
|
+
events.CommandOutputEvent(
|
|
83
83
|
session_id=agent.session.id,
|
|
84
|
-
|
|
85
|
-
|
|
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
|
)
|
klaude_code/tui/commands.py
CHANGED
|
@@ -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
|
|
7
|
-
from klaude_code.tui.components.common import create_grid
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
456
|
-
|
|
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(
|
|
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
|
klaude_code/tui/machine.py
CHANGED
|
@@ -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()
|
klaude_code/tui/renderer.py
CHANGED
|
@@ -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.
|
|
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(
|
|
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] = []
|
|
@@ -53,20 +53,20 @@ 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
|
-
|
|
56
|
+
model_id_str = m.model_id or "N/A"
|
|
57
57
|
# Display the base model name only; provider stays in the meta section.
|
|
58
58
|
display_name = m.model_name
|
|
59
59
|
first_line_prefix = f"{display_name:<{max_model_name_length}} → "
|
|
60
60
|
meta_parts: list[str] = [m.provider]
|
|
61
|
-
meta_parts.extend(format_model_params(m
|
|
61
|
+
meta_parts.extend(format_model_params(m))
|
|
62
62
|
meta_str = " · ".join(meta_parts)
|
|
63
63
|
title = [
|
|
64
64
|
("class:meta", f"{idx:>{num_width}}. "),
|
|
65
65
|
("class:msg", first_line_prefix),
|
|
66
|
-
("class:msg bold",
|
|
66
|
+
("class:msg bold", model_id_str),
|
|
67
67
|
("class:meta", f" {meta_str}\n"),
|
|
68
68
|
]
|
|
69
|
-
search_text = f"{m.selector} {m.model_name} {
|
|
69
|
+
search_text = f"{m.selector} {m.model_name} {model_id_str} {m.provider}"
|
|
70
70
|
items.append(SelectItem(title=title, value=m.selector, search_text=search_text))
|
|
71
71
|
|
|
72
72
|
return items
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: klaude-code
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Minimal code agent CLI
|
|
5
5
|
Requires-Dist: anthropic>=0.66.0
|
|
6
6
|
Requires-Dist: chardet>=5.2.0
|
|
@@ -155,95 +155,42 @@ Open in editor:
|
|
|
155
155
|
klaude config
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
#####
|
|
158
|
+
##### Model Configuration
|
|
159
159
|
|
|
160
|
-
You can add custom models to
|
|
160
|
+
You can add custom models to built-in providers or define new ones. Configuration is inherited from built-in providers by matching `provider_name`.
|
|
161
161
|
|
|
162
162
|
```yaml
|
|
163
163
|
# ~/.klaude/klaude-config.yaml
|
|
164
164
|
provider_list:
|
|
165
|
-
|
|
166
|
-
model_list:
|
|
167
|
-
- model_name: seed
|
|
168
|
-
model_params:
|
|
169
|
-
model: bytedance-seed/seed-1.6 # Model ID from OpenRouter
|
|
170
|
-
context_limit: 262000
|
|
171
|
-
cost:
|
|
172
|
-
input: 0.25
|
|
173
|
-
output: 2
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**How merging works:**
|
|
177
|
-
- Your models are merged with the built-in models for that provider
|
|
178
|
-
- You only need `provider_name` and `model_list` - protocol, api_key, etc. are inherited from the built-in config
|
|
179
|
-
- To override a built-in model, use the same `model_name` (e.g., `sonnet` to customize the built-in sonnet)
|
|
180
|
-
|
|
181
|
-
**More examples:**
|
|
182
|
-
|
|
183
|
-
```yaml
|
|
184
|
-
provider_list:
|
|
185
|
-
# Add multiple models to OpenRouter
|
|
165
|
+
# Add/Override models for built-in OpenRouter provider
|
|
186
166
|
- provider_name: openrouter
|
|
187
167
|
model_list:
|
|
188
168
|
- model_name: qwen-coder
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
context_limit: 131072
|
|
199
|
-
cost:
|
|
200
|
-
input: 0.8
|
|
201
|
-
output: 0.8
|
|
202
|
-
|
|
203
|
-
# Add models to Anthropic provider
|
|
204
|
-
- provider_name: anthropic
|
|
205
|
-
model_list:
|
|
206
|
-
- model_name: haiku@ant
|
|
207
|
-
model_params:
|
|
208
|
-
model: claude-3-5-haiku-20241022
|
|
209
|
-
context_limit: 200000
|
|
210
|
-
cost:
|
|
211
|
-
input: 1.0
|
|
212
|
-
output: 5.0
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
After adding models, run `klaude list` to verify they appear in the model list.
|
|
216
|
-
|
|
217
|
-
##### Overriding Provider Settings
|
|
218
|
-
|
|
219
|
-
Override provider-level settings (like api_key) while keeping built-in models:
|
|
220
|
-
|
|
221
|
-
```yaml
|
|
222
|
-
provider_list:
|
|
223
|
-
- provider_name: anthropic
|
|
224
|
-
api_key: sk-my-custom-key # Override the default ${ANTHROPIC_API_KEY}
|
|
225
|
-
# Built-in models (sonnet, opus) are still available
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
##### Adding New Providers
|
|
229
|
-
|
|
230
|
-
For providers not in the built-in list, you must specify `protocol`:
|
|
231
|
-
|
|
232
|
-
```yaml
|
|
233
|
-
provider_list:
|
|
234
|
-
- provider_name: my-azure-openai
|
|
169
|
+
model_id: qwen/qwen-2.5-coder-32b-instruct
|
|
170
|
+
context_limit: 131072
|
|
171
|
+
cost: { input: 0.3, output: 0.9 }
|
|
172
|
+
- model_name: sonnet # Override built-in sonnet params
|
|
173
|
+
model_id: anthropic/claude-3.5-sonnet
|
|
174
|
+
context_limit: 200000
|
|
175
|
+
|
|
176
|
+
# Add a completely new provider
|
|
177
|
+
- provider_name: my-azure
|
|
235
178
|
protocol: openai
|
|
236
179
|
api_key: ${AZURE_OPENAI_KEY}
|
|
237
180
|
base_url: https://my-instance.openai.azure.com/
|
|
238
181
|
is_azure: true
|
|
239
182
|
azure_api_version: "2024-02-15-preview"
|
|
240
183
|
model_list:
|
|
241
|
-
- model_name: gpt-4
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
context_limit: 128000
|
|
184
|
+
- model_name: gpt-4
|
|
185
|
+
model_id: gpt-4-deploy-name
|
|
186
|
+
context_limit: 128000
|
|
245
187
|
```
|
|
246
188
|
|
|
189
|
+
**Key Tips:**
|
|
190
|
+
- **Merging**: If `provider_name` matches a built-in provider, settings like `protocol` and `api_key` are inherited.
|
|
191
|
+
- **Overriding**: Use the same `model_name` as a built-in model to override its parameters.
|
|
192
|
+
- **Environment Variables**: Use `${VAR_NAME}` syntax for secrets.
|
|
193
|
+
|
|
247
194
|
##### Supported Protocols
|
|
248
195
|
|
|
249
196
|
- `anthropic` - Anthropic Messages API
|