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.
Files changed (55) hide show
  1. klaude_code/app/runtime.py +2 -6
  2. klaude_code/cli/main.py +0 -1
  3. klaude_code/config/assets/builtin_config.yaml +7 -0
  4. klaude_code/const.py +7 -4
  5. klaude_code/core/agent.py +10 -1
  6. klaude_code/core/agent_profile.py +47 -35
  7. klaude_code/core/executor.py +6 -21
  8. klaude_code/core/manager/sub_agent_manager.py +17 -1
  9. klaude_code/core/prompts/prompt-sub-agent-web.md +4 -4
  10. klaude_code/core/task.py +65 -4
  11. klaude_code/core/tool/__init__.py +0 -5
  12. klaude_code/core/tool/context.py +12 -1
  13. klaude_code/core/tool/offload.py +311 -0
  14. klaude_code/core/tool/shell/bash_tool.md +1 -43
  15. klaude_code/core/tool/sub_agent_tool.py +1 -0
  16. klaude_code/core/tool/todo/todo_write_tool.md +0 -23
  17. klaude_code/core/tool/tool_runner.py +14 -9
  18. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  19. klaude_code/core/tool/web/web_fetch_tool.py +14 -39
  20. klaude_code/core/turn.py +128 -139
  21. klaude_code/llm/anthropic/client.py +176 -82
  22. klaude_code/llm/bedrock/client.py +8 -12
  23. klaude_code/llm/claude/client.py +11 -15
  24. klaude_code/llm/client.py +31 -4
  25. klaude_code/llm/codex/client.py +7 -11
  26. klaude_code/llm/google/client.py +150 -69
  27. klaude_code/llm/openai_compatible/client.py +10 -15
  28. klaude_code/llm/openai_compatible/stream.py +68 -6
  29. klaude_code/llm/openrouter/client.py +9 -15
  30. klaude_code/llm/partial_message.py +35 -0
  31. klaude_code/llm/responses/client.py +134 -68
  32. klaude_code/llm/usage.py +30 -0
  33. klaude_code/protocol/commands.py +0 -4
  34. klaude_code/protocol/events/metadata.py +1 -0
  35. klaude_code/protocol/events/system.py +0 -4
  36. klaude_code/protocol/model.py +2 -15
  37. klaude_code/protocol/sub_agent/explore.py +0 -10
  38. klaude_code/protocol/sub_agent/image_gen.py +0 -7
  39. klaude_code/protocol/sub_agent/task.py +0 -10
  40. klaude_code/protocol/sub_agent/web.py +4 -12
  41. klaude_code/session/templates/export_session.html +4 -4
  42. klaude_code/skill/manager.py +2 -1
  43. klaude_code/tui/components/metadata.py +41 -49
  44. klaude_code/tui/components/rich/markdown.py +1 -3
  45. klaude_code/tui/components/rich/theme.py +2 -2
  46. klaude_code/tui/components/tools.py +0 -31
  47. klaude_code/tui/components/welcome.py +1 -32
  48. klaude_code/tui/input/prompt_toolkit.py +25 -9
  49. klaude_code/tui/machine.py +2 -1
  50. {klaude_code-2.4.2.dist-info → klaude_code-2.5.0.dist-info}/METADATA +1 -1
  51. {klaude_code-2.4.2.dist-info → klaude_code-2.5.0.dist-info}/RECORD +53 -53
  52. klaude_code/core/prompts/prompt-nano-banana.md +0 -1
  53. klaude_code/core/tool/truncation.py +0 -203
  54. {klaude_code-2.4.2.dist-info → klaude_code-2.5.0.dist-info}/WHEEL +0 -0
  55. {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)