klaude-code 2.4.2__py3-none-any.whl → 2.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- klaude_code/app/runtime.py +2 -6
- klaude_code/cli/main.py +0 -1
- klaude_code/config/assets/builtin_config.yaml +7 -0
- klaude_code/const.py +7 -4
- klaude_code/core/agent.py +10 -1
- klaude_code/core/agent_profile.py +47 -35
- klaude_code/core/executor.py +6 -21
- klaude_code/core/manager/sub_agent_manager.py +17 -1
- klaude_code/core/prompts/prompt-sub-agent-web.md +4 -4
- klaude_code/core/task.py +65 -4
- klaude_code/core/tool/__init__.py +0 -5
- klaude_code/core/tool/context.py +12 -1
- klaude_code/core/tool/offload.py +311 -0
- klaude_code/core/tool/shell/bash_tool.md +1 -43
- klaude_code/core/tool/sub_agent_tool.py +1 -0
- klaude_code/core/tool/todo/todo_write_tool.md +0 -23
- klaude_code/core/tool/tool_runner.py +14 -9
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +14 -39
- klaude_code/core/turn.py +128 -139
- klaude_code/llm/anthropic/client.py +176 -82
- klaude_code/llm/bedrock/client.py +8 -12
- klaude_code/llm/claude/client.py +11 -15
- klaude_code/llm/client.py +31 -4
- klaude_code/llm/codex/client.py +7 -11
- klaude_code/llm/google/client.py +150 -69
- klaude_code/llm/openai_compatible/client.py +10 -15
- klaude_code/llm/openai_compatible/stream.py +68 -6
- klaude_code/llm/openrouter/client.py +9 -15
- klaude_code/llm/partial_message.py +35 -0
- klaude_code/llm/responses/client.py +134 -68
- klaude_code/llm/usage.py +30 -0
- klaude_code/protocol/commands.py +0 -4
- klaude_code/protocol/events/metadata.py +1 -0
- klaude_code/protocol/events/system.py +0 -4
- klaude_code/protocol/model.py +2 -15
- klaude_code/protocol/sub_agent/explore.py +0 -10
- klaude_code/protocol/sub_agent/image_gen.py +0 -7
- klaude_code/protocol/sub_agent/task.py +0 -10
- klaude_code/protocol/sub_agent/web.py +4 -12
- klaude_code/session/templates/export_session.html +4 -4
- klaude_code/skill/manager.py +2 -1
- klaude_code/tui/components/metadata.py +41 -49
- klaude_code/tui/components/rich/markdown.py +1 -3
- klaude_code/tui/components/rich/theme.py +2 -2
- klaude_code/tui/components/tools.py +0 -31
- klaude_code/tui/components/welcome.py +1 -32
- klaude_code/tui/input/prompt_toolkit.py +25 -9
- klaude_code/tui/machine.py +2 -1
- {klaude_code-2.4.2.dist-info → klaude_code-2.5.0.dist-info}/METADATA +1 -1
- {klaude_code-2.4.2.dist-info → klaude_code-2.5.0.dist-info}/RECORD +53 -53
- klaude_code/core/prompts/prompt-nano-banana.md +0 -1
- klaude_code/core/tool/truncation.py +0 -203
- {klaude_code-2.4.2.dist-info → klaude_code-2.5.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.4.2.dist-info → klaude_code-2.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import re
|
|
3
|
-
import time
|
|
4
|
-
from abc import ABC, abstractmethod
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Protocol
|
|
8
|
-
from urllib.parse import urlparse
|
|
9
|
-
|
|
10
|
-
from klaude_code.const import (
|
|
11
|
-
TOOL_OUTPUT_DISPLAY_HEAD,
|
|
12
|
-
TOOL_OUTPUT_DISPLAY_TAIL,
|
|
13
|
-
TOOL_OUTPUT_MAX_LENGTH,
|
|
14
|
-
TOOL_OUTPUT_TRUNCATION_DIR,
|
|
15
|
-
URL_FILENAME_MAX_LENGTH,
|
|
16
|
-
)
|
|
17
|
-
from klaude_code.protocol import tools
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ToolCallLike(Protocol):
|
|
21
|
-
@property
|
|
22
|
-
def tool_name(self) -> str: ...
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def call_id(self) -> str: ...
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def arguments_json(self) -> str: ...
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclass
|
|
32
|
-
class TruncationResult:
|
|
33
|
-
"""Result of truncation operation."""
|
|
34
|
-
|
|
35
|
-
output: str
|
|
36
|
-
was_truncated: bool
|
|
37
|
-
saved_file_path: str | None = None
|
|
38
|
-
original_length: int = 0
|
|
39
|
-
truncated_length: int = 0
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
FILE_SAVED_PATTERN = re.compile(r"<file_saved>([^<]+)</file_saved>")
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _extract_saved_file_path(output: str) -> str | None:
|
|
46
|
-
"""Extract file path from <file_saved> tag if present."""
|
|
47
|
-
match = FILE_SAVED_PATTERN.search(output)
|
|
48
|
-
return match.group(1) if match else None
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def _extract_url_filename(url: str) -> str:
|
|
52
|
-
"""Extract a safe filename from a URL."""
|
|
53
|
-
parsed = urlparse(url)
|
|
54
|
-
# Combine host and path for a meaningful filename
|
|
55
|
-
host = parsed.netloc.replace(".", "_").replace(":", "_")
|
|
56
|
-
path = parsed.path.strip("/").replace("/", "_")
|
|
57
|
-
name = f"{host}_{path}" if path else host
|
|
58
|
-
# Sanitize: keep only alphanumeric, underscore, hyphen
|
|
59
|
-
name = re.sub(r"[^a-zA-Z0-9_\-]", "_", name)
|
|
60
|
-
# Limit length
|
|
61
|
-
return name[:URL_FILENAME_MAX_LENGTH] if len(name) > URL_FILENAME_MAX_LENGTH else name
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class TruncationStrategy(ABC):
|
|
65
|
-
"""Abstract base class for tool output truncation strategies."""
|
|
66
|
-
|
|
67
|
-
@abstractmethod
|
|
68
|
-
def truncate(self, output: str, tool_call: ToolCallLike | None = None) -> TruncationResult:
|
|
69
|
-
"""Truncate the output according to the strategy."""
|
|
70
|
-
...
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class SimpleTruncationStrategy(TruncationStrategy):
|
|
74
|
-
"""Simple character-based truncation strategy."""
|
|
75
|
-
|
|
76
|
-
def __init__(self, max_length: int = TOOL_OUTPUT_MAX_LENGTH):
|
|
77
|
-
self.max_length = max_length
|
|
78
|
-
|
|
79
|
-
def truncate(self, output: str, tool_call: ToolCallLike | None = None) -> TruncationResult:
|
|
80
|
-
if len(output) > self.max_length:
|
|
81
|
-
truncated_length = len(output) - self.max_length
|
|
82
|
-
truncated_output = output[: self.max_length] + f"… (truncated {truncated_length} characters)"
|
|
83
|
-
return TruncationResult(
|
|
84
|
-
output=truncated_output,
|
|
85
|
-
was_truncated=True,
|
|
86
|
-
original_length=len(output),
|
|
87
|
-
truncated_length=truncated_length,
|
|
88
|
-
)
|
|
89
|
-
return TruncationResult(output=output, was_truncated=False, original_length=len(output))
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class SmartTruncationStrategy(TruncationStrategy):
|
|
93
|
-
"""Smart truncation strategy that saves full output to file and shows head/tail."""
|
|
94
|
-
|
|
95
|
-
def __init__(
|
|
96
|
-
self,
|
|
97
|
-
max_length: int = TOOL_OUTPUT_MAX_LENGTH,
|
|
98
|
-
head_chars: int = TOOL_OUTPUT_DISPLAY_HEAD,
|
|
99
|
-
tail_chars: int = TOOL_OUTPUT_DISPLAY_TAIL,
|
|
100
|
-
truncation_dir: str = TOOL_OUTPUT_TRUNCATION_DIR,
|
|
101
|
-
):
|
|
102
|
-
self.max_length = max_length
|
|
103
|
-
self.head_chars = head_chars
|
|
104
|
-
self.tail_chars = tail_chars
|
|
105
|
-
self.truncation_dir = Path(truncation_dir)
|
|
106
|
-
|
|
107
|
-
def _get_file_identifier(self, tool_call: ToolCallLike | None) -> str:
|
|
108
|
-
"""Get a file identifier based on tool call. For WebFetch, use URL; otherwise use call_id."""
|
|
109
|
-
if tool_call and tool_call.tool_name == tools.WEB_FETCH:
|
|
110
|
-
try:
|
|
111
|
-
args = json.loads(tool_call.arguments_json)
|
|
112
|
-
url = args.get("url", "")
|
|
113
|
-
if url:
|
|
114
|
-
return _extract_url_filename(url)
|
|
115
|
-
except (json.JSONDecodeError, TypeError):
|
|
116
|
-
pass
|
|
117
|
-
# Fallback to call_id
|
|
118
|
-
if tool_call and tool_call.call_id:
|
|
119
|
-
return tool_call.call_id.replace("/", "_")
|
|
120
|
-
return "unknown"
|
|
121
|
-
|
|
122
|
-
def _save_to_file(self, output: str, tool_call: ToolCallLike | None) -> str | None:
|
|
123
|
-
"""Save full output to file. Returns file path or None on failure."""
|
|
124
|
-
try:
|
|
125
|
-
self.truncation_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
-
timestamp = int(time.time())
|
|
127
|
-
tool_name = (tool_call.tool_name if tool_call else "unknown").replace("/", "_")
|
|
128
|
-
identifier = self._get_file_identifier(tool_call)
|
|
129
|
-
filename = f"{tool_name}-{identifier}-{timestamp}.txt"
|
|
130
|
-
file_path = self.truncation_dir / filename
|
|
131
|
-
file_path.write_text(output, encoding="utf-8")
|
|
132
|
-
return str(file_path)
|
|
133
|
-
except OSError:
|
|
134
|
-
return None
|
|
135
|
-
|
|
136
|
-
def truncate(self, output: str, tool_call: ToolCallLike | None = None) -> TruncationResult:
|
|
137
|
-
if tool_call and tool_call.tool_name == tools.READ:
|
|
138
|
-
# Do not truncate Read tool outputs
|
|
139
|
-
return TruncationResult(output=output, was_truncated=False, original_length=len(output))
|
|
140
|
-
|
|
141
|
-
original_length = len(output)
|
|
142
|
-
|
|
143
|
-
if original_length <= self.max_length:
|
|
144
|
-
return TruncationResult(output=output, was_truncated=False, original_length=original_length)
|
|
145
|
-
|
|
146
|
-
# Check if file was already saved (e.g., by WebFetch)
|
|
147
|
-
existing_file_path = _extract_saved_file_path(output)
|
|
148
|
-
saved_file_path = existing_file_path or self._save_to_file(output, tool_call)
|
|
149
|
-
|
|
150
|
-
# Strip existing <file_saved> tag to avoid duplication in head/tail
|
|
151
|
-
content_to_truncate = FILE_SAVED_PATTERN.sub("", output).lstrip("\n") if existing_file_path else output
|
|
152
|
-
content_length = len(content_to_truncate)
|
|
153
|
-
|
|
154
|
-
truncated_length = content_length - self.head_chars - self.tail_chars
|
|
155
|
-
head_content = content_to_truncate[: self.head_chars]
|
|
156
|
-
tail_content = content_to_truncate[-self.tail_chars :]
|
|
157
|
-
|
|
158
|
-
# Build truncated output with file info
|
|
159
|
-
if saved_file_path:
|
|
160
|
-
header = (
|
|
161
|
-
f"<system-reminder>Output truncated ({truncated_length} chars hidden) to reduce context usage. "
|
|
162
|
-
f"Full content saved to <file_saved>{saved_file_path}</file_saved>. "
|
|
163
|
-
f"Use Read(offset, limit) or rg to inspect if needed. "
|
|
164
|
-
f"Showing first {self.head_chars} and last {self.tail_chars} chars:</system-reminder>\n\n"
|
|
165
|
-
)
|
|
166
|
-
else:
|
|
167
|
-
header = (
|
|
168
|
-
f"<system-reminder>Output truncated ({truncated_length} chars hidden) to reduce context usage. "
|
|
169
|
-
f"Showing first {self.head_chars} and last {self.tail_chars} chars:</system-reminder>\n\n"
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
truncated_output = (
|
|
173
|
-
f"{header}{head_content}\n\n"
|
|
174
|
-
f"<system-reminder>… {truncated_length} characters omitted …</system-reminder>\n\n"
|
|
175
|
-
f"{tail_content}"
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
return TruncationResult(
|
|
179
|
-
output=truncated_output,
|
|
180
|
-
was_truncated=True,
|
|
181
|
-
saved_file_path=saved_file_path,
|
|
182
|
-
original_length=original_length,
|
|
183
|
-
truncated_length=truncated_length,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
_default_strategy: TruncationStrategy = SmartTruncationStrategy()
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def get_truncation_strategy() -> TruncationStrategy:
|
|
191
|
-
"""Get the current truncation strategy."""
|
|
192
|
-
return _default_strategy
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def set_truncation_strategy(strategy: TruncationStrategy) -> None:
|
|
196
|
-
"""Set the truncation strategy to use."""
|
|
197
|
-
global _default_strategy
|
|
198
|
-
_default_strategy = strategy
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def truncate_tool_output(output: str, tool_call: ToolCallLike | None = None) -> TruncationResult:
|
|
202
|
-
"""Truncate tool output using the current strategy."""
|
|
203
|
-
return get_truncation_strategy().truncate(output, tool_call)
|
|
File without changes
|
|
File without changes
|