klaude-code 1.2.1__py3-none-any.whl → 1.2.3__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 (140) hide show
  1. klaude_code/cli/main.py +9 -4
  2. klaude_code/cli/runtime.py +42 -43
  3. klaude_code/command/__init__.py +7 -5
  4. klaude_code/command/clear_cmd.py +6 -29
  5. klaude_code/command/command_abc.py +44 -8
  6. klaude_code/command/diff_cmd.py +33 -27
  7. klaude_code/command/export_cmd.py +18 -26
  8. klaude_code/command/help_cmd.py +10 -8
  9. klaude_code/command/model_cmd.py +11 -40
  10. klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
  11. klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
  12. klaude_code/command/prompt-init.md +2 -5
  13. klaude_code/command/prompt_command.py +6 -6
  14. klaude_code/command/refresh_cmd.py +4 -5
  15. klaude_code/command/registry.py +16 -19
  16. klaude_code/command/terminal_setup_cmd.py +12 -11
  17. klaude_code/config/__init__.py +4 -0
  18. klaude_code/config/config.py +25 -26
  19. klaude_code/config/list_model.py +8 -3
  20. klaude_code/config/select_model.py +1 -1
  21. klaude_code/const/__init__.py +1 -1
  22. klaude_code/core/__init__.py +0 -3
  23. klaude_code/core/agent.py +25 -50
  24. klaude_code/core/executor.py +268 -101
  25. klaude_code/core/prompt.py +12 -12
  26. klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
  27. klaude_code/core/reminders.py +76 -95
  28. klaude_code/core/task.py +21 -14
  29. klaude_code/core/tool/__init__.py +45 -11
  30. klaude_code/core/tool/file/apply_patch.py +5 -1
  31. klaude_code/core/tool/file/apply_patch_tool.py +11 -13
  32. klaude_code/core/tool/file/edit_tool.py +27 -23
  33. klaude_code/core/tool/file/multi_edit_tool.py +15 -17
  34. klaude_code/core/tool/file/read_tool.py +41 -36
  35. klaude_code/core/tool/file/write_tool.py +13 -15
  36. klaude_code/core/tool/memory/memory_tool.py +85 -68
  37. klaude_code/core/tool/memory/skill_tool.py +10 -12
  38. klaude_code/core/tool/shell/bash_tool.py +24 -22
  39. klaude_code/core/tool/shell/command_safety.py +12 -1
  40. klaude_code/core/tool/sub_agent_tool.py +11 -12
  41. klaude_code/core/tool/todo/todo_write_tool.py +21 -28
  42. klaude_code/core/tool/todo/update_plan_tool.py +14 -24
  43. klaude_code/core/tool/tool_abc.py +3 -4
  44. klaude_code/core/tool/tool_context.py +7 -7
  45. klaude_code/core/tool/tool_registry.py +30 -47
  46. klaude_code/core/tool/tool_runner.py +35 -43
  47. klaude_code/core/tool/truncation.py +14 -20
  48. klaude_code/core/tool/web/mermaid_tool.py +12 -14
  49. klaude_code/core/tool/web/web_fetch_tool.py +15 -17
  50. klaude_code/core/turn.py +19 -7
  51. klaude_code/llm/__init__.py +3 -4
  52. klaude_code/llm/anthropic/client.py +30 -46
  53. klaude_code/llm/anthropic/input.py +4 -11
  54. klaude_code/llm/client.py +29 -8
  55. klaude_code/llm/input_common.py +66 -36
  56. klaude_code/llm/openai_compatible/client.py +42 -84
  57. klaude_code/llm/openai_compatible/input.py +11 -16
  58. klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
  59. klaude_code/llm/openrouter/client.py +40 -289
  60. klaude_code/llm/openrouter/input.py +13 -35
  61. klaude_code/llm/openrouter/reasoning_handler.py +209 -0
  62. klaude_code/llm/registry.py +5 -75
  63. klaude_code/llm/responses/client.py +34 -55
  64. klaude_code/llm/responses/input.py +24 -26
  65. klaude_code/llm/usage.py +109 -0
  66. klaude_code/protocol/__init__.py +4 -0
  67. klaude_code/protocol/events.py +3 -2
  68. klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
  69. klaude_code/protocol/model.py +49 -4
  70. klaude_code/protocol/op.py +18 -16
  71. klaude_code/protocol/op_handler.py +28 -0
  72. klaude_code/{core → protocol}/sub_agent.py +7 -0
  73. klaude_code/session/export.py +150 -70
  74. klaude_code/session/session.py +28 -14
  75. klaude_code/session/templates/export_session.html +180 -42
  76. klaude_code/trace/__init__.py +2 -2
  77. klaude_code/trace/log.py +11 -5
  78. klaude_code/ui/__init__.py +91 -8
  79. klaude_code/ui/core/__init__.py +1 -0
  80. klaude_code/ui/core/display.py +103 -0
  81. klaude_code/ui/core/input.py +71 -0
  82. klaude_code/ui/modes/__init__.py +1 -0
  83. klaude_code/ui/modes/debug/__init__.py +1 -0
  84. klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
  85. klaude_code/ui/modes/exec/__init__.py +1 -0
  86. klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
  87. klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
  88. klaude_code/ui/modes/repl/clipboard.py +152 -0
  89. klaude_code/ui/modes/repl/completers.py +429 -0
  90. klaude_code/ui/modes/repl/display.py +60 -0
  91. klaude_code/ui/modes/repl/event_handler.py +375 -0
  92. klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
  93. klaude_code/ui/modes/repl/key_bindings.py +170 -0
  94. klaude_code/ui/{repl → modes/repl}/renderer.py +109 -132
  95. klaude_code/ui/renderers/assistant.py +21 -0
  96. klaude_code/ui/renderers/common.py +0 -16
  97. klaude_code/ui/renderers/developer.py +18 -18
  98. klaude_code/ui/renderers/diffs.py +36 -14
  99. klaude_code/ui/renderers/errors.py +1 -1
  100. klaude_code/ui/renderers/metadata.py +50 -27
  101. klaude_code/ui/renderers/sub_agent.py +43 -9
  102. klaude_code/ui/renderers/thinking.py +33 -1
  103. klaude_code/ui/renderers/tools.py +212 -20
  104. klaude_code/ui/renderers/user_input.py +19 -23
  105. klaude_code/ui/rich/__init__.py +1 -0
  106. klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
  107. klaude_code/ui/{renderers → rich}/status.py +29 -18
  108. klaude_code/ui/{base → rich}/theme.py +8 -2
  109. klaude_code/ui/terminal/__init__.py +1 -0
  110. klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
  111. klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
  112. klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
  113. klaude_code/ui/utils/__init__.py +1 -0
  114. klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
  115. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
  116. klaude_code-1.2.3.dist-info/RECORD +161 -0
  117. klaude_code/core/clipboard_manifest.py +0 -124
  118. klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
  119. klaude_code/ui/base/__init__.py +0 -1
  120. klaude_code/ui/base/display_abc.py +0 -36
  121. klaude_code/ui/base/input_abc.py +0 -20
  122. klaude_code/ui/repl/display.py +0 -36
  123. klaude_code/ui/repl/event_handler.py +0 -247
  124. klaude_code/ui/repl/input.py +0 -773
  125. klaude_code/ui/rich_ext/__init__.py +0 -1
  126. klaude_code-1.2.1.dist-info/RECORD +0 -151
  127. /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
  128. /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
  129. /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
  130. /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
  131. /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
  132. /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
  133. /klaude_code/ui/{base → core}/stage_manager.py +0 -0
  134. /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
  135. /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
  136. /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
  137. /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
  138. /klaude_code/ui/{base → utils}/debouncer.py +0 -0
  139. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
  140. {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/entry_points.txt +0 -0
@@ -5,20 +5,10 @@ from pydantic import BaseModel
5
5
  from klaude_code.core.tool.tool_abc import ToolABC, load_desc
6
6
  from klaude_code.core.tool.tool_context import get_current_todo_context
7
7
  from klaude_code.core.tool.tool_registry import register
8
- from klaude_code.protocol.llm_parameter import ToolSchema
9
- from klaude_code.protocol.model import (
10
- TodoItem,
11
- TodoUIExtra,
12
- ToolResultItem,
13
- ToolResultUIExtra,
14
- ToolResultUIExtraType,
15
- ToolSideEffect,
16
- todo_list_str,
17
- )
18
- from klaude_code.protocol.tools import TODO_WRITE
19
-
20
-
21
- def get_new_completed_todos(old_todos: list[TodoItem], new_todos: list[TodoItem]) -> list[str]:
8
+ from klaude_code.protocol import llm_param, model, tools
9
+
10
+
11
+ def get_new_completed_todos(old_todos: list[model.TodoItem], new_todos: list[model.TodoItem]) -> list[str]:
22
12
  """
23
13
  Compare old and new todo lists to find newly completed todos.
24
14
 
@@ -49,15 +39,15 @@ def get_new_completed_todos(old_todos: list[TodoItem], new_todos: list[TodoItem]
49
39
 
50
40
 
51
41
  class TodoWriteArguments(BaseModel):
52
- todos: list[TodoItem]
42
+ todos: list[model.TodoItem]
53
43
 
54
44
 
55
- @register(TODO_WRITE)
45
+ @register(tools.TODO_WRITE)
56
46
  class TodoWriteTool(ToolABC):
57
47
  @classmethod
58
- def schema(cls) -> ToolSchema:
59
- return ToolSchema(
60
- name=TODO_WRITE,
48
+ def schema(cls) -> llm_param.ToolSchema:
49
+ return llm_param.ToolSchema(
50
+ name=tools.TODO_WRITE,
61
51
  type="function",
62
52
  description=load_desc(Path(__file__).parent / "todo_write_tool.md"),
63
53
  parameters={
@@ -69,7 +59,10 @@ class TodoWriteTool(ToolABC):
69
59
  "type": "object",
70
60
  "properties": {
71
61
  "content": {"type": "string", "minLength": 1},
72
- "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
62
+ "status": {
63
+ "type": "string",
64
+ "enum": ["pending", "in_progress", "completed"],
65
+ },
73
66
  "activeForm": {"type": "string", "minLength": 1},
74
67
  },
75
68
  "required": ["content", "status", "activeForm"],
@@ -84,11 +77,11 @@ class TodoWriteTool(ToolABC):
84
77
  )
85
78
 
86
79
  @classmethod
87
- async def call(cls, arguments: str) -> ToolResultItem:
80
+ async def call(cls, arguments: str) -> model.ToolResultItem:
88
81
  try:
89
82
  args = TodoWriteArguments.model_validate_json(arguments)
90
83
  except ValueError as e:
91
- return ToolResultItem(
84
+ return model.ToolResultItem(
92
85
  status="error",
93
86
  output=f"Invalid arguments: {e}",
94
87
  )
@@ -96,7 +89,7 @@ class TodoWriteTool(ToolABC):
96
89
  # Get current todo context to store todos
97
90
  todo_context = get_current_todo_context()
98
91
  if todo_context is None:
99
- return ToolResultItem(
92
+ return model.ToolResultItem(
100
93
  status="error",
101
94
  output="No active session found",
102
95
  )
@@ -110,19 +103,19 @@ class TodoWriteTool(ToolABC):
110
103
  # Store todos via todo context
111
104
  todo_context.set_todos(args.todos)
112
105
 
113
- ui_extra = TodoUIExtra(todos=args.todos, new_completed=new_completed)
106
+ ui_extra = model.TodoUIExtra(todos=args.todos, new_completed=new_completed)
114
107
 
115
108
  response = f"""Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
116
109
 
117
110
  <system-reminder>
118
111
  Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:
119
112
 
120
- {todo_list_str(args.todos)}. Continue on with the tasks at hand if applicable.
113
+ {model.todo_list_str(args.todos)}. Continue on with the tasks at hand if applicable.
121
114
  </system-reminder>"""
122
115
 
123
- return ToolResultItem(
116
+ return model.ToolResultItem(
124
117
  status="success",
125
118
  output=response,
126
- ui_extra=ToolResultUIExtra(type=ToolResultUIExtraType.TODO_LIST, todo_list=ui_extra),
127
- side_effects=[ToolSideEffect.TODO_CHANGE],
119
+ ui_extra=model.ToolResultUIExtra(type=model.ToolResultUIExtraType.TODO_LIST, todo_list=ui_extra),
120
+ side_effects=[model.ToolSideEffect.TODO_CHANGE],
128
121
  )
@@ -9,24 +9,14 @@ from pydantic import BaseModel, field_validator
9
9
  from klaude_code.core.tool.tool_abc import ToolABC, load_desc
10
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
- from klaude_code.protocol.llm_parameter import ToolSchema
13
- from klaude_code.protocol.model import (
14
- TodoItem,
15
- TodoStatusType,
16
- TodoUIExtra,
17
- ToolResultItem,
18
- ToolResultUIExtra,
19
- ToolResultUIExtraType,
20
- ToolSideEffect,
21
- )
22
- from klaude_code.protocol.tools import UPDATE_PLAN
12
+ from klaude_code.protocol import llm_param, model, tools
23
13
 
24
14
  from .todo_write_tool import get_new_completed_todos
25
15
 
26
16
 
27
17
  class PlanItemArguments(BaseModel):
28
18
  step: str
29
- status: TodoStatusType
19
+ status: model.TodoStatusType
30
20
 
31
21
  @field_validator("step")
32
22
  @classmethod
@@ -51,12 +41,12 @@ class UpdatePlanArguments(BaseModel):
51
41
  return value
52
42
 
53
43
 
54
- @register(UPDATE_PLAN)
44
+ @register(tools.UPDATE_PLAN)
55
45
  class UpdatePlanTool(ToolABC):
56
46
  @classmethod
57
- def schema(cls) -> ToolSchema:
58
- return ToolSchema(
59
- name=UPDATE_PLAN,
47
+ def schema(cls) -> llm_param.ToolSchema:
48
+ return llm_param.ToolSchema(
49
+ name=tools.UPDATE_PLAN,
60
50
  type="function",
61
51
  description=load_desc(Path(__file__).parent / "update_plan_tool.md"),
62
52
  parameters={
@@ -89,26 +79,26 @@ class UpdatePlanTool(ToolABC):
89
79
  )
90
80
 
91
81
  @classmethod
92
- async def call(cls, arguments: str) -> ToolResultItem:
82
+ async def call(cls, arguments: str) -> model.ToolResultItem:
93
83
  try:
94
84
  args = UpdatePlanArguments.model_validate_json(arguments)
95
85
  except ValueError as exc:
96
- return ToolResultItem(status="error", output=f"Invalid arguments: {exc}")
86
+ return model.ToolResultItem(status="error", output=f"Invalid arguments: {exc}")
97
87
 
98
88
  todo_context = get_current_todo_context()
99
89
  if todo_context is None:
100
- return ToolResultItem(status="error", output="No active session found")
90
+ return model.ToolResultItem(status="error", output="No active session found")
101
91
 
102
- new_todos = [TodoItem(content=item.step, status=item.status) for item in args.plan]
92
+ new_todos = [model.TodoItem(content=item.step, status=item.status) for item in args.plan]
103
93
  old_todos = todo_context.get_todos()
104
94
  new_completed = get_new_completed_todos(old_todos, new_todos)
105
95
  todo_context.set_todos(new_todos)
106
96
 
107
- ui_extra = TodoUIExtra(todos=new_todos, new_completed=new_completed)
97
+ ui_extra = model.TodoUIExtra(todos=new_todos, new_completed=new_completed)
108
98
 
109
- return ToolResultItem(
99
+ return model.ToolResultItem(
110
100
  status="success",
111
101
  output="Plan updated",
112
- ui_extra=ToolResultUIExtra(type=ToolResultUIExtraType.TODO_LIST, todo_list=ui_extra),
113
- side_effects=[ToolSideEffect.TODO_CHANGE],
102
+ ui_extra=model.ToolResultUIExtra(type=model.ToolResultUIExtraType.TODO_LIST, todo_list=ui_extra),
103
+ side_effects=[model.ToolSideEffect.TODO_CHANGE],
114
104
  )
@@ -2,8 +2,7 @@ import string
2
2
  from abc import ABC, abstractmethod
3
3
  from pathlib import Path
4
4
 
5
- from klaude_code.protocol.llm_parameter import ToolSchema
6
- from klaude_code.protocol.model import ToolResultItem
5
+ from klaude_code.protocol import llm_param, model
7
6
 
8
7
 
9
8
  def load_desc(path: Path, substitutions: dict[str, str] | None = None) -> str:
@@ -17,10 +16,10 @@ def load_desc(path: Path, substitutions: dict[str, str] | None = None) -> str:
17
16
  class ToolABC(ABC):
18
17
  @classmethod
19
18
  @abstractmethod
20
- def schema(cls) -> ToolSchema:
19
+ def schema(cls) -> llm_param.ToolSchema:
21
20
  raise NotImplementedError
22
21
 
23
22
  @classmethod
24
23
  @abstractmethod
25
- async def call(cls, arguments: str) -> ToolResultItem:
24
+ async def call(cls, arguments: str) -> model.ToolResultItem:
26
25
  raise NotImplementedError
@@ -5,8 +5,8 @@ from contextlib import contextmanager
5
5
  from contextvars import ContextVar, Token
6
6
  from dataclasses import dataclass
7
7
 
8
- from klaude_code.core.sub_agent import SubAgentResult
9
- from klaude_code.protocol.model import SubAgentState, TodoItem
8
+ from klaude_code.protocol import model
9
+ from klaude_code.protocol.sub_agent import SubAgentResult
10
10
  from klaude_code.session.session import Session
11
11
 
12
12
 
@@ -18,8 +18,8 @@ class TodoContext:
18
18
  a new list; they cannot access the full Session object.
19
19
  """
20
20
 
21
- get_todos: Callable[[], list[TodoItem]]
22
- set_todos: Callable[[list[TodoItem]], None]
21
+ get_todos: Callable[[], list[model.TodoItem]]
22
+ set_todos: Callable[[list[model.TodoItem]], None]
23
23
 
24
24
 
25
25
  @dataclass
@@ -100,7 +100,7 @@ def get_current_todo_context() -> TodoContext | None:
100
100
 
101
101
 
102
102
  # Holds a handle to run a nested subtask (sub-agent) from within a tool call.
103
- # The callable takes a SubAgentState and returns a SubAgentResult.
104
- current_run_subtask_callback: ContextVar[Callable[[SubAgentState], Awaitable[SubAgentResult]] | None] = ContextVar(
105
- "current_run_subtask_callback", default=None
103
+ # The callable takes a model.SubAgentState and returns a SubAgentResult.
104
+ current_run_subtask_callback: ContextVar[Callable[[model.SubAgentState], Awaitable[SubAgentResult]] | None] = (
105
+ ContextVar("current_run_subtask_callback", default=None)
106
106
  )
@@ -1,10 +1,9 @@
1
1
  from typing import Callable, TypeVar
2
2
 
3
- from klaude_code.core.sub_agent import get_sub_agent_profile, iter_sub_agent_profiles, sub_agent_tool_names
4
3
  from klaude_code.core.tool.sub_agent_tool import SubAgentTool
5
4
  from klaude_code.core.tool.tool_abc import ToolABC
6
- from klaude_code.protocol import tools
7
- from klaude_code.protocol.llm_parameter import ToolSchema
5
+ from klaude_code.protocol import llm_param, tools
6
+ from klaude_code.protocol.sub_agent import get_sub_agent_profile, iter_sub_agent_profiles, sub_agent_tool_names
8
7
 
9
8
  _REGISTRY: dict[str, type[ToolABC]] = {}
10
9
 
@@ -33,8 +32,8 @@ def list_tools() -> list[str]:
33
32
  return list(_REGISTRY.keys())
34
33
 
35
34
 
36
- def get_tool_schemas(tool_names: list[str]) -> list[ToolSchema]:
37
- schemas: list[ToolSchema] = []
35
+ def get_tool_schemas(tool_names: list[str]) -> list[llm_param.ToolSchema]:
36
+ schemas: list[llm_param.ToolSchema] = []
38
37
  for tool_name in tool_names:
39
38
  if tool_name not in _REGISTRY:
40
39
  raise ValueError(f"Unknown Tool: {tool_name}")
@@ -47,47 +46,31 @@ def get_registry() -> dict[str, type[ToolABC]]:
47
46
  return _REGISTRY
48
47
 
49
48
 
50
- def get_vanilla_tools() -> list[ToolSchema]:
51
- base_tool_names = [
52
- tools.BASH,
53
- tools.EDIT,
54
- tools.WRITE,
55
- tools.READ,
56
- ]
57
- return get_tool_schemas(base_tool_names)
58
-
59
-
60
- def get_main_agent_tools(model_name: str) -> list[ToolSchema]:
61
- def _base_main_tools(name: str) -> list[str]:
62
- if "gpt-5" in name:
63
- return [
64
- tools.BASH,
65
- tools.READ,
66
- tools.APPLY_PATCH,
67
- tools.UPDATE_PLAN,
68
- ]
69
- return [
70
- tools.BASH,
71
- tools.READ,
72
- tools.EDIT,
73
- tools.WRITE,
74
- tools.TODO_WRITE,
75
- ]
76
-
77
- tool_names = _base_main_tools(model_name)
78
- tool_names.extend(sub_agent_tool_names(enabled_only=True, model_name=model_name))
79
- tool_names.extend(
80
- [
81
- tools.SKILL,
82
- tools.MERMAID,
83
- tools.MEMORY,
84
- ]
85
- )
86
- return get_tool_schemas(tool_names)
49
+ def load_agent_tools(
50
+ model_name: str, sub_agent_type: tools.SubAgentType | None = None, *, vanilla: bool = False
51
+ ) -> list[llm_param.ToolSchema]:
52
+ """Get tools for an agent based on model and agent type.
53
+
54
+ Args:
55
+ model_name: The model name.
56
+ sub_agent_type: If None, returns main agent tools. Otherwise returns sub-agent tools.
57
+ vanilla: If True, returns minimal vanilla tools (ignores sub_agent_type).
58
+ """
59
+ if vanilla:
60
+ return get_tool_schemas([tools.BASH, tools.EDIT, tools.WRITE, tools.READ])
87
61
 
62
+ if sub_agent_type is not None:
63
+ profile = get_sub_agent_profile(sub_agent_type)
64
+ if not profile.enabled_for_model(model_name):
65
+ return []
66
+ return get_tool_schemas(list(profile.tool_set))
88
67
 
89
- def get_sub_agent_tools(model_name: str, sub_agent_type: tools.SubAgentType) -> list[ToolSchema]:
90
- profile = get_sub_agent_profile(sub_agent_type)
91
- if not profile.enabled_for_model(model_name):
92
- return []
93
- return get_tool_schemas(list(profile.tool_set))
68
+ # Main agent tools
69
+ if "gpt-5" in model_name:
70
+ tool_names = [tools.BASH, tools.READ, tools.APPLY_PATCH, tools.UPDATE_PLAN]
71
+ else:
72
+ tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.TODO_WRITE]
73
+
74
+ tool_names.extend(sub_agent_tool_names(enabled_only=True, model_name=model_name))
75
+ tool_names.extend([tools.SKILL, tools.MERMAID, tools.MEMORY])
76
+ return get_tool_schemas(tool_names)
@@ -2,22 +2,14 @@ import asyncio
2
2
  from collections.abc import AsyncGenerator, Callable, Iterable, Sequence
3
3
  from dataclasses import dataclass
4
4
 
5
- from klaude_code.const import CANCEL_OUTPUT
6
- from klaude_code.core.sub_agent import is_sub_agent_tool
5
+ from klaude_code import const
7
6
  from klaude_code.core.tool.tool_abc import ToolABC
8
7
  from klaude_code.core.tool.truncation import truncate_tool_output
9
8
  from klaude_code.protocol import model
10
- from klaude_code.protocol.model import (
11
- ToolCallItem,
12
- ToolResultItem,
13
- ToolResultUIExtra,
14
- ToolResultUIExtraType,
15
- ToolSideEffect,
16
- TruncationUIExtra,
17
- )
9
+ from klaude_code.protocol.sub_agent import is_sub_agent_tool
18
10
 
19
11
 
20
- async def run_tool(tool_call: ToolCallItem, registry: dict[str, type[ToolABC]]) -> ToolResultItem:
12
+ async def run_tool(tool_call: model.ToolCallItem, registry: dict[str, type[ToolABC]]) -> model.ToolResultItem:
21
13
  """Execute a tool call and return the result.
22
14
 
23
15
  Args:
@@ -28,7 +20,7 @@ async def run_tool(tool_call: ToolCallItem, registry: dict[str, type[ToolABC]])
28
20
  The result of the tool execution.
29
21
  """
30
22
  if tool_call.name not in registry:
31
- return ToolResultItem(
23
+ return model.ToolResultItem(
32
24
  call_id=tool_call.call_id,
33
25
  output=f"Tool {tool_call.name} not exists",
34
26
  status="error",
@@ -42,9 +34,9 @@ async def run_tool(tool_call: ToolCallItem, registry: dict[str, type[ToolABC]])
42
34
  truncation_result = truncate_tool_output(tool_result.output, tool_call)
43
35
  tool_result.output = truncation_result.output
44
36
  if truncation_result.was_truncated and truncation_result.saved_file_path:
45
- tool_result.ui_extra = ToolResultUIExtra(
46
- type=ToolResultUIExtraType.TRUNCATION,
47
- truncation=TruncationUIExtra(
37
+ tool_result.ui_extra = model.ToolResultUIExtra(
38
+ type=model.ToolResultUIExtraType.TRUNCATION,
39
+ truncation=model.TruncationUIExtra(
48
40
  saved_file_path=truncation_result.saved_file_path,
49
41
  original_length=truncation_result.original_length,
50
42
  truncated_length=truncation_result.truncated_length,
@@ -55,7 +47,7 @@ async def run_tool(tool_call: ToolCallItem, registry: dict[str, type[ToolABC]])
55
47
  # Propagate cooperative cancellation so outer layers can handle interrupts correctly.
56
48
  raise
57
49
  except Exception as e:
58
- return ToolResultItem(
50
+ return model.ToolResultItem(
59
51
  call_id=tool_call.call_id,
60
52
  output=f"Tool {tool_call.name} execution error: {e.__class__.__name__} {e}",
61
53
  status="error",
@@ -67,15 +59,15 @@ async def run_tool(tool_call: ToolCallItem, registry: dict[str, type[ToolABC]])
67
59
  class ToolExecutionCallStarted:
68
60
  """Represents the start of a tool call execution."""
69
61
 
70
- tool_call: ToolCallItem
62
+ tool_call: model.ToolCallItem
71
63
 
72
64
 
73
65
  @dataclass
74
66
  class ToolExecutionResult:
75
67
  """Represents the completion of a tool call with its result."""
76
68
 
77
- tool_call: ToolCallItem
78
- tool_result: ToolResultItem
69
+ tool_call: model.ToolCallItem
70
+ tool_result: model.ToolResultItem
79
71
 
80
72
 
81
73
  @dataclass
@@ -107,11 +99,11 @@ class ToolExecutor:
107
99
  self._registry = registry
108
100
  self._append_history = append_history
109
101
 
110
- self._unfinished_calls: dict[str, ToolCallItem] = {}
102
+ self._unfinished_calls: dict[str, model.ToolCallItem] = {}
111
103
  self._call_event_emitted: set[str] = set()
112
104
  self._sub_agent_tasks: set[asyncio.Task[list[ToolExecutorEvent]]] = set()
113
105
 
114
- async def run_tools(self, tool_calls: list[ToolCallItem]) -> AsyncGenerator[ToolExecutorEvent, None]:
106
+ async def run_tools(self, tool_calls: list[model.ToolCallItem]) -> AsyncGenerator[ToolExecutorEvent, None]:
115
107
  """Run the given tool calls and yield execution events.
116
108
 
117
109
  Tool calls are partitioned into regular tools and sub-agent tools. Regular tools
@@ -152,11 +144,15 @@ class ToolExecutor:
152
144
  execution_tasks.append(task)
153
145
 
154
146
  for task in asyncio.as_completed(execution_tasks):
155
- try:
156
- result_events = await task
157
- except asyncio.CancelledError:
158
- # Task was cancelled by ToolExecutor.cancel(); skip synthetic events here.
159
- continue
147
+ # Do not swallow asyncio.CancelledError here:
148
+ # - If the user interrupts the main agent, the executor cancels the
149
+ # outer agent task, which should propagate cancellation up through
150
+ # tool execution so the task can terminate and emit TaskFinishEvent.
151
+ # - Sub-agent tool tasks cancelled via ToolExecutor.cancel() are
152
+ # handled by synthesizing ToolExecutionResult events; any
153
+ # CancelledError raised here should still bubble up so the
154
+ # calling agent can stop cleanly, matching pre-refactor behavior.
155
+ result_events = await task
160
156
 
161
157
  for exec_event in result_events:
162
158
  yield exec_event
@@ -183,23 +179,19 @@ class ToolExecutor:
183
179
  return events_to_yield
184
180
 
185
181
  for call_id, tool_call in list(self._unfinished_calls.items()):
186
- cancel_result = ToolResultItem(
182
+ cancel_result = model.ToolResultItem(
187
183
  call_id=tool_call.call_id,
188
- output=CANCEL_OUTPUT,
184
+ output=const.CANCEL_OUTPUT,
189
185
  status="error",
190
186
  tool_name=tool_call.name,
191
187
  ui_extra=None,
192
188
  )
193
189
 
194
190
  if call_id not in self._call_event_emitted:
195
- events_to_yield.append(
196
- ToolExecutionCallStarted(tool_call=tool_call)
197
- )
191
+ events_to_yield.append(ToolExecutionCallStarted(tool_call=tool_call))
198
192
  self._call_event_emitted.add(call_id)
199
193
 
200
- events_to_yield.append(
201
- ToolExecutionResult(tool_call=tool_call, tool_result=cancel_result)
202
- )
194
+ events_to_yield.append(ToolExecutionResult(tool_call=tool_call, tool_result=cancel_result))
203
195
 
204
196
  self._append_history([cancel_result])
205
197
  self._unfinished_calls.pop(call_id, None)
@@ -216,10 +208,10 @@ class ToolExecutor:
216
208
 
217
209
  @staticmethod
218
210
  def _partition_tool_calls(
219
- tool_calls: list[ToolCallItem],
220
- ) -> tuple[list[ToolCallItem], list[ToolCallItem]]:
221
- regular_tool_calls: list[ToolCallItem] = []
222
- sub_agent_tool_calls: list[ToolCallItem] = []
211
+ tool_calls: list[model.ToolCallItem],
212
+ ) -> tuple[list[model.ToolCallItem], list[model.ToolCallItem]]:
213
+ regular_tool_calls: list[model.ToolCallItem] = []
214
+ sub_agent_tool_calls: list[model.ToolCallItem] = []
223
215
  for tool_call in tool_calls:
224
216
  if is_sub_agent_tool(tool_call.name):
225
217
  sub_agent_tool_calls.append(tool_call)
@@ -227,11 +219,11 @@ class ToolExecutor:
227
219
  regular_tool_calls.append(tool_call)
228
220
  return regular_tool_calls, sub_agent_tool_calls
229
221
 
230
- def _build_tool_call_started(self, tool_call: ToolCallItem) -> ToolExecutionCallStarted:
222
+ def _build_tool_call_started(self, tool_call: model.ToolCallItem) -> ToolExecutionCallStarted:
231
223
  return ToolExecutionCallStarted(tool_call=tool_call)
232
224
 
233
- async def _run_single_tool_call(self, tool_call: ToolCallItem) -> list[ToolExecutorEvent]:
234
- tool_result: ToolResultItem = await run_tool(tool_call, self._registry)
225
+ async def _run_single_tool_call(self, tool_call: model.ToolCallItem) -> list[ToolExecutorEvent]:
226
+ tool_result: model.ToolResultItem = await run_tool(tool_call, self._registry)
235
227
 
236
228
  self._append_history([tool_result])
237
229
 
@@ -242,7 +234,7 @@ class ToolExecutor:
242
234
  extra_events = self._build_tool_side_effect_events(tool_result)
243
235
  return [result_event, *extra_events]
244
236
 
245
- def _build_tool_side_effect_events(self, tool_result: ToolResultItem) -> list[ToolExecutorEvent]:
237
+ def _build_tool_side_effect_events(self, tool_result: model.ToolResultItem) -> list[ToolExecutorEvent]:
246
238
  side_effects = tool_result.side_effects
247
239
  if not side_effects:
248
240
  return []
@@ -250,7 +242,7 @@ class ToolExecutor:
250
242
  side_effect_events: list[ToolExecutorEvent] = []
251
243
 
252
244
  for side_effect in side_effects:
253
- if side_effect == ToolSideEffect.TODO_CHANGE:
245
+ if side_effect == model.ToolSideEffect.TODO_CHANGE:
254
246
  todos: list[model.TodoItem] | None = None
255
247
  if tool_result.ui_extra is not None and tool_result.ui_extra.todo_list is not None:
256
248
  todos = tool_result.ui_extra.todo_list.todos
@@ -6,14 +6,8 @@ from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
  from urllib.parse import urlparse
8
8
 
9
- from klaude_code.const import (
10
- TOOL_OUTPUT_DISPLAY_HEAD,
11
- TOOL_OUTPUT_DISPLAY_TAIL,
12
- TOOL_OUTPUT_MAX_LENGTH,
13
- TOOL_OUTPUT_TRUNCATION_DIR,
14
- )
15
- from klaude_code.protocol.model import ToolCallItem
16
- from klaude_code.protocol.tools import WEB_FETCH
9
+ from klaude_code import const
10
+ from klaude_code.protocol import model, tools
17
11
 
18
12
 
19
13
  @dataclass
@@ -47,7 +41,7 @@ class TruncationStrategy(ABC):
47
41
  """Abstract base class for tool output truncation strategies."""
48
42
 
49
43
  @abstractmethod
50
- def truncate(self, output: str, tool_call: ToolCallItem | None = None) -> TruncationResult:
44
+ def truncate(self, output: str, tool_call: model.ToolCallItem | None = None) -> TruncationResult:
51
45
  """Truncate the output according to the strategy."""
52
46
  ...
53
47
 
@@ -55,10 +49,10 @@ class TruncationStrategy(ABC):
55
49
  class SimpleTruncationStrategy(TruncationStrategy):
56
50
  """Simple character-based truncation strategy."""
57
51
 
58
- def __init__(self, max_length: int = TOOL_OUTPUT_MAX_LENGTH):
52
+ def __init__(self, max_length: int = const.TOOL_OUTPUT_MAX_LENGTH):
59
53
  self.max_length = max_length
60
54
 
61
- def truncate(self, output: str, tool_call: ToolCallItem | None = None) -> TruncationResult:
55
+ def truncate(self, output: str, tool_call: model.ToolCallItem | None = None) -> TruncationResult:
62
56
  if len(output) > self.max_length:
63
57
  truncated_length = len(output) - self.max_length
64
58
  truncated_output = output[: self.max_length] + f"... (truncated {truncated_length} characters)"
@@ -76,19 +70,19 @@ class SmartTruncationStrategy(TruncationStrategy):
76
70
 
77
71
  def __init__(
78
72
  self,
79
- max_length: int = TOOL_OUTPUT_MAX_LENGTH,
80
- head_chars: int = TOOL_OUTPUT_DISPLAY_HEAD,
81
- tail_chars: int = TOOL_OUTPUT_DISPLAY_TAIL,
82
- truncation_dir: str = TOOL_OUTPUT_TRUNCATION_DIR,
73
+ max_length: int = const.TOOL_OUTPUT_MAX_LENGTH,
74
+ head_chars: int = const.TOOL_OUTPUT_DISPLAY_HEAD,
75
+ tail_chars: int = const.TOOL_OUTPUT_DISPLAY_TAIL,
76
+ truncation_dir: str = const.TOOL_OUTPUT_TRUNCATION_DIR,
83
77
  ):
84
78
  self.max_length = max_length
85
79
  self.head_chars = head_chars
86
80
  self.tail_chars = tail_chars
87
81
  self.truncation_dir = Path(truncation_dir)
88
82
 
89
- def _get_file_identifier(self, tool_call: ToolCallItem | None) -> str:
83
+ def _get_file_identifier(self, tool_call: model.ToolCallItem | None) -> str:
90
84
  """Get a file identifier based on tool call. For WebFetch, use URL; otherwise use call_id."""
91
- if tool_call and tool_call.name == WEB_FETCH:
85
+ if tool_call and tool_call.name == tools.WEB_FETCH:
92
86
  try:
93
87
  args = json.loads(tool_call.arguments)
94
88
  url = args.get("url", "")
@@ -101,7 +95,7 @@ class SmartTruncationStrategy(TruncationStrategy):
101
95
  return tool_call.call_id.replace("/", "_")
102
96
  return "unknown"
103
97
 
104
- def _save_to_file(self, output: str, tool_call: ToolCallItem | None) -> str | None:
98
+ def _save_to_file(self, output: str, tool_call: model.ToolCallItem | None) -> str | None:
105
99
  """Save full output to file. Returns file path or None on failure."""
106
100
  try:
107
101
  self.truncation_dir.mkdir(parents=True, exist_ok=True)
@@ -115,7 +109,7 @@ class SmartTruncationStrategy(TruncationStrategy):
115
109
  except (OSError, IOError):
116
110
  return None
117
111
 
118
- def truncate(self, output: str, tool_call: ToolCallItem | None = None) -> TruncationResult:
112
+ def truncate(self, output: str, tool_call: model.ToolCallItem | None = None) -> TruncationResult:
119
113
  original_length = len(output)
120
114
 
121
115
  if original_length <= self.max_length:
@@ -171,6 +165,6 @@ def set_truncation_strategy(strategy: TruncationStrategy) -> None:
171
165
  _default_strategy = strategy
172
166
 
173
167
 
174
- def truncate_tool_output(output: str, tool_call: ToolCallItem | None = None) -> TruncationResult:
168
+ def truncate_tool_output(output: str, tool_call: model.ToolCallItem | None = None) -> TruncationResult:
175
169
  """Truncate tool output using the current strategy."""
176
170
  return get_truncation_strategy().truncate(output, tool_call)