klaude-code 2.5.2__py3-none-any.whl → 2.6.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/auth/__init__.py +10 -0
- klaude_code/auth/env.py +77 -0
- klaude_code/cli/auth_cmd.py +89 -21
- klaude_code/cli/config_cmd.py +5 -5
- klaude_code/cli/cost_cmd.py +167 -68
- klaude_code/cli/main.py +51 -27
- klaude_code/cli/self_update.py +7 -7
- klaude_code/config/assets/builtin_config.yaml +45 -24
- klaude_code/config/builtin_config.py +23 -9
- klaude_code/config/config.py +19 -9
- klaude_code/config/model_matcher.py +1 -1
- klaude_code/const.py +2 -1
- klaude_code/core/tool/file/edit_tool.py +1 -1
- klaude_code/core/tool/file/read_tool.py +2 -2
- klaude_code/core/tool/file/write_tool.py +1 -1
- klaude_code/core/turn.py +21 -4
- klaude_code/llm/anthropic/client.py +75 -50
- klaude_code/llm/anthropic/input.py +20 -9
- klaude_code/llm/google/client.py +235 -148
- klaude_code/llm/google/input.py +44 -36
- klaude_code/llm/openai_compatible/stream.py +114 -100
- klaude_code/llm/openrouter/client.py +1 -0
- klaude_code/llm/openrouter/reasoning.py +4 -29
- klaude_code/llm/partial_message.py +2 -32
- klaude_code/llm/responses/client.py +99 -81
- klaude_code/llm/responses/input.py +11 -25
- klaude_code/llm/stream_parts.py +94 -0
- klaude_code/log.py +57 -0
- klaude_code/protocol/events.py +214 -0
- klaude_code/protocol/sub_agent/image_gen.py +0 -4
- klaude_code/session/session.py +51 -18
- klaude_code/tui/command/fork_session_cmd.py +14 -23
- klaude_code/tui/command/model_picker.py +2 -17
- klaude_code/tui/command/resume_cmd.py +2 -18
- klaude_code/tui/command/sub_agent_model_cmd.py +5 -19
- klaude_code/tui/command/thinking_cmd.py +2 -14
- klaude_code/tui/commands.py +0 -5
- klaude_code/tui/components/common.py +1 -1
- klaude_code/tui/components/metadata.py +21 -21
- klaude_code/tui/components/rich/quote.py +36 -8
- klaude_code/tui/components/rich/theme.py +2 -0
- klaude_code/tui/components/sub_agent.py +6 -0
- klaude_code/tui/display.py +11 -1
- klaude_code/tui/input/completers.py +11 -7
- klaude_code/tui/input/prompt_toolkit.py +3 -1
- klaude_code/tui/machine.py +108 -56
- klaude_code/tui/renderer.py +4 -65
- klaude_code/tui/terminal/selector.py +174 -31
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/METADATA +23 -31
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/RECORD +52 -58
- klaude_code/cli/session_cmd.py +0 -96
- klaude_code/protocol/events/__init__.py +0 -63
- klaude_code/protocol/events/base.py +0 -18
- klaude_code/protocol/events/chat.py +0 -30
- klaude_code/protocol/events/lifecycle.py +0 -23
- klaude_code/protocol/events/metadata.py +0 -16
- klaude_code/protocol/events/streaming.py +0 -43
- klaude_code/protocol/events/system.py +0 -56
- klaude_code/protocol/events/tools.py +0 -27
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from klaude_code.protocol import llm_param, message, model
|
|
9
|
+
from klaude_code.protocol.commands import CommandName
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AssistantImageDeltaEvent",
|
|
13
|
+
"AssistantTextDeltaEvent",
|
|
14
|
+
"AssistantTextEndEvent",
|
|
15
|
+
"AssistantTextStartEvent",
|
|
16
|
+
"CommandOutputEvent",
|
|
17
|
+
"DeveloperMessageEvent",
|
|
18
|
+
"EndEvent",
|
|
19
|
+
"ErrorEvent",
|
|
20
|
+
"Event",
|
|
21
|
+
"InterruptEvent",
|
|
22
|
+
"ReplayEventUnion",
|
|
23
|
+
"ReplayHistoryEvent",
|
|
24
|
+
"ResponseCompleteEvent",
|
|
25
|
+
"ResponseEvent",
|
|
26
|
+
"TaskFinishEvent",
|
|
27
|
+
"TaskMetadataEvent",
|
|
28
|
+
"TaskStartEvent",
|
|
29
|
+
"ThinkingDeltaEvent",
|
|
30
|
+
"ThinkingEndEvent",
|
|
31
|
+
"ThinkingStartEvent",
|
|
32
|
+
"TodoChangeEvent",
|
|
33
|
+
"ToolCallEvent",
|
|
34
|
+
"ToolCallStartEvent",
|
|
35
|
+
"ToolResultEvent",
|
|
36
|
+
"TurnEndEvent",
|
|
37
|
+
"TurnStartEvent",
|
|
38
|
+
"UsageEvent",
|
|
39
|
+
"UserMessageEvent",
|
|
40
|
+
"WelcomeEvent",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Event(BaseModel):
|
|
45
|
+
"""Base event."""
|
|
46
|
+
|
|
47
|
+
session_id: str
|
|
48
|
+
timestamp: float = Field(default_factory=time.time)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ResponseEvent(Event):
|
|
52
|
+
"""Event associated with a single model response."""
|
|
53
|
+
|
|
54
|
+
response_id: str | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class UserMessageEvent(Event):
|
|
58
|
+
content: str
|
|
59
|
+
images: list[message.ImageURLPart] | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class DeveloperMessageEvent(Event):
|
|
63
|
+
"""DeveloperMessages are reminders in user messages or tool results."""
|
|
64
|
+
|
|
65
|
+
item: message.DeveloperMessage
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TodoChangeEvent(Event):
|
|
69
|
+
todos: list[model.TodoItem]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class CommandOutputEvent(Event):
|
|
73
|
+
"""Event for command output display. Not persisted to session history."""
|
|
74
|
+
|
|
75
|
+
command_name: CommandName | str
|
|
76
|
+
content: str = ""
|
|
77
|
+
ui_extra: model.ToolResultUIExtra | None = None
|
|
78
|
+
is_error: bool = False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TaskStartEvent(Event):
|
|
82
|
+
sub_agent_state: model.SubAgentState | None = None
|
|
83
|
+
model_id: str | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TaskFinishEvent(Event):
|
|
87
|
+
task_result: str
|
|
88
|
+
has_structured_output: bool = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TurnStartEvent(Event):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TurnEndEvent(Event):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class UsageEvent(ResponseEvent):
|
|
100
|
+
usage: model.Usage
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TaskMetadataEvent(Event):
|
|
104
|
+
metadata: model.TaskMetadataItem
|
|
105
|
+
cancelled: bool = False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ThinkingStartEvent(ResponseEvent):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ThinkingDeltaEvent(ResponseEvent):
|
|
113
|
+
content: str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ThinkingEndEvent(ResponseEvent):
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AssistantTextStartEvent(ResponseEvent):
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AssistantTextDeltaEvent(ResponseEvent):
|
|
125
|
+
content: str
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AssistantTextEndEvent(ResponseEvent):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AssistantImageDeltaEvent(ResponseEvent):
|
|
133
|
+
file_path: str
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ToolCallStartEvent(ResponseEvent):
|
|
137
|
+
tool_call_id: str
|
|
138
|
+
tool_name: str
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ResponseCompleteEvent(ResponseEvent):
|
|
142
|
+
"""Final snapshot of the model response."""
|
|
143
|
+
|
|
144
|
+
content: str
|
|
145
|
+
thinking_text: str | None = None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class WelcomeEvent(Event):
|
|
149
|
+
work_dir: str
|
|
150
|
+
llm_config: llm_param.LLMConfigParameter
|
|
151
|
+
show_klaude_code_info: bool = True
|
|
152
|
+
loaded_skills: dict[str, list[str]] = Field(default_factory=dict)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ErrorEvent(Event):
|
|
156
|
+
error_message: str
|
|
157
|
+
can_retry: bool = False
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class InterruptEvent(Event):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class EndEvent(Event):
|
|
165
|
+
"""Global display shutdown."""
|
|
166
|
+
|
|
167
|
+
session_id: str = "__app__"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
type ReplayEventUnion = (
|
|
171
|
+
TaskStartEvent
|
|
172
|
+
| TaskFinishEvent
|
|
173
|
+
| TurnStartEvent
|
|
174
|
+
| ThinkingStartEvent
|
|
175
|
+
| ThinkingDeltaEvent
|
|
176
|
+
| ThinkingEndEvent
|
|
177
|
+
| AssistantTextStartEvent
|
|
178
|
+
| AssistantTextDeltaEvent
|
|
179
|
+
| AssistantTextEndEvent
|
|
180
|
+
| AssistantImageDeltaEvent
|
|
181
|
+
| ToolCallEvent
|
|
182
|
+
| ToolResultEvent
|
|
183
|
+
| UserMessageEvent
|
|
184
|
+
| TaskMetadataEvent
|
|
185
|
+
| InterruptEvent
|
|
186
|
+
| DeveloperMessageEvent
|
|
187
|
+
| ErrorEvent
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ReplayHistoryEvent(Event):
|
|
192
|
+
events: list[ReplayEventUnion]
|
|
193
|
+
updated_at: float
|
|
194
|
+
is_load: bool = True
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ToolCallEvent(ResponseEvent):
|
|
198
|
+
tool_call_id: str
|
|
199
|
+
tool_name: str
|
|
200
|
+
arguments: str
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ToolResultEvent(ResponseEvent):
|
|
204
|
+
tool_call_id: str
|
|
205
|
+
tool_name: str
|
|
206
|
+
result: str
|
|
207
|
+
ui_extra: model.ToolResultUIExtra | None = None
|
|
208
|
+
status: Literal["success", "error", "aborted"]
|
|
209
|
+
task_metadata: model.TaskMetadata | None = None
|
|
210
|
+
is_last_in_turn: bool = True
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def is_error(self) -> bool:
|
|
214
|
+
return self.status in ("error", "aborted")
|
|
@@ -66,10 +66,6 @@ IMAGE_GEN_PARAMETERS: dict[str, Any] = {
|
|
|
66
66
|
"enum": ["1K", "2K", "4K"],
|
|
67
67
|
"description": "Output size for Nano Banana Pro (must use uppercase K).",
|
|
68
68
|
},
|
|
69
|
-
"extra": {
|
|
70
|
-
"type": "object",
|
|
71
|
-
"description": "Provider/model-specific extra parameters (future-proofing).",
|
|
72
|
-
},
|
|
73
69
|
},
|
|
74
70
|
"additionalProperties": False,
|
|
75
71
|
},
|
klaude_code/session/session.py
CHANGED
|
@@ -304,24 +304,57 @@ class Session(BaseModel):
|
|
|
304
304
|
yield events.TurnStartEvent(session_id=self.id)
|
|
305
305
|
match it:
|
|
306
306
|
case message.AssistantMessage() as am:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
last_assistant_content = message.format_saved_images(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
307
|
+
all_images = [part for part in am.parts if isinstance(part, message.ImageFilePart)]
|
|
308
|
+
full_content = message.join_text_parts(am.parts)
|
|
309
|
+
last_assistant_content = message.format_saved_images(all_images, full_content)
|
|
310
|
+
|
|
311
|
+
# Reconstruct streaming boundaries from saved parts.
|
|
312
|
+
# This allows replay to reuse the same TUI state machine as live events.
|
|
313
|
+
thinking_open = False
|
|
314
|
+
assistant_open = False
|
|
315
|
+
|
|
316
|
+
for part in am.parts:
|
|
317
|
+
if isinstance(part, message.ThinkingTextPart):
|
|
318
|
+
if assistant_open:
|
|
319
|
+
assistant_open = False
|
|
320
|
+
yield events.AssistantTextEndEvent(response_id=am.response_id, session_id=self.id)
|
|
321
|
+
if not thinking_open:
|
|
322
|
+
thinking_open = True
|
|
323
|
+
yield events.ThinkingStartEvent(response_id=am.response_id, session_id=self.id)
|
|
324
|
+
if part.text:
|
|
325
|
+
yield events.ThinkingDeltaEvent(
|
|
326
|
+
content=part.text,
|
|
327
|
+
response_id=am.response_id,
|
|
328
|
+
session_id=self.id,
|
|
329
|
+
)
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
if thinking_open:
|
|
333
|
+
thinking_open = False
|
|
334
|
+
yield events.ThinkingEndEvent(response_id=am.response_id, session_id=self.id)
|
|
335
|
+
|
|
336
|
+
if isinstance(part, message.TextPart):
|
|
337
|
+
if not assistant_open:
|
|
338
|
+
assistant_open = True
|
|
339
|
+
yield events.AssistantTextStartEvent(response_id=am.response_id, session_id=self.id)
|
|
340
|
+
if part.text:
|
|
341
|
+
yield events.AssistantTextDeltaEvent(
|
|
342
|
+
content=part.text,
|
|
343
|
+
response_id=am.response_id,
|
|
344
|
+
session_id=self.id,
|
|
345
|
+
)
|
|
346
|
+
elif isinstance(part, message.ImageFilePart):
|
|
347
|
+
yield events.AssistantImageDeltaEvent(
|
|
348
|
+
file_path=part.file_path,
|
|
349
|
+
response_id=am.response_id,
|
|
350
|
+
session_id=self.id,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if thinking_open:
|
|
354
|
+
yield events.ThinkingEndEvent(response_id=am.response_id, session_id=self.id)
|
|
355
|
+
if assistant_open:
|
|
356
|
+
yield events.AssistantTextEndEvent(response_id=am.response_id, session_id=self.id)
|
|
357
|
+
|
|
325
358
|
for part in am.parts:
|
|
326
359
|
if not isinstance(part, message.ToolCallPart):
|
|
327
360
|
continue
|
|
@@ -3,26 +3,23 @@ import sys
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Literal
|
|
5
5
|
|
|
6
|
-
from prompt_toolkit.styles import Style
|
|
6
|
+
from prompt_toolkit.styles import Style, merge_styles
|
|
7
7
|
|
|
8
8
|
from klaude_code.protocol import commands, events, message, model
|
|
9
9
|
from klaude_code.tui.input.clipboard import copy_to_clipboard
|
|
10
|
-
from klaude_code.tui.terminal.selector import SelectItem, select_one
|
|
10
|
+
from klaude_code.tui.terminal.selector import DEFAULT_PICKER_STYLE, SelectItem, select_one
|
|
11
11
|
|
|
12
12
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
13
13
|
|
|
14
|
-
FORK_SELECT_STYLE =
|
|
14
|
+
FORK_SELECT_STYLE = merge_styles(
|
|
15
15
|
[
|
|
16
|
-
|
|
17
|
-
(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
("search_none", "noinherit fg:ansired"),
|
|
24
|
-
("question", "bold"),
|
|
25
|
-
("text", ""),
|
|
16
|
+
DEFAULT_PICKER_STYLE,
|
|
17
|
+
Style(
|
|
18
|
+
[
|
|
19
|
+
("separator", "fg:ansibrightblack"),
|
|
20
|
+
("assistant", "fg:ansiblue"),
|
|
21
|
+
]
|
|
22
|
+
),
|
|
26
23
|
]
|
|
27
24
|
)
|
|
28
25
|
|
|
@@ -144,6 +141,7 @@ def _build_select_items(fork_points: list[ForkPoint]) -> list[SelectItem[int]]:
|
|
|
144
141
|
title=title_parts,
|
|
145
142
|
value=fp.history_index,
|
|
146
143
|
search_text=fp.user_message if not is_last else "fork entire conversation",
|
|
144
|
+
selectable=not is_first,
|
|
147
145
|
)
|
|
148
146
|
)
|
|
149
147
|
|
|
@@ -163,6 +161,9 @@ def _select_fork_point_sync(fork_points: list[ForkPoint]) -> int | Literal["canc
|
|
|
163
161
|
|
|
164
162
|
# Default to the last option (fork entire conversation)
|
|
165
163
|
last_value = items[-1].value
|
|
164
|
+
if last_value is None:
|
|
165
|
+
# Should not happen as we populate all items with int values
|
|
166
|
+
return -1
|
|
166
167
|
|
|
167
168
|
# Non-interactive environments default to forking entire conversation
|
|
168
169
|
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
@@ -241,16 +242,6 @@ class ForkSessionCommand(CommandABC):
|
|
|
241
242
|
)
|
|
242
243
|
return CommandResult(events=[event])
|
|
243
244
|
|
|
244
|
-
# First option (empty session) is just for UI display, not a valid fork point
|
|
245
|
-
if selected == fork_points[0].history_index:
|
|
246
|
-
event = events.CommandOutputEvent(
|
|
247
|
-
session_id=agent.session.id,
|
|
248
|
-
command_name=self.name,
|
|
249
|
-
content="(cannot fork to empty session)",
|
|
250
|
-
is_error=True,
|
|
251
|
-
)
|
|
252
|
-
return CommandResult(events=[event])
|
|
253
|
-
|
|
254
245
|
# Perform the fork
|
|
255
246
|
new_session = agent.session.fork(until_index=selected)
|
|
256
247
|
await new_session.wait_for_flush()
|
|
@@ -72,9 +72,7 @@ def select_model_interactive(
|
|
|
72
72
|
return ModelSelectResult(status=ModelSelectStatus.NON_TTY)
|
|
73
73
|
|
|
74
74
|
# Interactive selection
|
|
75
|
-
from
|
|
76
|
-
|
|
77
|
-
from klaude_code.tui.terminal.selector import build_model_select_items, select_one
|
|
75
|
+
from klaude_code.tui.terminal.selector import DEFAULT_PICKER_STYLE, build_model_select_items, select_one
|
|
78
76
|
|
|
79
77
|
names = [m.selector for m in result.filtered_models]
|
|
80
78
|
|
|
@@ -100,20 +98,7 @@ def select_model_interactive(
|
|
|
100
98
|
pointer="→",
|
|
101
99
|
use_search_filter=True,
|
|
102
100
|
initial_value=initial_value,
|
|
103
|
-
style=
|
|
104
|
-
[
|
|
105
|
-
("pointer", "ansigreen"),
|
|
106
|
-
("highlighted", "ansigreen"),
|
|
107
|
-
("msg", ""),
|
|
108
|
-
("meta", "fg:ansibrightblack"),
|
|
109
|
-
("text", "ansibrightblack"),
|
|
110
|
-
("question", "bold"),
|
|
111
|
-
("search_prefix", "ansibrightblack"),
|
|
112
|
-
# search filter colors at the bottom
|
|
113
|
-
("search_success", "noinherit fg:ansigreen"),
|
|
114
|
-
("search_none", "noinherit fg:ansired"),
|
|
115
|
-
]
|
|
116
|
-
),
|
|
101
|
+
style=DEFAULT_PICKER_STYLE,
|
|
117
102
|
)
|
|
118
103
|
if isinstance(selected, str) and selected in names:
|
|
119
104
|
return ModelSelectResult(status=ModelSelectStatus.SELECTED, model=selected)
|
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
-
from prompt_toolkit.styles import Style
|
|
4
|
-
|
|
5
3
|
from klaude_code.log import log
|
|
6
4
|
from klaude_code.protocol import commands, events, message, op
|
|
7
5
|
from klaude_code.session.selector import build_session_select_options, format_user_messages_display
|
|
8
|
-
from klaude_code.tui.terminal.selector import SelectItem, select_one
|
|
6
|
+
from klaude_code.tui.terminal.selector import DEFAULT_PICKER_STYLE, SelectItem, select_one
|
|
9
7
|
|
|
10
8
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
11
9
|
|
|
12
|
-
SESSION_SELECT_STYLE = Style(
|
|
13
|
-
[
|
|
14
|
-
("msg", "fg:ansibrightblack"),
|
|
15
|
-
("meta", ""),
|
|
16
|
-
("pointer", "bold fg:ansigreen"),
|
|
17
|
-
("highlighted", "fg:ansigreen"),
|
|
18
|
-
("search_prefix", "fg:ansibrightblack"),
|
|
19
|
-
("search_success", "noinherit fg:ansigreen"),
|
|
20
|
-
("search_none", "noinherit fg:ansired"),
|
|
21
|
-
("question", "bold"),
|
|
22
|
-
("text", ""),
|
|
23
|
-
]
|
|
24
|
-
)
|
|
25
|
-
|
|
26
10
|
|
|
27
11
|
def select_session_sync() -> str | None:
|
|
28
12
|
"""Interactive session selection (sync version for asyncio.to_thread)."""
|
|
@@ -62,7 +46,7 @@ def select_session_sync() -> str | None:
|
|
|
62
46
|
message="Select a session to resume:",
|
|
63
47
|
items=items,
|
|
64
48
|
pointer="→",
|
|
65
|
-
style=
|
|
49
|
+
style=DEFAULT_PICKER_STYLE,
|
|
66
50
|
)
|
|
67
51
|
except KeyboardInterrupt:
|
|
68
52
|
return None
|
|
@@ -4,27 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
|
|
7
|
-
from prompt_toolkit.styles import Style
|
|
8
|
-
|
|
9
7
|
from klaude_code.config.config import load_config
|
|
10
8
|
from klaude_code.config.sub_agent_model_helper import SubAgentModelHelper, SubAgentModelInfo
|
|
11
9
|
from klaude_code.protocol import commands, events, message, op
|
|
12
|
-
from klaude_code.tui.terminal.selector import SelectItem, build_model_select_items, select_one
|
|
10
|
+
from klaude_code.tui.terminal.selector import DEFAULT_PICKER_STYLE, SelectItem, build_model_select_items, select_one
|
|
13
11
|
|
|
14
12
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
15
13
|
|
|
16
|
-
SELECT_STYLE = Style(
|
|
17
|
-
[
|
|
18
|
-
("instruction", "ansibrightblack"),
|
|
19
|
-
("pointer", "ansigreen"),
|
|
20
|
-
("highlighted", "ansigreen"),
|
|
21
|
-
("text", "ansibrightblack"),
|
|
22
|
-
("question", "bold"),
|
|
23
|
-
("meta", "fg:ansibrightblack"),
|
|
24
|
-
("msg", ""),
|
|
25
|
-
]
|
|
26
|
-
)
|
|
27
|
-
|
|
28
14
|
USE_DEFAULT_BEHAVIOR = "__default__"
|
|
29
15
|
|
|
30
16
|
|
|
@@ -69,8 +55,8 @@ def _select_sub_agent_sync(
|
|
|
69
55
|
result = select_one(
|
|
70
56
|
message="Select sub-agent to configure:",
|
|
71
57
|
items=items,
|
|
72
|
-
pointer="
|
|
73
|
-
style=
|
|
58
|
+
pointer="→",
|
|
59
|
+
style=DEFAULT_PICKER_STYLE,
|
|
74
60
|
use_search_filter=False,
|
|
75
61
|
)
|
|
76
62
|
return result if isinstance(result, str) else None
|
|
@@ -103,8 +89,8 @@ def _select_model_for_sub_agent_sync(
|
|
|
103
89
|
result = select_one(
|
|
104
90
|
message=f"Select model for {sub_agent_type}:",
|
|
105
91
|
items=all_items,
|
|
106
|
-
pointer="
|
|
107
|
-
style=
|
|
92
|
+
pointer="→",
|
|
93
|
+
style=DEFAULT_PICKER_STYLE,
|
|
108
94
|
use_search_filter=True,
|
|
109
95
|
)
|
|
110
96
|
return result if isinstance(result, str) else None
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
-
from prompt_toolkit.styles import Style
|
|
4
|
-
|
|
5
3
|
from klaude_code.config.thinking import get_thinking_picker_data, parse_thinking_value
|
|
6
4
|
from klaude_code.protocol import commands, events, llm_param, message, op
|
|
7
|
-
from klaude_code.tui.terminal.selector import SelectItem, select_one
|
|
5
|
+
from klaude_code.tui.terminal.selector import DEFAULT_PICKER_STYLE, SelectItem, select_one
|
|
8
6
|
|
|
9
7
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
10
8
|
|
|
11
|
-
SELECT_STYLE = Style(
|
|
12
|
-
[
|
|
13
|
-
("instruction", "ansibrightblack"),
|
|
14
|
-
("pointer", "ansigreen"),
|
|
15
|
-
("highlighted", "ansigreen"),
|
|
16
|
-
("text", "ansibrightblack"),
|
|
17
|
-
("question", "bold"),
|
|
18
|
-
]
|
|
19
|
-
)
|
|
20
|
-
|
|
21
9
|
|
|
22
10
|
def _select_thinking_sync(config: llm_param.LLMConfigParameter) -> llm_param.Thinking | None:
|
|
23
11
|
"""Select thinking level (sync version)."""
|
|
@@ -35,7 +23,7 @@ def _select_thinking_sync(config: llm_param.LLMConfigParameter) -> llm_param.Thi
|
|
|
35
23
|
message=data.message,
|
|
36
24
|
items=items,
|
|
37
25
|
pointer="→",
|
|
38
|
-
style=
|
|
26
|
+
style=DEFAULT_PICKER_STYLE,
|
|
39
27
|
use_search_filter=False,
|
|
40
28
|
)
|
|
41
29
|
if result is None:
|
klaude_code/tui/commands.py
CHANGED
|
@@ -13,11 +13,6 @@ class RenderCommand:
|
|
|
13
13
|
pass
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@dataclass(frozen=True, slots=True)
|
|
17
|
-
class RenderReplayHistory(RenderCommand):
|
|
18
|
-
event: events.ReplayHistoryEvent
|
|
19
|
-
|
|
20
|
-
|
|
21
16
|
@dataclass(frozen=True, slots=True)
|
|
22
17
|
class RenderWelcome(RenderCommand):
|
|
23
18
|
event: events.WelcomeEvent
|
|
@@ -40,7 +40,7 @@ def truncate_middle(
|
|
|
40
40
|
remaining = max(0, len(truncated_lines))
|
|
41
41
|
return Text(f" … (more {remaining} lines)", style=ThemeKey.TOOL_RESULT_TRUNCATED)
|
|
42
42
|
|
|
43
|
-
lines = text.split("\n")
|
|
43
|
+
lines = [line for line in text.split("\n") if line.strip()]
|
|
44
44
|
truncated_lines = 0
|
|
45
45
|
head_lines: list[str] = []
|
|
46
46
|
tail_lines: list[str] = []
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from rich.console import Group, RenderableType
|
|
2
|
-
from rich.padding import Padding
|
|
3
2
|
from rich.text import Text
|
|
4
3
|
|
|
5
4
|
from klaude_code.const import DEFAULT_MAX_TOKENS
|
|
@@ -12,14 +11,14 @@ from klaude_code.ui.common import format_number
|
|
|
12
11
|
def _render_task_metadata_block(
|
|
13
12
|
metadata: model.TaskMetadata,
|
|
14
13
|
*,
|
|
15
|
-
|
|
14
|
+
mark: Text,
|
|
16
15
|
show_context_and_time: bool = True,
|
|
17
16
|
) -> RenderableType:
|
|
18
17
|
"""Render a single TaskMetadata block.
|
|
19
18
|
|
|
20
19
|
Args:
|
|
21
20
|
metadata: The TaskMetadata to render.
|
|
22
|
-
|
|
21
|
+
mark: The mark to display in the first column.
|
|
23
22
|
show_context_and_time: Whether to show context usage percent and time.
|
|
24
23
|
|
|
25
24
|
Returns:
|
|
@@ -31,19 +30,15 @@ def _render_task_metadata_block(
|
|
|
31
30
|
currency = metadata.usage.currency if metadata.usage else "USD"
|
|
32
31
|
currency_symbol = "¥" if currency == "CNY" else "$"
|
|
33
32
|
|
|
34
|
-
#
|
|
35
|
-
mark = Text("└", style=ThemeKey.METADATA_DIM) if is_sub_agent else Text("◆", style=ThemeKey.METADATA)
|
|
36
|
-
|
|
37
|
-
# Second column: model@provider description / tokens / cost / …
|
|
33
|
+
# Second column: provider/model description / tokens / cost / …
|
|
38
34
|
content = Text()
|
|
39
|
-
content.append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
|
|
40
35
|
if metadata.provider is not None:
|
|
41
|
-
content.append_text(Text("
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
content.append_text(Text(metadata.provider.lower().replace(" ", "-"), style=ThemeKey.METADATA))
|
|
37
|
+
content.append_text(Text("/", style=ThemeKey.METADATA_DIM))
|
|
38
|
+
content.append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
|
|
44
39
|
if metadata.description:
|
|
45
40
|
content.append_text(Text(" ", style=ThemeKey.METADATA)).append_text(
|
|
46
|
-
Text(metadata.description, style=ThemeKey.
|
|
41
|
+
Text(metadata.description, style=ThemeKey.METADATA_ITALIC)
|
|
47
42
|
)
|
|
48
43
|
|
|
49
44
|
# All info parts (tokens, cost, context, etc.)
|
|
@@ -63,7 +58,7 @@ def _render_task_metadata_block(
|
|
|
63
58
|
token_text.append(" ∿", style=ThemeKey.METADATA_DIM)
|
|
64
59
|
token_text.append(format_number(metadata.usage.reasoning_tokens), style=ThemeKey.METADATA)
|
|
65
60
|
if metadata.usage.image_tokens > 0:
|
|
66
|
-
token_text.append("
|
|
61
|
+
token_text.append(" ⊡", style=ThemeKey.METADATA_DIM)
|
|
67
62
|
token_text.append(format_number(metadata.usage.image_tokens), style=ThemeKey.METADATA)
|
|
68
63
|
parts.append(token_text)
|
|
69
64
|
|
|
@@ -134,7 +129,7 @@ def _render_task_metadata_block(
|
|
|
134
129
|
content.append_text(Text(" ", style=ThemeKey.METADATA_DIM).join(parts))
|
|
135
130
|
|
|
136
131
|
grid.add_row(mark, content)
|
|
137
|
-
return grid
|
|
132
|
+
return grid
|
|
138
133
|
|
|
139
134
|
|
|
140
135
|
def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
@@ -144,16 +139,20 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
|
144
139
|
if e.cancelled:
|
|
145
140
|
renderables.append(Text())
|
|
146
141
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
142
|
+
has_sub_agents = len(e.metadata.sub_agent_task_metadata) > 0
|
|
143
|
+
# Use an extra space for the main agent mark to align with two-character marks (├─, └─)
|
|
144
|
+
main_mark_text = "✓"
|
|
145
|
+
main_mark = Text(main_mark_text, style=ThemeKey.METADATA)
|
|
146
|
+
|
|
147
|
+
renderables.append(_render_task_metadata_block(e.metadata.main_agent, mark=main_mark, show_context_and_time=True))
|
|
150
148
|
|
|
151
149
|
# Render each sub-agent metadata block
|
|
152
150
|
for meta in e.metadata.sub_agent_task_metadata:
|
|
153
|
-
|
|
151
|
+
sub_mark = Text(" └", style=ThemeKey.METADATA_DIM)
|
|
152
|
+
renderables.append(_render_task_metadata_block(meta, mark=sub_mark, show_context_and_time=True))
|
|
154
153
|
|
|
155
154
|
# Add total cost line when there are sub-agents
|
|
156
|
-
if
|
|
155
|
+
if has_sub_agents:
|
|
157
156
|
total_cost = 0.0
|
|
158
157
|
currency = "USD"
|
|
159
158
|
# Sum up costs from main agent and all sub-agents
|
|
@@ -166,12 +165,13 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
|
166
165
|
|
|
167
166
|
currency_symbol = "¥" if currency == "CNY" else "$"
|
|
168
167
|
total_line = Text.assemble(
|
|
169
|
-
("
|
|
168
|
+
(" └", ThemeKey.METADATA_DIM),
|
|
169
|
+
(" Σ ", ThemeKey.METADATA_DIM),
|
|
170
170
|
("total ", ThemeKey.METADATA_DIM),
|
|
171
171
|
(currency_symbol, ThemeKey.METADATA_DIM),
|
|
172
172
|
(f"{total_cost:.4f}", ThemeKey.METADATA_DIM),
|
|
173
173
|
)
|
|
174
174
|
|
|
175
|
-
renderables.append(
|
|
175
|
+
renderables.append(total_line)
|
|
176
176
|
|
|
177
177
|
return Group(*renderables)
|