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.
- klaude_code/cli/main.py +9 -4
- klaude_code/cli/runtime.py +42 -43
- klaude_code/command/__init__.py +7 -5
- klaude_code/command/clear_cmd.py +6 -29
- klaude_code/command/command_abc.py +44 -8
- klaude_code/command/diff_cmd.py +33 -27
- klaude_code/command/export_cmd.py +18 -26
- klaude_code/command/help_cmd.py +10 -8
- klaude_code/command/model_cmd.py +11 -40
- klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
- klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
- klaude_code/command/prompt-init.md +2 -5
- klaude_code/command/prompt_command.py +6 -6
- klaude_code/command/refresh_cmd.py +4 -5
- klaude_code/command/registry.py +16 -19
- klaude_code/command/terminal_setup_cmd.py +12 -11
- klaude_code/config/__init__.py +4 -0
- klaude_code/config/config.py +25 -26
- klaude_code/config/list_model.py +8 -3
- klaude_code/config/select_model.py +1 -1
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/__init__.py +0 -3
- klaude_code/core/agent.py +25 -50
- klaude_code/core/executor.py +268 -101
- klaude_code/core/prompt.py +12 -12
- klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
- klaude_code/core/reminders.py +76 -95
- klaude_code/core/task.py +21 -14
- klaude_code/core/tool/__init__.py +45 -11
- klaude_code/core/tool/file/apply_patch.py +5 -1
- klaude_code/core/tool/file/apply_patch_tool.py +11 -13
- klaude_code/core/tool/file/edit_tool.py +27 -23
- klaude_code/core/tool/file/multi_edit_tool.py +15 -17
- klaude_code/core/tool/file/read_tool.py +41 -36
- klaude_code/core/tool/file/write_tool.py +13 -15
- klaude_code/core/tool/memory/memory_tool.py +85 -68
- klaude_code/core/tool/memory/skill_tool.py +10 -12
- klaude_code/core/tool/shell/bash_tool.py +24 -22
- klaude_code/core/tool/shell/command_safety.py +12 -1
- klaude_code/core/tool/sub_agent_tool.py +11 -12
- klaude_code/core/tool/todo/todo_write_tool.py +21 -28
- klaude_code/core/tool/todo/update_plan_tool.py +14 -24
- klaude_code/core/tool/tool_abc.py +3 -4
- klaude_code/core/tool/tool_context.py +7 -7
- klaude_code/core/tool/tool_registry.py +30 -47
- klaude_code/core/tool/tool_runner.py +35 -43
- klaude_code/core/tool/truncation.py +14 -20
- klaude_code/core/tool/web/mermaid_tool.py +12 -14
- klaude_code/core/tool/web/web_fetch_tool.py +15 -17
- klaude_code/core/turn.py +19 -7
- klaude_code/llm/__init__.py +3 -4
- klaude_code/llm/anthropic/client.py +30 -46
- klaude_code/llm/anthropic/input.py +4 -11
- klaude_code/llm/client.py +29 -8
- klaude_code/llm/input_common.py +66 -36
- klaude_code/llm/openai_compatible/client.py +42 -84
- klaude_code/llm/openai_compatible/input.py +11 -16
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
- klaude_code/llm/openrouter/client.py +40 -289
- klaude_code/llm/openrouter/input.py +13 -35
- klaude_code/llm/openrouter/reasoning_handler.py +209 -0
- klaude_code/llm/registry.py +5 -75
- klaude_code/llm/responses/client.py +34 -55
- klaude_code/llm/responses/input.py +24 -26
- klaude_code/llm/usage.py +109 -0
- klaude_code/protocol/__init__.py +4 -0
- klaude_code/protocol/events.py +3 -2
- klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
- klaude_code/protocol/model.py +49 -4
- klaude_code/protocol/op.py +18 -16
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/{core → protocol}/sub_agent.py +7 -0
- klaude_code/session/export.py +150 -70
- klaude_code/session/session.py +28 -14
- klaude_code/session/templates/export_session.html +180 -42
- klaude_code/trace/__init__.py +2 -2
- klaude_code/trace/log.py +11 -5
- klaude_code/ui/__init__.py +91 -8
- klaude_code/ui/core/__init__.py +1 -0
- klaude_code/ui/core/display.py +103 -0
- klaude_code/ui/core/input.py +71 -0
- klaude_code/ui/modes/__init__.py +1 -0
- klaude_code/ui/modes/debug/__init__.py +1 -0
- klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
- klaude_code/ui/modes/exec/__init__.py +1 -0
- klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
- klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
- klaude_code/ui/modes/repl/clipboard.py +152 -0
- klaude_code/ui/modes/repl/completers.py +429 -0
- klaude_code/ui/modes/repl/display.py +60 -0
- klaude_code/ui/modes/repl/event_handler.py +375 -0
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
- klaude_code/ui/modes/repl/key_bindings.py +170 -0
- klaude_code/ui/{repl → modes/repl}/renderer.py +109 -132
- klaude_code/ui/renderers/assistant.py +21 -0
- klaude_code/ui/renderers/common.py +0 -16
- klaude_code/ui/renderers/developer.py +18 -18
- klaude_code/ui/renderers/diffs.py +36 -14
- klaude_code/ui/renderers/errors.py +1 -1
- klaude_code/ui/renderers/metadata.py +50 -27
- klaude_code/ui/renderers/sub_agent.py +43 -9
- klaude_code/ui/renderers/thinking.py +33 -1
- klaude_code/ui/renderers/tools.py +212 -20
- klaude_code/ui/renderers/user_input.py +19 -23
- klaude_code/ui/rich/__init__.py +1 -0
- klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
- klaude_code/ui/{renderers → rich}/status.py +29 -18
- klaude_code/ui/{base → rich}/theme.py +8 -2
- klaude_code/ui/terminal/__init__.py +1 -0
- klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
- klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
- klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
- klaude_code/ui/utils/__init__.py +1 -0
- klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
- klaude_code-1.2.3.dist-info/RECORD +161 -0
- klaude_code/core/clipboard_manifest.py +0 -124
- klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
- klaude_code/ui/base/__init__.py +0 -1
- klaude_code/ui/base/display_abc.py +0 -36
- klaude_code/ui/base/input_abc.py +0 -20
- klaude_code/ui/repl/display.py +0 -36
- klaude_code/ui/repl/event_handler.py +0 -247
- klaude_code/ui/repl/input.py +0 -773
- klaude_code/ui/rich_ext/__init__.py +0 -1
- klaude_code-1.2.1.dist-info/RECORD +0 -151
- /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
- /klaude_code/ui/{base → core}/stage_manager.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
- /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
- /klaude_code/ui/{base → utils}/debouncer.py +0 -0
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
- {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
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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": {
|
|
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
|
|
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
|
|
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.
|
|
9
|
-
from klaude_code.protocol.
|
|
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] =
|
|
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.
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
10
|
-
|
|
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)
|