klaude-code 2.0.1__py3-none-any.whl → 2.1.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 (160) hide show
  1. klaude_code/app/__init__.py +12 -0
  2. klaude_code/app/runtime.py +215 -0
  3. klaude_code/cli/auth_cmd.py +2 -2
  4. klaude_code/cli/config_cmd.py +2 -2
  5. klaude_code/cli/cost_cmd.py +1 -1
  6. klaude_code/cli/debug.py +12 -36
  7. klaude_code/cli/list_model.py +3 -3
  8. klaude_code/cli/main.py +17 -60
  9. klaude_code/cli/self_update.py +2 -187
  10. klaude_code/cli/session_cmd.py +2 -2
  11. klaude_code/config/config.py +1 -1
  12. klaude_code/config/select_model.py +1 -1
  13. klaude_code/const.py +10 -1
  14. klaude_code/core/agent.py +9 -62
  15. klaude_code/core/agent_profile.py +284 -0
  16. klaude_code/core/executor.py +343 -230
  17. klaude_code/core/manager/llm_clients_builder.py +1 -1
  18. klaude_code/core/manager/sub_agent_manager.py +16 -29
  19. klaude_code/core/reminders.py +107 -155
  20. klaude_code/core/task.py +12 -20
  21. klaude_code/core/tool/__init__.py +5 -19
  22. klaude_code/core/tool/context.py +84 -0
  23. klaude_code/core/tool/file/apply_patch_tool.py +18 -21
  24. klaude_code/core/tool/file/edit_tool.py +42 -44
  25. klaude_code/core/tool/file/read_tool.py +14 -9
  26. klaude_code/core/tool/file/write_tool.py +12 -13
  27. klaude_code/core/tool/report_back_tool.py +4 -1
  28. klaude_code/core/tool/shell/bash_tool.py +6 -11
  29. klaude_code/core/tool/skill/skill_tool.py +3 -1
  30. klaude_code/core/tool/sub_agent_tool.py +8 -7
  31. klaude_code/core/tool/todo/todo_write_tool.py +3 -9
  32. klaude_code/core/tool/todo/update_plan_tool.py +3 -5
  33. klaude_code/core/tool/tool_abc.py +2 -1
  34. klaude_code/core/tool/tool_registry.py +2 -33
  35. klaude_code/core/tool/tool_runner.py +13 -10
  36. klaude_code/core/tool/web/mermaid_tool.py +3 -1
  37. klaude_code/core/tool/web/web_fetch_tool.py +5 -3
  38. klaude_code/core/tool/web/web_search_tool.py +5 -3
  39. klaude_code/core/turn.py +86 -26
  40. klaude_code/llm/anthropic/client.py +1 -1
  41. klaude_code/llm/bedrock/client.py +1 -1
  42. klaude_code/llm/claude/client.py +1 -1
  43. klaude_code/llm/codex/client.py +1 -1
  44. klaude_code/llm/google/client.py +1 -1
  45. klaude_code/llm/openai_compatible/client.py +1 -1
  46. klaude_code/llm/openai_compatible/tool_call_accumulator.py +1 -1
  47. klaude_code/llm/openrouter/client.py +1 -1
  48. klaude_code/llm/openrouter/reasoning.py +1 -1
  49. klaude_code/llm/responses/client.py +1 -1
  50. klaude_code/protocol/events/__init__.py +57 -0
  51. klaude_code/protocol/events/base.py +18 -0
  52. klaude_code/protocol/events/chat.py +20 -0
  53. klaude_code/protocol/events/lifecycle.py +22 -0
  54. klaude_code/protocol/events/metadata.py +15 -0
  55. klaude_code/protocol/events/streaming.py +43 -0
  56. klaude_code/protocol/events/system.py +53 -0
  57. klaude_code/protocol/events/tools.py +23 -0
  58. klaude_code/protocol/message.py +3 -11
  59. klaude_code/protocol/model.py +78 -9
  60. klaude_code/protocol/op.py +5 -0
  61. klaude_code/protocol/sub_agent/explore.py +0 -15
  62. klaude_code/protocol/sub_agent/task.py +1 -1
  63. klaude_code/protocol/sub_agent/web.py +1 -17
  64. klaude_code/protocol/tools.py +0 -1
  65. klaude_code/session/session.py +6 -5
  66. klaude_code/skill/assets/create-plan/SKILL.md +76 -0
  67. klaude_code/skill/loader.py +1 -1
  68. klaude_code/skill/system_skills.py +1 -1
  69. klaude_code/tui/__init__.py +8 -0
  70. klaude_code/{command → tui/command}/clear_cmd.py +2 -1
  71. klaude_code/{command → tui/command}/debug_cmd.py +4 -3
  72. klaude_code/{command → tui/command}/export_cmd.py +2 -1
  73. klaude_code/{command → tui/command}/export_online_cmd.py +6 -5
  74. klaude_code/{command → tui/command}/fork_session_cmd.py +10 -9
  75. klaude_code/{command → tui/command}/help_cmd.py +3 -2
  76. klaude_code/{command → tui/command}/model_cmd.py +5 -4
  77. klaude_code/{command → tui/command}/model_select.py +2 -2
  78. klaude_code/{command → tui/command}/prompt_command.py +4 -3
  79. klaude_code/{command → tui/command}/refresh_cmd.py +3 -1
  80. klaude_code/{command → tui/command}/registry.py +16 -6
  81. klaude_code/{command → tui/command}/release_notes_cmd.py +3 -2
  82. klaude_code/{command → tui/command}/resume_cmd.py +6 -5
  83. klaude_code/{command → tui/command}/status_cmd.py +4 -3
  84. klaude_code/{command → tui/command}/terminal_setup_cmd.py +4 -3
  85. klaude_code/{command → tui/command}/thinking_cmd.py +4 -3
  86. klaude_code/tui/commands.py +164 -0
  87. klaude_code/{ui/renderers → tui/components}/assistant.py +3 -3
  88. klaude_code/{ui/renderers → tui/components}/bash_syntax.py +2 -2
  89. klaude_code/{ui/renderers → tui/components}/common.py +1 -1
  90. klaude_code/tui/components/developer.py +231 -0
  91. klaude_code/{ui/renderers → tui/components}/diffs.py +2 -2
  92. klaude_code/{ui/renderers → tui/components}/errors.py +2 -2
  93. klaude_code/{ui/renderers → tui/components}/metadata.py +34 -21
  94. klaude_code/{ui → tui/components}/rich/markdown.py +78 -34
  95. klaude_code/{ui → tui/components}/rich/status.py +2 -2
  96. klaude_code/{ui → tui/components}/rich/theme.py +12 -5
  97. klaude_code/{ui/renderers → tui/components}/sub_agent.py +23 -43
  98. klaude_code/{ui/renderers → tui/components}/thinking.py +3 -3
  99. klaude_code/{ui/renderers → tui/components}/tools.py +11 -48
  100. klaude_code/{ui/renderers → tui/components}/user_input.py +3 -20
  101. klaude_code/tui/display.py +85 -0
  102. klaude_code/{ui/modes/repl → tui/input}/__init__.py +1 -1
  103. klaude_code/{ui/modes/repl → tui/input}/completers.py +1 -1
  104. klaude_code/{ui/modes/repl/input_prompt_toolkit.py → tui/input/prompt_toolkit.py} +11 -7
  105. klaude_code/tui/machine.py +606 -0
  106. klaude_code/tui/renderer.py +707 -0
  107. klaude_code/tui/runner.py +321 -0
  108. klaude_code/tui/terminal/__init__.py +56 -0
  109. klaude_code/{ui → tui}/terminal/color.py +1 -1
  110. klaude_code/{ui → tui}/terminal/control.py +1 -1
  111. klaude_code/{ui → tui}/terminal/notifier.py +1 -1
  112. klaude_code/{ui → tui}/terminal/selector.py +36 -17
  113. klaude_code/ui/__init__.py +6 -50
  114. klaude_code/ui/core/display.py +3 -3
  115. klaude_code/ui/core/input.py +2 -1
  116. klaude_code/ui/{modes/debug/display.py → debug_mode.py} +1 -1
  117. klaude_code/ui/{modes/exec/display.py → exec_mode.py} +1 -4
  118. klaude_code/ui/terminal/__init__.py +6 -54
  119. klaude_code/ui/terminal/title.py +31 -0
  120. klaude_code/update.py +163 -0
  121. {klaude_code-2.0.1.dist-info → klaude_code-2.1.0.dist-info}/METADATA +1 -1
  122. klaude_code-2.1.0.dist-info/RECORD +235 -0
  123. klaude_code/cli/runtime.py +0 -525
  124. klaude_code/core/prompt.py +0 -108
  125. klaude_code/core/tool/file/move_tool.md +0 -41
  126. klaude_code/core/tool/file/move_tool.py +0 -435
  127. klaude_code/core/tool/tool_context.py +0 -148
  128. klaude_code/protocol/events.py +0 -194
  129. klaude_code/skill/assets/dev-docs/SKILL.md +0 -108
  130. klaude_code/trace/__init__.py +0 -21
  131. klaude_code/ui/core/stage_manager.py +0 -48
  132. klaude_code/ui/modes/__init__.py +0 -1
  133. klaude_code/ui/modes/debug/__init__.py +0 -1
  134. klaude_code/ui/modes/exec/__init__.py +0 -1
  135. klaude_code/ui/modes/repl/display.py +0 -61
  136. klaude_code/ui/modes/repl/event_handler.py +0 -634
  137. klaude_code/ui/modes/repl/renderer.py +0 -463
  138. klaude_code/ui/renderers/developer.py +0 -215
  139. klaude_code/ui/utils/__init__.py +0 -1
  140. klaude_code-2.0.1.dist-info/RECORD +0 -229
  141. /klaude_code/{trace/log.py → log.py} +0 -0
  142. /klaude_code/{command → tui/command}/__init__.py +0 -0
  143. /klaude_code/{command → tui/command}/command_abc.py +0 -0
  144. /klaude_code/{command → tui/command}/prompt-commit.md +0 -0
  145. /klaude_code/{command → tui/command}/prompt-init.md +0 -0
  146. /klaude_code/{ui/renderers → tui/components}/__init__.py +0 -0
  147. /klaude_code/{ui/renderers → tui/components}/mermaid_viewer.py +0 -0
  148. /klaude_code/{ui → tui/components}/rich/__init__.py +0 -0
  149. /klaude_code/{ui → tui/components}/rich/cjk_wrap.py +0 -0
  150. /klaude_code/{ui → tui/components}/rich/code_panel.py +0 -0
  151. /klaude_code/{ui → tui/components}/rich/live.py +0 -0
  152. /klaude_code/{ui → tui/components}/rich/quote.py +0 -0
  153. /klaude_code/{ui → tui/components}/rich/searchable_text.py +0 -0
  154. /klaude_code/{ui/modes/repl → tui/input}/clipboard.py +0 -0
  155. /klaude_code/{ui/modes/repl → tui/input}/key_bindings.py +0 -0
  156. /klaude_code/{ui → tui}/terminal/image.py +0 -0
  157. /klaude_code/{ui → tui}/terminal/progress_bar.py +0 -0
  158. /klaude_code/ui/{utils/common.py → common.py} +0 -0
  159. {klaude_code-2.0.1.dist-info → klaude_code-2.1.0.dist-info}/WHEEL +0 -0
  160. {klaude_code-2.0.1.dist-info → klaude_code-2.1.0.dist-info}/entry_points.txt +0 -0
@@ -6,8 +6,8 @@ from pathlib import Path
6
6
 
7
7
  from pydantic import BaseModel, field_validator
8
8
 
9
+ from klaude_code.core.tool.context import ToolContext
9
10
  from klaude_code.core.tool.tool_abc import ToolABC, load_desc
10
- from klaude_code.core.tool.tool_context import get_current_todo_context
11
11
  from klaude_code.core.tool.tool_registry import register
12
12
  from klaude_code.protocol import llm_param, message, model, tools
13
13
 
@@ -79,15 +79,13 @@ class UpdatePlanTool(ToolABC):
79
79
  )
80
80
 
81
81
  @classmethod
82
- async def call(cls, arguments: str) -> message.ToolResultMessage:
82
+ async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
83
83
  try:
84
84
  args = UpdatePlanArguments.model_validate_json(arguments)
85
85
  except ValueError as exc:
86
86
  return message.ToolResultMessage(status="error", output_text=f"Invalid arguments: {exc}")
87
87
 
88
- todo_context = get_current_todo_context()
89
- if todo_context is None:
90
- return message.ToolResultMessage(status="error", output_text="No active session found")
88
+ todo_context = context.todo_context
91
89
 
92
90
  new_todos = [model.TodoItem(content=item.step, status=item.status) for item in args.plan]
93
91
  old_todos = todo_context.get_todos()
@@ -4,6 +4,7 @@ from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from pathlib import Path
6
6
 
7
+ from klaude_code.core.tool.context import ToolContext
7
8
  from klaude_code.protocol import llm_param, message
8
9
 
9
10
 
@@ -27,7 +28,7 @@ class ToolABC(ABC):
27
28
 
28
29
  @classmethod
29
30
  @abstractmethod
30
- async def call(cls, arguments: str) -> message.ToolResultMessage:
31
+ async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
31
32
  raise NotImplementedError
32
33
 
33
34
 
@@ -3,8 +3,8 @@ from typing import TypeVar
3
3
 
4
4
  from klaude_code.core.tool.sub_agent_tool import SubAgentTool
5
5
  from klaude_code.core.tool.tool_abc import ToolABC
6
- from klaude_code.protocol import llm_param, tools
7
- from klaude_code.protocol.sub_agent import get_sub_agent_profile, iter_sub_agent_profiles, sub_agent_tool_names
6
+ from klaude_code.protocol import llm_param
7
+ from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
8
8
 
9
9
  _REGISTRY: dict[str, type[ToolABC]] = {}
10
10
 
@@ -45,34 +45,3 @@ def get_tool_schemas(tool_names: list[str]) -> list[llm_param.ToolSchema]:
45
45
  def get_registry() -> dict[str, type[ToolABC]]:
46
46
  """Get the global tool registry."""
47
47
  return _REGISTRY
48
-
49
-
50
- def load_agent_tools(
51
- model_name: str, sub_agent_type: tools.SubAgentType | None = None, *, vanilla: bool = False
52
- ) -> list[llm_param.ToolSchema]:
53
- """Get tools for an agent based on model and agent type.
54
-
55
- Args:
56
- model_name: The model name.
57
- sub_agent_type: If None, returns main agent tools. Otherwise returns sub-agent tools.
58
- vanilla: If True, returns minimal vanilla tools (ignores sub_agent_type).
59
- """
60
- if vanilla:
61
- return get_tool_schemas([tools.BASH, tools.EDIT, tools.WRITE, tools.READ])
62
-
63
- if sub_agent_type is not None:
64
- profile = get_sub_agent_profile(sub_agent_type)
65
- return get_tool_schemas(list(profile.tool_set))
66
-
67
- # Main agent tools
68
- if "gpt-5" in model_name:
69
- tool_names = [tools.BASH, tools.READ, tools.APPLY_PATCH, tools.MOVE, tools.UPDATE_PLAN]
70
- elif "gemini-3" in model_name:
71
- tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.MOVE]
72
- else:
73
- tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.MOVE, tools.TODO_WRITE]
74
-
75
- tool_names.extend(sub_agent_tool_names(enabled_only=True, model_name=model_name))
76
- tool_names.extend([tools.SKILL, tools.MERMAID])
77
- # tool_names.extend([tools.MEMORY])
78
- return get_tool_schemas(tool_names)
@@ -3,9 +3,9 @@ from collections.abc import AsyncGenerator, Callable, Iterable, Sequence
3
3
  from dataclasses import dataclass
4
4
 
5
5
  from klaude_code.const import CANCEL_OUTPUT
6
+ from klaude_code.core.tool.context import ToolContext
6
7
  from klaude_code.core.tool.report_back_tool import ReportBackTool
7
8
  from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy
8
- from klaude_code.core.tool.tool_context import current_sub_agent_session_id_recorder
9
9
  from klaude_code.core.tool.truncation import truncate_tool_output
10
10
  from klaude_code.protocol import message, model, tools
11
11
 
@@ -18,19 +18,24 @@ class ToolCallRequest:
18
18
  arguments_json: str
19
19
 
20
20
 
21
- async def run_tool(tool_call: ToolCallRequest, registry: dict[str, type[ToolABC]]) -> message.ToolResultMessage:
21
+ async def run_tool(
22
+ tool_call: ToolCallRequest,
23
+ registry: dict[str, type[ToolABC]],
24
+ context: ToolContext,
25
+ ) -> message.ToolResultMessage:
22
26
  """Execute a tool call and return the result.
23
27
 
24
28
  Args:
25
29
  tool_call: The tool call to execute.
26
30
  registry: The tool registry mapping tool names to tool classes.
31
+ context: The explicit tool execution context.
27
32
 
28
33
  Returns:
29
34
  The result of the tool execution.
30
35
  """
31
36
  # Special handling for report_back tool (not registered in global registry)
32
37
  if tool_call.tool_name == tools.REPORT_BACK:
33
- tool_result = await ReportBackTool.call(tool_call.arguments_json)
38
+ tool_result = await ReportBackTool.call(tool_call.arguments_json, context)
34
39
  tool_result.call_id = tool_call.call_id
35
40
  tool_result.tool_name = tool_call.tool_name
36
41
  return tool_result
@@ -43,7 +48,7 @@ async def run_tool(tool_call: ToolCallRequest, registry: dict[str, type[ToolABC]
43
48
  tool_name=tool_call.tool_name,
44
49
  )
45
50
  try:
46
- tool_result = await registry[tool_call.tool_name].call(tool_call.arguments_json)
51
+ tool_result = await registry[tool_call.tool_name].call(tool_call.arguments_json, context)
47
52
  tool_result.call_id = tool_call.call_id
48
53
  tool_result.tool_name = tool_call.tool_name
49
54
  if tool_result.output_text:
@@ -109,9 +114,11 @@ class ToolExecutor:
109
114
  def __init__(
110
115
  self,
111
116
  *,
117
+ context: ToolContext,
112
118
  registry: dict[str, type[ToolABC]],
113
119
  append_history: Callable[[Sequence[message.HistoryEvent]], None],
114
120
  ) -> None:
121
+ self._context = context
115
122
  self._registry = registry
116
123
  self._append_history = append_history
117
124
 
@@ -268,15 +275,11 @@ class ToolExecutor:
268
275
 
269
276
  async def _run_single_tool_call(self, tool_call: ToolCallRequest) -> list[ToolExecutorEvent]:
270
277
  def _record_sub_agent_session_id(session_id: str) -> None:
271
- # Keep the first recorded id if multiple writes happen.
272
278
  if tool_call.call_id not in self._sub_agent_session_ids:
273
279
  self._sub_agent_session_ids[tool_call.call_id] = session_id
274
280
 
275
- recorder_token = current_sub_agent_session_id_recorder.set(_record_sub_agent_session_id)
276
- try:
277
- tool_result: message.ToolResultMessage = await run_tool(tool_call, self._registry)
278
- finally:
279
- current_sub_agent_session_id_recorder.reset(recorder_token)
281
+ call_context = self._context.with_record_sub_agent_session_id(_record_sub_agent_session_id)
282
+ tool_result: message.ToolResultMessage = await run_tool(tool_call, self._registry, call_context)
280
283
 
281
284
  self._append_history([tool_result])
282
285
 
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from pydantic import BaseModel, Field
9
9
 
10
10
  from klaude_code.const import MERMAID_LIVE_PREFIX
11
+ from klaude_code.core.tool.context import ToolContext
11
12
  from klaude_code.core.tool.tool_abc import ToolABC, load_desc
12
13
  from klaude_code.core.tool.tool_registry import register
13
14
  from klaude_code.protocol import llm_param, message, model, tools
@@ -40,7 +41,8 @@ class MermaidTool(ToolABC):
40
41
  )
41
42
 
42
43
  @classmethod
43
- async def call(cls, arguments: str) -> message.ToolResultMessage:
44
+ async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
45
+ del context
44
46
  try:
45
47
  args = cls.MermaidArguments.model_validate_json(arguments)
46
48
  except Exception as exc: # pragma: no cover - defensive
@@ -16,6 +16,7 @@ from klaude_code.const import (
16
16
  WEB_FETCH_DEFAULT_TIMEOUT_SEC,
17
17
  WEB_FETCH_USER_AGENT,
18
18
  )
19
+ from klaude_code.core.tool.context import ToolContext
19
20
  from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy, ToolMetadata, load_desc
20
21
  from klaude_code.core.tool.tool_registry import register
21
22
  from klaude_code.protocol import llm_param, message, tools
@@ -213,7 +214,7 @@ class WebFetchTool(ToolABC):
213
214
  url: str
214
215
 
215
216
  @classmethod
216
- async def call(cls, arguments: str) -> message.ToolResultMessage:
217
+ async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
217
218
  try:
218
219
  args = WebFetchTool.WebFetchArguments.model_validate_json(arguments)
219
220
  except ValueError as e:
@@ -221,10 +222,11 @@ class WebFetchTool(ToolABC):
221
222
  status="error",
222
223
  output_text=f"Invalid arguments: {e}",
223
224
  )
224
- return await cls.call_with_args(args)
225
+ return await cls.call_with_args(args, context)
225
226
 
226
227
  @classmethod
227
- async def call_with_args(cls, args: WebFetchArguments) -> message.ToolResultMessage:
228
+ async def call_with_args(cls, args: WebFetchArguments, context: ToolContext) -> message.ToolResultMessage:
229
+ del context
228
230
  url = args.url
229
231
 
230
232
  # Basic URL validation
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from pydantic import BaseModel
6
6
 
7
7
  from klaude_code.const import WEB_SEARCH_DEFAULT_MAX_RESULTS, WEB_SEARCH_MAX_RESULTS_LIMIT
8
+ from klaude_code.core.tool.context import ToolContext
8
9
  from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy, ToolMetadata, load_desc
9
10
  from klaude_code.core.tool.tool_registry import register
10
11
  from klaude_code.protocol import llm_param, message, tools
@@ -91,7 +92,7 @@ class WebSearchTool(ToolABC):
91
92
  max_results: int = WEB_SEARCH_DEFAULT_MAX_RESULTS
92
93
 
93
94
  @classmethod
94
- async def call(cls, arguments: str) -> message.ToolResultMessage:
95
+ async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
95
96
  try:
96
97
  args = WebSearchTool.WebSearchArguments.model_validate_json(arguments)
97
98
  except ValueError as e:
@@ -99,10 +100,11 @@ class WebSearchTool(ToolABC):
99
100
  status="error",
100
101
  output_text=f"Invalid arguments: {e}",
101
102
  )
102
- return await cls.call_with_args(args)
103
+ return await cls.call_with_args(args, context)
103
104
 
104
105
  @classmethod
105
- async def call_with_args(cls, args: WebSearchArguments) -> message.ToolResultMessage:
106
+ async def call_with_args(cls, args: WebSearchArguments, context: ToolContext) -> message.ToolResultMessage:
107
+ del context
106
108
  query = args.query.strip()
107
109
  if not query:
108
110
  return message.ToolResultMessage(
klaude_code/core/turn.py CHANGED
@@ -5,8 +5,8 @@ from dataclasses import dataclass, field
5
5
  from typing import TYPE_CHECKING
6
6
 
7
7
  from klaude_code.const import INTERRUPT_MARKER, SUPPORTED_IMAGE_SIZES
8
- from klaude_code.core.tool import ToolABC, tool_context
9
- from klaude_code.core.tool.tool_context import current_sub_agent_resume_claims
8
+ from klaude_code.core.tool import ToolABC
9
+ from klaude_code.core.tool.context import SubAgentResumeClaims, ToolContext
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from klaude_code.core.task import SessionContext
@@ -20,8 +20,8 @@ from klaude_code.core.tool.tool_runner import (
20
20
  ToolExecutorEvent,
21
21
  )
22
22
  from klaude_code.llm import LLMClientABC
23
+ from klaude_code.log import DebugType, log_debug
23
24
  from klaude_code.protocol import events, llm_param, message, model, tools
24
- from klaude_code.trace import DebugType, log_debug
25
25
 
26
26
 
27
27
  class TurnError(Exception):
@@ -206,6 +206,8 @@ class TurnExecutor:
206
206
 
207
207
  ctx = self._context
208
208
  session_ctx = ctx.session_ctx
209
+ thinking_active = False
210
+ assistant_text_active = False
209
211
  message_types = (
210
212
  message.SystemMessage,
211
213
  message.DeveloperMessage,
@@ -247,12 +249,30 @@ class TurnExecutor:
247
249
  )
248
250
  match delta:
249
251
  case message.ThinkingTextDelta() as delta:
252
+ if not thinking_active:
253
+ thinking_active = True
254
+ yield events.ThinkingStartEvent(
255
+ response_id=delta.response_id,
256
+ session_id=session_ctx.session_id,
257
+ )
250
258
  yield events.ThinkingDeltaEvent(
251
259
  content=delta.content,
252
260
  response_id=delta.response_id,
253
261
  session_id=session_ctx.session_id,
254
262
  )
255
263
  case message.AssistantTextDelta() as delta:
264
+ if thinking_active:
265
+ thinking_active = False
266
+ yield events.ThinkingEndEvent(
267
+ response_id=delta.response_id,
268
+ session_id=session_ctx.session_id,
269
+ )
270
+ if not assistant_text_active:
271
+ assistant_text_active = True
272
+ yield events.AssistantTextStartEvent(
273
+ response_id=delta.response_id,
274
+ session_id=session_ctx.session_id,
275
+ )
256
276
  if delta.response_id:
257
277
  self._assistant_response_id = delta.response_id
258
278
  self._assistant_delta_buffer.append(delta.content)
@@ -262,6 +282,12 @@ class TurnExecutor:
262
282
  session_id=session_ctx.session_id,
263
283
  )
264
284
  case message.AssistantImageDelta() as delta:
285
+ if thinking_active:
286
+ thinking_active = False
287
+ yield events.ThinkingEndEvent(
288
+ response_id=delta.response_id,
289
+ session_id=session_ctx.session_id,
290
+ )
265
291
  yield events.AssistantImageDeltaEvent(
266
292
  file_path=delta.file_path,
267
293
  response_id=delta.response_id,
@@ -270,6 +296,18 @@ class TurnExecutor:
270
296
  case message.AssistantMessage() as msg:
271
297
  if msg.response_id is None and self._assistant_response_id:
272
298
  msg.response_id = self._assistant_response_id
299
+ if thinking_active:
300
+ thinking_active = False
301
+ yield events.ThinkingEndEvent(
302
+ response_id=msg.response_id,
303
+ session_id=session_ctx.session_id,
304
+ )
305
+ if assistant_text_active:
306
+ assistant_text_active = False
307
+ yield events.AssistantTextEndEvent(
308
+ response_id=msg.response_id,
309
+ session_id=session_ctx.session_id,
310
+ )
273
311
  turn_result.assistant_message = msg
274
312
  for part in msg.parts:
275
313
  if isinstance(part, message.ToolCallPart):
@@ -281,11 +319,16 @@ class TurnExecutor:
281
319
  arguments_json=part.arguments_json,
282
320
  )
283
321
  )
284
- yield events.AssistantMessageEvent(
285
- content=message.join_text_parts(msg.parts),
286
- response_id=msg.response_id,
287
- session_id=session_ctx.session_id,
288
- )
322
+ if msg.stop_reason != "aborted":
323
+ thinking_text = "".join(
324
+ part.text for part in msg.parts if isinstance(part, message.ThinkingTextPart)
325
+ )
326
+ yield events.ResponseCompleteEvent(
327
+ content=message.join_text_parts(msg.parts),
328
+ response_id=msg.response_id,
329
+ session_id=session_ctx.session_id,
330
+ thinking_text=thinking_text or None,
331
+ )
289
332
  if msg.stop_reason == "aborted":
290
333
  yield events.InterruptEvent(session_id=session_ctx.session_id)
291
334
  if msg.usage:
@@ -296,9 +339,9 @@ class TurnExecutor:
296
339
  metadata.model_name = ctx.llm_client.model_name
297
340
  if metadata.provider is None:
298
341
  metadata.provider = ctx.llm_client.get_llm_config().provider_name or None
299
- yield events.ResponseMetadataEvent(
342
+ yield events.UsageEvent(
300
343
  session_id=session_ctx.session_id,
301
- metadata=metadata,
344
+ usage=metadata,
302
345
  )
303
346
  case message.StreamErrorItem() as msg:
304
347
  turn_result.stream_error = msg
@@ -309,12 +352,23 @@ class TurnExecutor:
309
352
  debug_type=DebugType.RESPONSE,
310
353
  )
311
354
  case message.ToolCallStartItem() as msg:
312
- yield events.TurnToolCallStartEvent(
355
+ if thinking_active:
356
+ thinking_active = False
357
+ yield events.ThinkingEndEvent(
358
+ response_id=msg.response_id,
359
+ session_id=session_ctx.session_id,
360
+ )
361
+ if assistant_text_active:
362
+ assistant_text_active = False
363
+ yield events.AssistantTextEndEvent(
364
+ response_id=msg.response_id,
365
+ session_id=session_ctx.session_id,
366
+ )
367
+ yield events.ToolCallStartEvent(
313
368
  session_id=session_ctx.session_id,
314
369
  response_id=msg.response_id,
315
370
  tool_call_id=msg.call_id,
316
371
  tool_name=msg.name,
317
- arguments="",
318
372
  )
319
373
  case _:
320
374
  continue
@@ -332,20 +386,26 @@ class TurnExecutor:
332
386
 
333
387
  ctx = self._context
334
388
  session_ctx = ctx.session_ctx
335
- with tool_context(session_ctx.file_tracker, session_ctx.todo_context):
336
- resume_claims_token = current_sub_agent_resume_claims.set(set())
337
- executor = ToolExecutor(
338
- registry=ctx.tool_registry,
339
- append_history=session_ctx.append_history,
340
- )
341
- self._tool_executor = executor
342
- try:
343
- async for exec_event in executor.run_tools(tool_calls):
344
- for ui_event in build_events_from_tool_executor_event(session_ctx.session_id, exec_event):
345
- yield ui_event
346
- finally:
347
- self._tool_executor = None
348
- current_sub_agent_resume_claims.reset(resume_claims_token)
389
+ tool_context = ToolContext(
390
+ file_tracker=session_ctx.file_tracker,
391
+ todo_context=session_ctx.todo_context,
392
+ session_id=session_ctx.session_id,
393
+ run_subtask=session_ctx.run_subtask,
394
+ sub_agent_resume_claims=SubAgentResumeClaims(),
395
+ )
396
+
397
+ executor = ToolExecutor(
398
+ context=tool_context,
399
+ registry=ctx.tool_registry,
400
+ append_history=session_ctx.append_history,
401
+ )
402
+ self._tool_executor = executor
403
+ try:
404
+ async for exec_event in executor.run_tools(tool_calls):
405
+ for ui_event in build_events_from_tool_executor_event(session_ctx.session_id, exec_event):
406
+ yield ui_event
407
+ finally:
408
+ self._tool_executor = None
349
409
 
350
410
  def _persist_partial_assistant_on_cancel(self) -> None:
351
411
  """Persist streamed assistant text when a turn is interrupted.
@@ -34,8 +34,8 @@ from klaude_code.llm.client import LLMClientABC
34
34
  from klaude_code.llm.input_common import apply_config_defaults
35
35
  from klaude_code.llm.registry import register
36
36
  from klaude_code.llm.usage import MetadataTracker, error_stream_items
37
+ from klaude_code.log import DebugType, log_debug
37
38
  from klaude_code.protocol import llm_param, message, model
38
- from klaude_code.trace import DebugType, log_debug
39
39
 
40
40
 
41
41
  def _map_anthropic_stop_reason(reason: str) -> model.StopReason | None:
@@ -14,8 +14,8 @@ from klaude_code.llm.client import LLMClientABC
14
14
  from klaude_code.llm.input_common import apply_config_defaults
15
15
  from klaude_code.llm.registry import register
16
16
  from klaude_code.llm.usage import MetadataTracker, error_stream_items
17
+ from klaude_code.log import DebugType, log_debug
17
18
  from klaude_code.protocol import llm_param, message
18
- from klaude_code.trace import DebugType, log_debug
19
19
 
20
20
 
21
21
  @register(llm_param.LLMClientProtocol.BEDROCK)
@@ -22,8 +22,8 @@ from klaude_code.llm.client import LLMClientABC
22
22
  from klaude_code.llm.input_common import apply_config_defaults
23
23
  from klaude_code.llm.registry import register
24
24
  from klaude_code.llm.usage import MetadataTracker, error_stream_items
25
+ from klaude_code.log import DebugType, log_debug
25
26
  from klaude_code.protocol import llm_param, message
26
- from klaude_code.trace import DebugType, log_debug
27
27
 
28
28
  _CLAUDE_OAUTH_REQUIRED_BETAS: tuple[str, ...] = (
29
29
  ANTHROPIC_BETA_OAUTH,
@@ -25,8 +25,8 @@ from klaude_code.llm.registry import register
25
25
  from klaude_code.llm.responses.client import parse_responses_stream
26
26
  from klaude_code.llm.responses.input import convert_history_to_input, convert_tool_schema
27
27
  from klaude_code.llm.usage import MetadataTracker, error_stream_items
28
+ from klaude_code.log import DebugType, log_debug
28
29
  from klaude_code.protocol import llm_param, message
29
- from klaude_code.trace import DebugType, log_debug
30
30
 
31
31
 
32
32
  def build_payload(param: llm_param.LLMCallParameter) -> ResponseCreateParamsStreaming:
@@ -26,8 +26,8 @@ from klaude_code.llm.google.input import convert_history_to_contents, convert_to
26
26
  from klaude_code.llm.input_common import apply_config_defaults
27
27
  from klaude_code.llm.registry import register
28
28
  from klaude_code.llm.usage import MetadataTracker
29
+ from klaude_code.log import DebugType, log_debug
29
30
  from klaude_code.protocol import llm_param, message, model
30
- from klaude_code.trace import DebugType, log_debug
31
31
 
32
32
 
33
33
  def _build_config(param: llm_param.LLMCallParameter) -> GenerateContentConfig:
@@ -13,8 +13,8 @@ from klaude_code.llm.openai_compatible.input import convert_history_to_input, co
13
13
  from klaude_code.llm.openai_compatible.stream import DefaultReasoningHandler, parse_chat_completions_stream
14
14
  from klaude_code.llm.registry import register
15
15
  from klaude_code.llm.usage import MetadataTracker
16
+ from klaude_code.log import DebugType, log_debug
16
17
  from klaude_code.protocol import llm_param, message
17
- from klaude_code.trace import DebugType, log_debug
18
18
 
19
19
 
20
20
  def build_payload(param: llm_param.LLMCallParameter) -> tuple[CompletionCreateParamsStreaming, dict[str, object]]:
@@ -4,8 +4,8 @@ from abc import ABC, abstractmethod
4
4
  from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall
5
5
  from pydantic import BaseModel, Field
6
6
 
7
+ from klaude_code.log import log_debug
7
8
  from klaude_code.protocol import message
8
- from klaude_code.trace.log import log_debug
9
9
 
10
10
 
11
11
  def normalize_tool_name(name: str) -> str:
@@ -22,8 +22,8 @@ from klaude_code.llm.openrouter.input import convert_history_to_input, is_claude
22
22
  from klaude_code.llm.openrouter.reasoning import ReasoningStreamHandler
23
23
  from klaude_code.llm.registry import register
24
24
  from klaude_code.llm.usage import MetadataTracker
25
+ from klaude_code.log import DebugType, is_debug_enabled, log_debug
25
26
  from klaude_code.protocol import llm_param, message
26
- from klaude_code.trace import DebugType, is_debug_enabled, log_debug
27
27
 
28
28
 
29
29
  def build_payload(
@@ -1,8 +1,8 @@
1
1
  from pydantic import BaseModel
2
2
 
3
3
  from klaude_code.llm.openai_compatible.stream import ReasoningDeltaResult, ReasoningHandlerABC
4
+ from klaude_code.log import log
4
5
  from klaude_code.protocol import message
5
- from klaude_code.trace import log
6
6
 
7
7
 
8
8
  class ReasoningDetail(BaseModel):
@@ -14,8 +14,8 @@ from klaude_code.llm.input_common import apply_config_defaults
14
14
  from klaude_code.llm.registry import register
15
15
  from klaude_code.llm.responses.input import convert_history_to_input, convert_tool_schema
16
16
  from klaude_code.llm.usage import MetadataTracker, error_stream_items
17
+ from klaude_code.log import DebugType, log_debug
17
18
  from klaude_code.protocol import llm_param, message, model
18
- from klaude_code.trace import DebugType, log_debug
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from openai import AsyncStream
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from klaude_code.protocol.events.base import Event, ResponseEvent
4
+ from klaude_code.protocol.events.chat import DeveloperMessageEvent, TodoChangeEvent, UserMessageEvent
5
+ from klaude_code.protocol.events.lifecycle import TaskFinishEvent, TaskStartEvent, TurnEndEvent, TurnStartEvent
6
+ from klaude_code.protocol.events.metadata import TaskMetadataEvent, UsageEvent
7
+ from klaude_code.protocol.events.streaming import (
8
+ AssistantImageDeltaEvent,
9
+ AssistantTextDeltaEvent,
10
+ AssistantTextEndEvent,
11
+ AssistantTextStartEvent,
12
+ ResponseCompleteEvent,
13
+ ThinkingDeltaEvent,
14
+ ThinkingEndEvent,
15
+ ThinkingStartEvent,
16
+ ToolCallStartEvent,
17
+ )
18
+ from klaude_code.protocol.events.system import (
19
+ EndEvent,
20
+ ErrorEvent,
21
+ InterruptEvent,
22
+ ReplayEventUnion,
23
+ ReplayHistoryEvent,
24
+ WelcomeEvent,
25
+ )
26
+ from klaude_code.protocol.events.tools import ToolCallEvent, ToolResultEvent
27
+
28
+ __all__ = [
29
+ "AssistantImageDeltaEvent",
30
+ "AssistantTextDeltaEvent",
31
+ "AssistantTextEndEvent",
32
+ "AssistantTextStartEvent",
33
+ "DeveloperMessageEvent",
34
+ "EndEvent",
35
+ "ErrorEvent",
36
+ "Event",
37
+ "InterruptEvent",
38
+ "ReplayEventUnion",
39
+ "ReplayHistoryEvent",
40
+ "ResponseCompleteEvent",
41
+ "ResponseEvent",
42
+ "TaskFinishEvent",
43
+ "TaskMetadataEvent",
44
+ "TaskStartEvent",
45
+ "ThinkingDeltaEvent",
46
+ "ThinkingEndEvent",
47
+ "ThinkingStartEvent",
48
+ "TodoChangeEvent",
49
+ "ToolCallEvent",
50
+ "ToolCallStartEvent",
51
+ "ToolResultEvent",
52
+ "TurnEndEvent",
53
+ "TurnStartEvent",
54
+ "UsageEvent",
55
+ "UserMessageEvent",
56
+ "WelcomeEvent",
57
+ ]
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class Event(BaseModel):
9
+ """Base event."""
10
+
11
+ session_id: str
12
+ timestamp: float = Field(default_factory=time.time)
13
+
14
+
15
+ class ResponseEvent(Event):
16
+ """Event associated with a single model response."""
17
+
18
+ response_id: str | None = None
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from klaude_code.protocol import message, model
4
+
5
+ from .base import Event
6
+
7
+
8
+ class UserMessageEvent(Event):
9
+ content: str
10
+ images: list[message.ImageURLPart] | None = None
11
+
12
+
13
+ class DeveloperMessageEvent(Event):
14
+ """DeveloperMessages are reminders in user messages or tool results."""
15
+
16
+ item: message.DeveloperMessage
17
+
18
+
19
+ class TodoChangeEvent(Event):
20
+ todos: list[model.TodoItem]