klaude-code 1.8.0__py3-none-any.whl → 2.0.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 (142) hide show
  1. klaude_code/auth/base.py +97 -0
  2. klaude_code/auth/claude/__init__.py +6 -0
  3. klaude_code/auth/claude/exceptions.py +9 -0
  4. klaude_code/auth/claude/oauth.py +172 -0
  5. klaude_code/auth/claude/token_manager.py +26 -0
  6. klaude_code/auth/codex/token_manager.py +10 -50
  7. klaude_code/cli/auth_cmd.py +127 -46
  8. klaude_code/cli/config_cmd.py +4 -2
  9. klaude_code/cli/cost_cmd.py +14 -9
  10. klaude_code/cli/list_model.py +248 -200
  11. klaude_code/cli/main.py +1 -1
  12. klaude_code/cli/runtime.py +7 -5
  13. klaude_code/cli/self_update.py +1 -1
  14. klaude_code/cli/session_cmd.py +1 -1
  15. klaude_code/command/clear_cmd.py +6 -2
  16. klaude_code/command/command_abc.py +2 -2
  17. klaude_code/command/debug_cmd.py +4 -4
  18. klaude_code/command/export_cmd.py +2 -2
  19. klaude_code/command/export_online_cmd.py +12 -12
  20. klaude_code/command/fork_session_cmd.py +29 -23
  21. klaude_code/command/help_cmd.py +4 -4
  22. klaude_code/command/model_cmd.py +4 -4
  23. klaude_code/command/model_select.py +1 -1
  24. klaude_code/command/prompt-commit.md +82 -0
  25. klaude_code/command/prompt_command.py +3 -3
  26. klaude_code/command/refresh_cmd.py +2 -2
  27. klaude_code/command/registry.py +7 -5
  28. klaude_code/command/release_notes_cmd.py +4 -4
  29. klaude_code/command/resume_cmd.py +15 -11
  30. klaude_code/command/status_cmd.py +4 -4
  31. klaude_code/command/terminal_setup_cmd.py +8 -8
  32. klaude_code/command/thinking_cmd.py +4 -4
  33. klaude_code/config/assets/builtin_config.yaml +52 -3
  34. klaude_code/config/builtin_config.py +16 -5
  35. klaude_code/config/config.py +31 -7
  36. klaude_code/config/thinking.py +4 -4
  37. klaude_code/const.py +146 -91
  38. klaude_code/core/agent.py +3 -12
  39. klaude_code/core/executor.py +21 -13
  40. klaude_code/core/manager/sub_agent_manager.py +71 -7
  41. klaude_code/core/prompt.py +1 -1
  42. klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
  43. klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
  44. klaude_code/core/reminders.py +88 -69
  45. klaude_code/core/task.py +44 -45
  46. klaude_code/core/tool/file/apply_patch_tool.py +9 -9
  47. klaude_code/core/tool/file/diff_builder.py +3 -5
  48. klaude_code/core/tool/file/edit_tool.py +23 -23
  49. klaude_code/core/tool/file/move_tool.py +43 -43
  50. klaude_code/core/tool/file/read_tool.py +44 -39
  51. klaude_code/core/tool/file/write_tool.py +14 -14
  52. klaude_code/core/tool/report_back_tool.py +4 -4
  53. klaude_code/core/tool/shell/bash_tool.py +23 -23
  54. klaude_code/core/tool/skill/skill_tool.py +7 -7
  55. klaude_code/core/tool/sub_agent_tool.py +38 -9
  56. klaude_code/core/tool/todo/todo_write_tool.py +8 -8
  57. klaude_code/core/tool/todo/update_plan_tool.py +6 -6
  58. klaude_code/core/tool/tool_abc.py +2 -2
  59. klaude_code/core/tool/tool_context.py +27 -0
  60. klaude_code/core/tool/tool_runner.py +88 -42
  61. klaude_code/core/tool/truncation.py +38 -20
  62. klaude_code/core/tool/web/mermaid_tool.py +6 -7
  63. klaude_code/core/tool/web/web_fetch_tool.py +68 -30
  64. klaude_code/core/tool/web/web_search_tool.py +15 -17
  65. klaude_code/core/turn.py +120 -73
  66. klaude_code/llm/anthropic/client.py +104 -44
  67. klaude_code/llm/anthropic/input.py +116 -108
  68. klaude_code/llm/bedrock/client.py +8 -5
  69. klaude_code/llm/claude/__init__.py +3 -0
  70. klaude_code/llm/claude/client.py +105 -0
  71. klaude_code/llm/client.py +4 -3
  72. klaude_code/llm/codex/client.py +16 -10
  73. klaude_code/llm/google/client.py +122 -60
  74. klaude_code/llm/google/input.py +94 -108
  75. klaude_code/llm/image.py +123 -0
  76. klaude_code/llm/input_common.py +136 -189
  77. klaude_code/llm/openai_compatible/client.py +17 -7
  78. klaude_code/llm/openai_compatible/input.py +36 -66
  79. klaude_code/llm/openai_compatible/stream.py +119 -67
  80. klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
  81. klaude_code/llm/openrouter/client.py +34 -9
  82. klaude_code/llm/openrouter/input.py +63 -64
  83. klaude_code/llm/openrouter/reasoning.py +22 -24
  84. klaude_code/llm/registry.py +20 -15
  85. klaude_code/llm/responses/client.py +107 -45
  86. klaude_code/llm/responses/input.py +115 -98
  87. klaude_code/llm/usage.py +52 -25
  88. klaude_code/protocol/__init__.py +1 -0
  89. klaude_code/protocol/events.py +16 -12
  90. klaude_code/protocol/llm_param.py +22 -3
  91. klaude_code/protocol/message.py +250 -0
  92. klaude_code/protocol/model.py +94 -281
  93. klaude_code/protocol/op.py +2 -2
  94. klaude_code/protocol/sub_agent/__init__.py +2 -2
  95. klaude_code/protocol/sub_agent/explore.py +10 -0
  96. klaude_code/protocol/sub_agent/image_gen.py +119 -0
  97. klaude_code/protocol/sub_agent/task.py +10 -0
  98. klaude_code/protocol/sub_agent/web.py +10 -0
  99. klaude_code/session/codec.py +6 -6
  100. klaude_code/session/export.py +261 -62
  101. klaude_code/session/selector.py +7 -24
  102. klaude_code/session/session.py +125 -53
  103. klaude_code/session/store.py +5 -32
  104. klaude_code/session/templates/export_session.html +1 -1
  105. klaude_code/session/templates/mermaid_viewer.html +1 -1
  106. klaude_code/trace/log.py +11 -6
  107. klaude_code/ui/core/input.py +1 -1
  108. klaude_code/ui/core/stage_manager.py +1 -8
  109. klaude_code/ui/modes/debug/display.py +2 -2
  110. klaude_code/ui/modes/repl/clipboard.py +2 -2
  111. klaude_code/ui/modes/repl/completers.py +18 -10
  112. klaude_code/ui/modes/repl/event_handler.py +136 -127
  113. klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
  114. klaude_code/ui/modes/repl/key_bindings.py +1 -1
  115. klaude_code/ui/modes/repl/renderer.py +107 -15
  116. klaude_code/ui/renderers/assistant.py +2 -2
  117. klaude_code/ui/renderers/common.py +65 -7
  118. klaude_code/ui/renderers/developer.py +7 -6
  119. klaude_code/ui/renderers/diffs.py +11 -11
  120. klaude_code/ui/renderers/mermaid_viewer.py +49 -2
  121. klaude_code/ui/renderers/metadata.py +39 -31
  122. klaude_code/ui/renderers/sub_agent.py +57 -16
  123. klaude_code/ui/renderers/thinking.py +37 -2
  124. klaude_code/ui/renderers/tools.py +180 -165
  125. klaude_code/ui/rich/live.py +3 -1
  126. klaude_code/ui/rich/markdown.py +39 -7
  127. klaude_code/ui/rich/quote.py +76 -1
  128. klaude_code/ui/rich/status.py +14 -8
  129. klaude_code/ui/rich/theme.py +13 -6
  130. klaude_code/ui/terminal/image.py +34 -0
  131. klaude_code/ui/terminal/notifier.py +2 -1
  132. klaude_code/ui/terminal/progress_bar.py +4 -4
  133. klaude_code/ui/terminal/selector.py +22 -4
  134. klaude_code/ui/utils/common.py +55 -0
  135. {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/METADATA +28 -6
  136. klaude_code-2.0.0.dist-info/RECORD +229 -0
  137. klaude_code/command/prompt-jj-describe.md +0 -32
  138. klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
  139. klaude_code/protocol/sub_agent/oracle.py +0 -91
  140. klaude_code-1.8.0.dist-info/RECORD +0 -219
  141. {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/WHEEL +0 -0
  142. {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -10,7 +10,7 @@ from rich.console import Console
10
10
  from rich.text import Text
11
11
 
12
12
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
13
- from klaude_code.protocol import commands, events, model
13
+ from klaude_code.protocol import commands, events, message, model
14
14
  from klaude_code.session.export import build_export_html
15
15
 
16
16
 
@@ -33,15 +33,15 @@ class ExportOnlineCommand(CommandABC):
33
33
  def is_interactive(self) -> bool:
34
34
  return False
35
35
 
36
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
36
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
37
37
  del user_input # unused
38
38
  # Check if npx or surge is available
39
39
  surge_cmd = self._get_surge_command()
40
40
  if not surge_cmd:
41
41
  event = events.DeveloperMessageEvent(
42
42
  session_id=agent.session.id,
43
- item=model.DeveloperMessageItem(
44
- content="surge.sh CLI not found. Install with: npm install -g surge",
43
+ item=message.DeveloperMessage(
44
+ parts=message.text_parts_from_str("surge.sh CLI not found. Install with: npm install -g surge"),
45
45
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
46
46
  ),
47
47
  )
@@ -50,29 +50,29 @@ class ExportOnlineCommand(CommandABC):
50
50
  try:
51
51
  console = Console()
52
52
  # Check login status inside status context since npx surge whoami can be slow
53
- with console.status(Text("Checking surge.sh login status...", style="dim"), spinner_style="dim"):
53
+ with console.status(Text("Checking surge.sh login status", style="dim"), spinner_style="dim"):
54
54
  logged_in = self._is_surge_logged_in(surge_cmd)
55
55
 
56
56
  if not logged_in:
57
57
  login_cmd = " ".join([*surge_cmd, "login"])
58
58
  event = events.DeveloperMessageEvent(
59
59
  session_id=agent.session.id,
60
- item=model.DeveloperMessageItem(
61
- content=f"Not logged in to surge.sh. Please run: {login_cmd}",
60
+ item=message.DeveloperMessage(
61
+ parts=message.text_parts_from_str(f"Not logged in to surge.sh. Please run: {login_cmd}"),
62
62
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
63
63
  ),
64
64
  )
65
65
  return CommandResult(events=[event])
66
66
 
67
- with console.status(Text("Deploying to surge.sh...", style="dim"), spinner_style="dim"):
67
+ with console.status(Text("Deploying to surge.sh", style="dim"), spinner_style="dim"):
68
68
  html_doc = self._build_html(agent)
69
69
  domain = self._generate_domain()
70
70
  url = self._deploy_to_surge(surge_cmd, html_doc, domain)
71
71
 
72
72
  event = events.DeveloperMessageEvent(
73
73
  session_id=agent.session.id,
74
- item=model.DeveloperMessageItem(
75
- content=f"Session deployed to: {url}",
74
+ item=message.DeveloperMessage(
75
+ parts=message.text_parts_from_str(f"Session deployed to: {url}"),
76
76
  command_output=model.CommandOutput(command_name=self.name),
77
77
  ),
78
78
  )
@@ -82,8 +82,8 @@ class ExportOnlineCommand(CommandABC):
82
82
 
83
83
  event = events.DeveloperMessageEvent(
84
84
  session_id=agent.session.id,
85
- item=model.DeveloperMessageItem(
86
- content=f"Failed to deploy session: {exc}\n{traceback.format_exc()}",
85
+ item=message.DeveloperMessage(
86
+ parts=message.text_parts_from_str(f"Failed to deploy session: {exc}\n{traceback.format_exc()}"),
87
87
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
88
88
  ),
89
89
  )
@@ -6,7 +6,7 @@ from typing import Literal
6
6
  from prompt_toolkit.styles import Style
7
7
 
8
8
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
9
- from klaude_code.protocol import commands, events, model
9
+ from klaude_code.protocol import commands, events, message, model
10
10
  from klaude_code.ui.modes.repl.clipboard import copy_to_clipboard
11
11
  from klaude_code.ui.terminal.selector import SelectItem, select_one
12
12
 
@@ -41,29 +41,29 @@ def _truncate(text: str, max_len: int = 60) -> str:
41
41
  text = text.replace("\n", " ").strip()
42
42
  if len(text) <= max_len:
43
43
  return text
44
- return text[: max_len - 3] + "..."
44
+ return text[: max_len - 1] + ""
45
45
 
46
46
 
47
- def _build_fork_points(conversation_history: list[model.ConversationItem]) -> list[ForkPoint]:
47
+ def _build_fork_points(conversation_history: list[message.HistoryEvent]) -> list[ForkPoint]:
48
48
  """Build list of fork points from conversation history.
49
49
 
50
50
  Fork points are:
51
- - Each UserMessageItem position (for UI display, including first which would be empty session)
51
+ - Each UserMessage position (for UI display, including first which would be empty session)
52
52
  - The end of the conversation (fork entire conversation)
53
53
  """
54
54
  fork_points: list[ForkPoint] = []
55
55
  user_indices: list[int] = []
56
56
 
57
57
  for i, item in enumerate(conversation_history):
58
- if isinstance(item, model.UserMessageItem):
58
+ if isinstance(item, message.UserMessage):
59
59
  user_indices.append(i)
60
60
 
61
- # For each UserMessageItem, create a fork point at that position
61
+ # For each UserMessage, create a fork point at that position
62
62
  for i, user_idx in enumerate(user_indices):
63
63
  user_item = conversation_history[user_idx]
64
- assert isinstance(user_item, model.UserMessageItem)
64
+ assert isinstance(user_item, message.UserMessage)
65
65
 
66
- # Find the end of this "task" (next UserMessageItem or end of history)
66
+ # Find the end of this "task" (next UserMessage or end of history)
67
67
  next_user_idx = user_indices[i + 1] if i + 1 < len(user_indices) else len(conversation_history)
68
68
 
69
69
  # Count tool calls by name and find last assistant message in this segment
@@ -71,15 +71,19 @@ def _build_fork_points(conversation_history: list[model.ConversationItem]) -> li
71
71
  last_assistant_content = ""
72
72
  for j in range(user_idx, next_user_idx):
73
73
  item = conversation_history[j]
74
- if isinstance(item, model.ToolCallItem):
75
- tool_stats[item.name] = tool_stats.get(item.name, 0) + 1
76
- elif isinstance(item, model.AssistantMessageItem) and item.content:
77
- last_assistant_content = item.content
78
-
74
+ if isinstance(item, message.AssistantMessage):
75
+ for part in item.parts:
76
+ if isinstance(part, message.ToolCallPart):
77
+ tool_stats[part.tool_name] = tool_stats.get(part.tool_name, 0) + 1
78
+ text = message.join_text_parts(item.parts)
79
+ if text:
80
+ last_assistant_content = text
81
+
82
+ user_text = message.join_text_parts(user_item.parts)
79
83
  fork_points.append(
80
84
  ForkPoint(
81
85
  history_index=user_idx,
82
- user_message=user_item.content or "(empty)",
86
+ user_message=user_text or "(empty)",
83
87
  tool_call_stats=tool_stats,
84
88
  last_assistant_summary=_truncate(last_assistant_content) if last_assistant_content else "",
85
89
  )
@@ -195,14 +199,14 @@ class ForkSessionCommand(CommandABC):
195
199
  def is_interactive(self) -> bool:
196
200
  return True
197
201
 
198
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
202
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
199
203
  del user_input # unused
200
204
 
201
205
  if agent.session.messages_count == 0:
202
206
  event = events.DeveloperMessageEvent(
203
207
  session_id=agent.session.id,
204
- item=model.DeveloperMessageItem(
205
- content="(no messages to fork)",
208
+ item=message.DeveloperMessage(
209
+ parts=message.text_parts_from_str("(no messages to fork)"),
206
210
  command_output=model.CommandOutput(command_name=self.name),
207
211
  ),
208
212
  )
@@ -221,8 +225,8 @@ class ForkSessionCommand(CommandABC):
221
225
 
222
226
  event = events.DeveloperMessageEvent(
223
227
  session_id=agent.session.id,
224
- item=model.DeveloperMessageItem(
225
- content=f"Session forked successfully. New session id: {new_session.id}",
228
+ item=message.DeveloperMessage(
229
+ parts=message.text_parts_from_str(f"Session forked successfully. New session id: {new_session.id}"),
226
230
  command_output=model.CommandOutput(
227
231
  command_name=self.name,
228
232
  ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
@@ -237,8 +241,8 @@ class ForkSessionCommand(CommandABC):
237
241
  if selected == "cancelled":
238
242
  event = events.DeveloperMessageEvent(
239
243
  session_id=agent.session.id,
240
- item=model.DeveloperMessageItem(
241
- content="(fork cancelled)",
244
+ item=message.DeveloperMessage(
245
+ parts=message.text_parts_from_str("(fork cancelled)"),
242
246
  command_output=model.CommandOutput(command_name=self.name),
243
247
  ),
244
248
  )
@@ -256,8 +260,10 @@ class ForkSessionCommand(CommandABC):
256
260
 
257
261
  event = events.DeveloperMessageEvent(
258
262
  session_id=agent.session.id,
259
- item=model.DeveloperMessageItem(
260
- content=f"Session forked ({fork_description}). New session id: {new_session.id}",
263
+ item=message.DeveloperMessage(
264
+ parts=message.text_parts_from_str(
265
+ f"Session forked ({fork_description}). New session id: {new_session.id}"
266
+ ),
261
267
  command_output=model.CommandOutput(
262
268
  command_name=self.name,
263
269
  ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
@@ -1,5 +1,5 @@
1
1
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
2
- from klaude_code.protocol import commands, events, model
2
+ from klaude_code.protocol import commands, events, message, model
3
3
 
4
4
 
5
5
  class HelpCommand(CommandABC):
@@ -13,7 +13,7 @@ class HelpCommand(CommandABC):
13
13
  def summary(self) -> str:
14
14
  return "Show help and available commands"
15
15
 
16
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
16
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
17
17
  del user_input # unused
18
18
  lines: list[str] = [
19
19
  """
@@ -41,8 +41,8 @@ Available slash commands:"""
41
41
 
42
42
  event = events.DeveloperMessageEvent(
43
43
  session_id=agent.session.id,
44
- item=model.DeveloperMessageItem(
45
- content="\n".join(lines),
44
+ item=message.DeveloperMessage(
45
+ parts=message.text_parts_from_str("\n".join(lines)),
46
46
  command_output=model.CommandOutput(command_name=self.name),
47
47
  ),
48
48
  )
@@ -4,7 +4,7 @@ from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
6
  from klaude_code.command.model_select import select_model_interactive
7
- from klaude_code.protocol import commands, events, model, op
7
+ from klaude_code.protocol import commands, events, message, model, op
8
8
  from klaude_code.ui.terminal.selector import SelectItem, select_one
9
9
 
10
10
  SELECT_STYLE = Style(
@@ -65,7 +65,7 @@ class ModelCommand(CommandABC):
65
65
  def placeholder(self) -> str:
66
66
  return "model name"
67
67
 
68
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
68
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
69
69
  selected_model = await asyncio.to_thread(select_model_interactive, preferred=user_input.text)
70
70
 
71
71
  current_model = agent.profile.llm_client.model_name if agent.profile else None
@@ -74,8 +74,8 @@ class ModelCommand(CommandABC):
74
74
  events=[
75
75
  events.DeveloperMessageEvent(
76
76
  session_id=agent.session.id,
77
- item=model.DeveloperMessageItem(
78
- content="(no change)",
77
+ item=message.DeveloperMessage(
78
+ parts=message.text_parts_from_str("(no change)"),
79
79
  command_output=model.CommandOutput(command_name=self.name),
80
80
  ),
81
81
  )
@@ -50,7 +50,7 @@ def select_model_interactive(preferred: str | None = None) -> str | None:
50
50
  selected = select_one(
51
51
  message=message,
52
52
  items=items,
53
- pointer="->",
53
+ pointer="",
54
54
  use_search_filter=True,
55
55
  initial_value=config.main_model,
56
56
  style=Style(
@@ -0,0 +1,82 @@
1
+ ---
2
+ description: Commit current git changes
3
+ ---
4
+
5
+ ## Workflow
6
+
7
+ ### Step 1: Detect version control system
8
+
9
+ Check if `jj` is available in the current environment. (check in your <env> tag)
10
+
11
+ ### Step 2: Run pre-commit checks
12
+
13
+ Before creating a commit, run the following checks:
14
+
15
+ 1. Run the project's linter to check and fix code style issues
16
+ 2. Run the project's test suite to ensure all tests pass
17
+ 3. If either check fails, stop the commit process and report the errors to the user
18
+ 4. If both checks pass, proceed to the next step
19
+
20
+ ### Step 3A: If jj is available
21
+
22
+ 1. Run `jj status` and `jj log -r 'ancestors(@, 10)'` to see working copy changes and the last 10 changes
23
+ 2. For each change that has no description (shows as "(no description set)"):
24
+ - Run `jj diff -r <change_id> --git` to view the diff
25
+ - Read related files if needed to understand the context
26
+ - Use `jj describe -r <change_id>` to add a meaningful description
27
+
28
+ ### Step 2B: If jj is not available (git)
29
+
30
+ 1. Run `git status` to check working directory state
31
+ 2. Run `git diff --cached` to check if there are staged changes
32
+ 3. If staging area has content:
33
+ - Ask the user: "Staging area has changes. Commit only staged changes, or stage and commit all changes?"
34
+ - If user chooses staged only: proceed with staged changes
35
+ - If user chooses all: run `git add -A` first
36
+ 4. If staging area is empty:
37
+ - Run `git add -A` to stage all changes
38
+ 5. Review the changes with `git diff --cached`
39
+ 6. Create the commit
40
+
41
+ ## Commit Message Format
42
+
43
+ In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC:
44
+
45
+ For jj:
46
+ ```bash
47
+ jj describe -m "$(cat <<'EOF'
48
+ Commit message here.
49
+ EOF
50
+ )"
51
+ ```
52
+
53
+ For git:
54
+ ```bash
55
+ git commit -m "$(cat <<'EOF'
56
+ Commit message here.
57
+ EOF
58
+ )"
59
+ ```
60
+
61
+ ## Message Style
62
+
63
+ Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
64
+
65
+ ```
66
+ <type>(<scope>): <description>
67
+ ```
68
+
69
+ Types:
70
+ - `feat`: New feature
71
+ - `fix`: Bug fix
72
+ - `docs`: Documentation changes
73
+ - `style`: Code style changes (formatting, no logic change)
74
+ - `refactor`: Code refactoring (no feature or fix)
75
+ - `test`: Adding or updating tests
76
+ - `chore`: Build process, dependencies, or tooling changes
77
+
78
+ Examples:
79
+ - `feat(cli): add --verbose flag for debug output`
80
+ - `fix(llm): handle API timeout errors gracefully`
81
+ - `docs(readme): update installation instructions`
82
+ - `refactor(core): simplify session state management`
@@ -3,7 +3,7 @@ from importlib.resources import files
3
3
  import yaml
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
- from klaude_code.protocol import commands, model, op
6
+ from klaude_code.protocol import commands, message, op
7
7
  from klaude_code.trace import log_debug
8
8
 
9
9
 
@@ -55,7 +55,7 @@ class PromptCommand(CommandABC):
55
55
  def support_addition_params(self) -> bool:
56
56
  return True
57
57
 
58
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
58
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
59
59
  self._ensure_loaded()
60
60
  template_content = self._content or ""
61
61
  user_input_text = user_input.text.strip() or "<none>"
@@ -71,7 +71,7 @@ class PromptCommand(CommandABC):
71
71
  operations=[
72
72
  op.RunAgentOperation(
73
73
  session_id=agent.session.id,
74
- input=model.UserInputPayload(text=final_prompt, images=user_input.images),
74
+ input=message.UserInputPayload(text=final_prompt, images=user_input.images),
75
75
  )
76
76
  ]
77
77
  )
@@ -1,5 +1,5 @@
1
1
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
2
- from klaude_code.protocol import commands, events, model
2
+ from klaude_code.protocol import commands, events, message
3
3
 
4
4
 
5
5
  class RefreshTerminalCommand(CommandABC):
@@ -17,7 +17,7 @@ class RefreshTerminalCommand(CommandABC):
17
17
  def is_interactive(self) -> bool:
18
18
  return True
19
19
 
20
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
20
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
21
21
  del user_input # unused
22
22
  import os
23
23
 
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
3
3
 
4
4
  from klaude_code.command.command_abc import Agent, CommandResult
5
5
  from klaude_code.command.prompt_command import PromptCommand
6
- from klaude_code.protocol import commands, events, model, op
6
+ from klaude_code.protocol import commands, events, message, model, op
7
7
  from klaude_code.trace import log_debug
8
8
 
9
9
  if TYPE_CHECKING:
@@ -133,7 +133,7 @@ def is_slash_command_name(name: str) -> bool:
133
133
  return _resolve_command_key(name) is not None
134
134
 
135
135
 
136
- async def dispatch_command(user_input: model.UserInputPayload, agent: Agent, *, submission_id: str) -> CommandResult:
136
+ async def dispatch_command(user_input: message.UserInputPayload, agent: Agent, *, submission_id: str) -> CommandResult:
137
137
  _ensure_commands_loaded()
138
138
  # Detect command name
139
139
  raw = user_input.text
@@ -168,7 +168,7 @@ async def dispatch_command(user_input: model.UserInputPayload, agent: Agent, *,
168
168
  command_identifier: commands.CommandName | str = command.name
169
169
 
170
170
  try:
171
- user_input_for_command = model.UserInputPayload(text=rest, images=user_input.images)
171
+ user_input_for_command = message.UserInputPayload(text=rest, images=user_input.images)
172
172
  result = await command.run(agent, user_input_for_command)
173
173
  ops = list(result.operations or [])
174
174
  for operation in ops:
@@ -187,8 +187,10 @@ async def dispatch_command(user_input: model.UserInputPayload, agent: Agent, *,
187
187
  events=[
188
188
  events.DeveloperMessageEvent(
189
189
  session_id=agent.session.id,
190
- item=model.DeveloperMessageItem(
191
- content=f"Command {command_identifier} error: [{e.__class__.__name__}] {e!s}",
190
+ item=message.DeveloperMessage(
191
+ parts=message.text_parts_from_str(
192
+ f"Command {command_identifier} error: [{e.__class__.__name__}] {e!s}"
193
+ ),
192
194
  command_output=command_output,
193
195
  ),
194
196
  )
@@ -1,7 +1,7 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
4
- from klaude_code.protocol import commands, events, model
4
+ from klaude_code.protocol import commands, events, message, model
5
5
 
6
6
 
7
7
  def _read_changelog() -> str:
@@ -68,15 +68,15 @@ class ReleaseNotesCommand(CommandABC):
68
68
  def summary(self) -> str:
69
69
  return "Show the latest release notes"
70
70
 
71
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
71
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
72
72
  del user_input # unused
73
73
  changelog = _read_changelog()
74
74
  content = _extract_releases(changelog, count=10)
75
75
 
76
76
  event = events.DeveloperMessageEvent(
77
77
  session_id=agent.session.id,
78
- item=model.DeveloperMessageItem(
79
- content=content,
78
+ item=message.DeveloperMessage(
79
+ parts=message.text_parts_from_str(content),
80
80
  command_output=model.CommandOutput(command_name=self.name),
81
81
  ),
82
82
  )
@@ -3,7 +3,7 @@ import asyncio
3
3
  from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
- from klaude_code.protocol import commands, events, model, op
6
+ from klaude_code.protocol import commands, events, message, model, op
7
7
  from klaude_code.session.selector import build_session_select_options, format_user_messages_display
8
8
  from klaude_code.trace import log
9
9
  from klaude_code.ui.terminal.selector import SelectItem, select_one
@@ -35,14 +35,16 @@ def select_session_sync() -> str | None:
35
35
  display_msgs = format_user_messages_display(opt.user_messages)
36
36
  title: list[tuple[str, str]] = []
37
37
  title.append(("fg:ansibrightblack", f"{idx:2}. "))
38
- title.append(
39
- ("class:meta", f"{opt.relative_time} · {opt.messages_count} · {opt.model_name} · {opt.session_id}\n")
40
- )
41
- for msg in display_msgs:
38
+ title.append(("class:meta", f"{opt.relative_time} · {opt.messages_count} · {opt.model_name}"))
39
+ title.append(("fg:ansibrightblack dim", f" · {opt.session_id}\n"))
40
+ for i, msg in enumerate(display_msgs):
41
+ is_last = i == len(display_msgs) - 1
42
42
  if msg == "⋮":
43
43
  title.append(("class:msg", f" {msg}\n"))
44
44
  else:
45
- title.append(("class:msg", f" > {msg}\n"))
45
+ prefix = "└─" if is_last else "├─"
46
+ title.append(("fg:ansibrightblack dim", f" {prefix} "))
47
+ title.append(("class:msg", f"{msg}\n"))
46
48
  title.append(("", "\n"))
47
49
 
48
50
  search_text = " ".join(opt.user_messages) + f" {opt.model_name} {opt.session_id}"
@@ -80,14 +82,16 @@ class ResumeCommand(CommandABC):
80
82
  def is_interactive(self) -> bool:
81
83
  return True
82
84
 
83
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
85
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
84
86
  del user_input # unused
85
87
 
86
88
  if agent.session.messages_count > 0:
87
89
  event = events.DeveloperMessageEvent(
88
90
  session_id=agent.session.id,
89
- item=model.DeveloperMessageItem(
90
- content="Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection.",
91
+ item=message.DeveloperMessage(
92
+ parts=message.text_parts_from_str(
93
+ "Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection."
94
+ ),
91
95
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
92
96
  ),
93
97
  )
@@ -97,8 +101,8 @@ class ResumeCommand(CommandABC):
97
101
  if selected_session_id is None:
98
102
  event = events.DeveloperMessageEvent(
99
103
  session_id=agent.session.id,
100
- item=model.DeveloperMessageItem(
101
- content="(no session selected)",
104
+ item=message.DeveloperMessage(
105
+ parts=message.text_parts_from_str("(no session selected)"),
102
106
  command_output=model.CommandOutput(command_name=self.name),
103
107
  ),
104
108
  )
@@ -1,5 +1,5 @@
1
1
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
2
- from klaude_code.protocol import commands, events, model
2
+ from klaude_code.protocol import commands, events, message, model
3
3
  from klaude_code.session.session import Session
4
4
 
5
5
 
@@ -132,15 +132,15 @@ class StatusCommand(CommandABC):
132
132
  def summary(self) -> str:
133
133
  return "Show session usage statistics"
134
134
 
135
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
135
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
136
136
  del user_input # unused
137
137
  session = agent.session
138
138
  aggregated = accumulate_session_usage(session)
139
139
 
140
140
  event = events.DeveloperMessageEvent(
141
141
  session_id=session.id,
142
- item=model.DeveloperMessageItem(
143
- content=format_status_content(aggregated),
142
+ item=message.DeveloperMessage(
143
+ parts=message.text_parts_from_str(format_status_content(aggregated)),
144
144
  command_output=model.CommandOutput(
145
145
  command_name=self.name,
146
146
  ui_extra=model.SessionStatusUIExtra(
@@ -3,7 +3,7 @@ import subprocess
3
3
  from pathlib import Path
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
- from klaude_code.protocol import commands, events, model
6
+ from klaude_code.protocol import commands, events, message, model
7
7
 
8
8
 
9
9
  class TerminalSetupCommand(CommandABC):
@@ -21,7 +21,7 @@ class TerminalSetupCommand(CommandABC):
21
21
  def is_interactive(self) -> bool:
22
22
  return False
23
23
 
24
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
24
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
25
25
  del user_input # unused
26
26
  term_program = os.environ.get("TERM_PROGRAM", "").lower()
27
27
 
@@ -221,28 +221,28 @@ class TerminalSetupCommand(CommandABC):
221
221
 
222
222
  return message
223
223
 
224
- def _create_success_result(self, agent: "Agent", message: str) -> CommandResult:
224
+ def _create_success_result(self, agent: "Agent", msg: str) -> CommandResult:
225
225
  """Create success result"""
226
226
  return CommandResult(
227
227
  events=[
228
228
  events.DeveloperMessageEvent(
229
229
  session_id=agent.session.id,
230
- item=model.DeveloperMessageItem(
231
- content=message,
230
+ item=message.DeveloperMessage(
231
+ parts=message.text_parts_from_str(msg),
232
232
  command_output=model.CommandOutput(command_name=self.name, is_error=False),
233
233
  ),
234
234
  )
235
235
  ]
236
236
  )
237
237
 
238
- def _create_error_result(self, agent: "Agent", message: str) -> CommandResult:
238
+ def _create_error_result(self, agent: "Agent", msg: str) -> CommandResult:
239
239
  """Create error result"""
240
240
  return CommandResult(
241
241
  events=[
242
242
  events.DeveloperMessageEvent(
243
243
  session_id=agent.session.id,
244
- item=model.DeveloperMessageItem(
245
- content=message,
244
+ item=message.DeveloperMessage(
245
+ parts=message.text_parts_from_str(msg),
246
246
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
247
247
  ),
248
248
  )
@@ -4,7 +4,7 @@ from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
6
  from klaude_code.config.thinking import get_thinking_picker_data, parse_thinking_value
7
- from klaude_code.protocol import commands, events, llm_param, model, op
7
+ from klaude_code.protocol import commands, events, llm_param, message, model, op
8
8
  from klaude_code.ui.terminal.selector import SelectItem, select_one
9
9
 
10
10
  SELECT_STYLE = Style(
@@ -67,7 +67,7 @@ class ThinkingCommand(CommandABC):
67
67
  def is_interactive(self) -> bool:
68
68
  return True
69
69
 
70
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
70
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
71
71
  del user_input # unused
72
72
  if agent.profile is None:
73
73
  return CommandResult(events=[])
@@ -80,8 +80,8 @@ class ThinkingCommand(CommandABC):
80
80
  events=[
81
81
  events.DeveloperMessageEvent(
82
82
  session_id=agent.session.id,
83
- item=model.DeveloperMessageItem(
84
- content="(no change)",
83
+ item=message.DeveloperMessage(
84
+ parts=message.text_parts_from_str("(no change)"),
85
85
  command_output=model.CommandOutput(command_name=self.name),
86
86
  ),
87
87
  )