klaude-code 1.2.17__py3-none-any.whl → 1.2.19__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 (70) hide show
  1. klaude_code/cli/config_cmd.py +1 -1
  2. klaude_code/cli/debug.py +1 -1
  3. klaude_code/cli/main.py +45 -31
  4. klaude_code/cli/runtime.py +49 -13
  5. klaude_code/{version.py → cli/self_update.py} +110 -2
  6. klaude_code/command/__init__.py +4 -1
  7. klaude_code/command/clear_cmd.py +2 -7
  8. klaude_code/command/command_abc.py +33 -5
  9. klaude_code/command/debug_cmd.py +79 -0
  10. klaude_code/command/diff_cmd.py +2 -6
  11. klaude_code/command/export_cmd.py +7 -7
  12. klaude_code/command/export_online_cmd.py +9 -8
  13. klaude_code/command/help_cmd.py +4 -9
  14. klaude_code/command/model_cmd.py +10 -6
  15. klaude_code/command/prompt_command.py +2 -6
  16. klaude_code/command/refresh_cmd.py +2 -7
  17. klaude_code/command/registry.py +69 -26
  18. klaude_code/command/release_notes_cmd.py +2 -6
  19. klaude_code/command/status_cmd.py +2 -7
  20. klaude_code/command/terminal_setup_cmd.py +2 -6
  21. klaude_code/command/thinking_cmd.py +16 -10
  22. klaude_code/config/select_model.py +81 -5
  23. klaude_code/const/__init__.py +1 -1
  24. klaude_code/core/executor.py +257 -110
  25. klaude_code/core/manager/__init__.py +2 -4
  26. klaude_code/core/prompts/prompt-claude-code.md +1 -1
  27. klaude_code/core/prompts/prompt-sub-agent-explore.md +14 -2
  28. klaude_code/core/prompts/prompt-sub-agent-web.md +8 -5
  29. klaude_code/core/reminders.py +9 -35
  30. klaude_code/core/task.py +9 -7
  31. klaude_code/core/tool/file/read_tool.md +1 -1
  32. klaude_code/core/tool/file/read_tool.py +41 -12
  33. klaude_code/core/tool/memory/skill_loader.py +12 -10
  34. klaude_code/core/tool/shell/bash_tool.py +22 -2
  35. klaude_code/core/tool/tool_registry.py +1 -1
  36. klaude_code/core/tool/tool_runner.py +26 -23
  37. klaude_code/core/tool/truncation.py +23 -9
  38. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  39. klaude_code/core/tool/web/web_fetch_tool.py +36 -1
  40. klaude_code/core/turn.py +28 -0
  41. klaude_code/llm/anthropic/client.py +25 -9
  42. klaude_code/llm/openai_compatible/client.py +5 -2
  43. klaude_code/llm/openrouter/client.py +7 -3
  44. klaude_code/llm/responses/client.py +6 -1
  45. klaude_code/protocol/commands.py +1 -0
  46. klaude_code/protocol/sub_agent/web.py +3 -2
  47. klaude_code/session/session.py +35 -15
  48. klaude_code/session/templates/export_session.html +45 -32
  49. klaude_code/trace/__init__.py +20 -2
  50. klaude_code/ui/modes/repl/completers.py +231 -73
  51. klaude_code/ui/modes/repl/event_handler.py +8 -6
  52. klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
  53. klaude_code/ui/modes/repl/renderer.py +2 -2
  54. klaude_code/ui/renderers/common.py +54 -0
  55. klaude_code/ui/renderers/developer.py +2 -3
  56. klaude_code/ui/renderers/errors.py +1 -1
  57. klaude_code/ui/renderers/metadata.py +12 -5
  58. klaude_code/ui/renderers/thinking.py +24 -8
  59. klaude_code/ui/renderers/tools.py +82 -14
  60. klaude_code/ui/rich/code_panel.py +112 -0
  61. klaude_code/ui/rich/markdown.py +3 -4
  62. klaude_code/ui/rich/status.py +0 -2
  63. klaude_code/ui/rich/theme.py +10 -1
  64. klaude_code/ui/utils/common.py +0 -18
  65. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/METADATA +32 -7
  66. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/RECORD +69 -68
  67. klaude_code/core/manager/agent_manager.py +0 -132
  68. /klaude_code/{config → cli}/list_model.py +0 -0
  69. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/WHEEL +0 -0
  70. {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/entry_points.txt +0 -0
@@ -35,7 +35,7 @@ def build_payload(
35
35
  extra_headers: dict[str, str] = {}
36
36
 
37
37
  if param.thinking:
38
- if param.thinking.budget_tokens is not None:
38
+ if param.thinking.type != "disabled" and param.thinking.budget_tokens is not None:
39
39
  extra_body["reasoning"] = {
40
40
  "max_tokens": param.thinking.budget_tokens,
41
41
  "enable": True,
@@ -139,7 +139,8 @@ class OpenRouterClient(LLMClientABC):
139
139
  for item in reasoning_details:
140
140
  try:
141
141
  reasoning_detail = ReasoningDetail.model_validate(item)
142
- metadata_tracker.record_token()
142
+ if reasoning_detail.text or reasoning_detail.summary:
143
+ metadata_tracker.record_token()
143
144
  state.stage = "reasoning"
144
145
  # Yield delta immediately for streaming
145
146
  if reasoning_detail.text:
@@ -198,7 +199,10 @@ class OpenRouterClient(LLMClientABC):
198
199
  yield model.StreamErrorItem(error=f"{e.__class__.__name__} {e!s}")
199
200
 
200
201
  # Finalize
201
- for item in state.flush_all():
202
+ flushed_items = state.flush_all()
203
+ if flushed_items:
204
+ metadata_tracker.record_token()
205
+ for item in flushed_items:
202
206
  yield item
203
207
 
204
208
  metadata_tracker.set_response_id(state.response_id)
@@ -77,6 +77,7 @@ async def parse_responses_stream(
77
77
  yield model.StartItem(response_id=response_id)
78
78
  case responses.ResponseReasoningSummaryTextDeltaEvent() as event:
79
79
  if event.delta:
80
+ metadata_tracker.record_token()
80
81
  yield model.ReasoningTextDelta(
81
82
  content=event.delta,
82
83
  response_id=response_id,
@@ -89,10 +90,12 @@ async def parse_responses_stream(
89
90
  model=str(param.model),
90
91
  )
91
92
  case responses.ResponseTextDeltaEvent() as event:
92
- metadata_tracker.record_token()
93
+ if event.delta:
94
+ metadata_tracker.record_token()
93
95
  yield model.AssistantMessageDelta(content=event.delta, response_id=response_id)
94
96
  case responses.ResponseOutputItemAddedEvent() as event:
95
97
  if isinstance(event.item, responses.ResponseFunctionToolCall):
98
+ metadata_tracker.record_token()
96
99
  yield model.ToolCallStartItem(
97
100
  response_id=response_id,
98
101
  call_id=event.item.call_id,
@@ -102,6 +105,7 @@ async def parse_responses_stream(
102
105
  match event.item:
103
106
  case responses.ResponseReasoningItem() as item:
104
107
  if item.encrypted_content:
108
+ metadata_tracker.record_token()
105
109
  yield model.ReasoningEncryptedItem(
106
110
  id=item.id,
107
111
  encrypted_content=item.encrypted_content,
@@ -109,6 +113,7 @@ async def parse_responses_stream(
109
113
  model=str(param.model),
110
114
  )
111
115
  case responses.ResponseOutputMessage() as item:
116
+ metadata_tracker.record_token()
112
117
  yield model.AssistantMessageItem(
113
118
  content="\n".join(
114
119
  [
@@ -3,6 +3,7 @@ from enum import Enum
3
3
 
4
4
  class CommandName(str, Enum):
5
5
  INIT = "init"
6
+ DEBUG = "debug"
6
7
  DIFF = "diff"
7
8
  HELP = "help"
8
9
  MODEL = "model"
@@ -20,14 +20,15 @@ Capabilities:
20
20
 
21
21
  How to use:
22
22
  - Write a clear prompt describing what information you need - the agent will search and fetch as needed
23
+ - Account for "Today's date" in <env>. For example, if <env> says "Today's date: 2025-07-01", and the user wants the latest docs, do not use 2024 in the search query. Use 2025.
23
24
  - Optionally provide a `url` if you already know the target page
24
25
  - Use `output_format` (JSON Schema) to get structured data back from the agent
25
- - Account for "Today's date" in <env>. For example, if <env> says "Today's date: 2025-07-01", and the user wants the latest docs, do not use 2024 in the search query. Use 2025.
26
26
 
27
27
  What you receive:
28
28
  - The agent returns a text response summarizing its findings
29
29
  - With `output_format`, you receive structured JSON matching your schema
30
- - The response is the agent's analysis, not raw web content\
30
+ - The response is the agent's analysis, not raw web content
31
+ - Web content is saved to local files (paths included in Sources) - read them directly if you need full content\
31
32
  """
32
33
 
33
34
  WEB_AGENT_PARAMETERS = {
@@ -7,7 +7,7 @@ from typing import ClassVar
7
7
 
8
8
  from pydantic import BaseModel, Field, PrivateAttr
9
9
 
10
- from klaude_code.protocol import events, model, tools
10
+ from klaude_code.protocol import events, llm_param, model, tools
11
11
 
12
12
 
13
13
  class Session(BaseModel):
@@ -22,6 +22,9 @@ class Session(BaseModel):
22
22
  # Model name used for this session
23
23
  # Used in list method SessionMetaBrief
24
24
  model_name: str | None = None
25
+
26
+ model_config_name: str | None = None
27
+ model_thinking: llm_param.Thinking | None = None
25
28
  # Timestamps (epoch seconds)
26
29
  created_at: float = Field(default_factory=lambda: time.time())
27
30
  updated_at: float = Field(default_factory=lambda: time.time())
@@ -36,7 +39,7 @@ class Session(BaseModel):
36
39
 
37
40
  @property
38
41
  def messages_count(self) -> int:
39
- """Count of user and assistant messages in conversation history.
42
+ """Count of user, assistant messages, and tool calls in conversation history.
40
43
 
41
44
  This is a cached property that is invalidated when append_history is called.
42
45
  """
@@ -44,7 +47,7 @@ class Session(BaseModel):
44
47
  self._messages_count_cache = sum(
45
48
  1
46
49
  for it in self.conversation_history
47
- if isinstance(it, (model.UserMessageItem, model.AssistantMessageItem))
50
+ if isinstance(it, (model.UserMessageItem, model.AssistantMessageItem, model.ToolCallItem))
48
51
  )
49
52
  return self._messages_count_cache
50
53
 
@@ -103,14 +106,7 @@ class Session(BaseModel):
103
106
  return self._messages_dir() / f"{prefix}-{self.id}.jsonl"
104
107
 
105
108
  @classmethod
106
- def create(cls, id: str | None = None) -> "Session":
107
- """Create a new session without checking for existing files."""
108
- return Session(id=id or uuid.uuid4().hex, work_dir=Path.cwd())
109
-
110
- @classmethod
111
- def load(cls, id: str) -> "Session":
112
- """Load an existing session or create a new one if not found."""
113
- # Load session metadata
109
+ def _find_session_file(cls, id: str) -> Path | None:
114
110
  sessions_dir = cls._sessions_dir()
115
111
  session_candidates = sorted(
116
112
  sessions_dir.glob(f"*-{id}.json"),
@@ -118,13 +114,24 @@ class Session(BaseModel):
118
114
  reverse=True,
119
115
  )
120
116
  if not session_candidates:
121
- # No existing session; create a new one
117
+ return None
118
+ return session_candidates[0]
119
+
120
+ @classmethod
121
+ def create(cls, id: str | None = None) -> "Session":
122
+ """Create a new session without checking for existing files."""
123
+ return Session(id=id or uuid.uuid4().hex, work_dir=Path.cwd())
124
+
125
+ @classmethod
126
+ def load_meta(cls, id: str) -> "Session":
127
+ """Load session metadata only (without loading messages history)."""
128
+
129
+ session_path = cls._find_session_file(id)
130
+ if session_path is None:
122
131
  return Session(id=id, work_dir=Path.cwd())
123
- session_path = session_candidates[0]
124
132
 
125
133
  raw = json.loads(session_path.read_text())
126
134
 
127
- # Basic fields (conversation history is loaded separately)
128
135
  work_dir_str = raw.get("work_dir", str(Path.cwd()))
129
136
 
130
137
  sub_agent_state_raw = raw.get("sub_agent_state")
@@ -135,8 +142,12 @@ class Session(BaseModel):
135
142
  created_at = float(raw.get("created_at", time.time()))
136
143
  updated_at = float(raw.get("updated_at", created_at))
137
144
  model_name = raw.get("model_name")
145
+ model_config_name = raw.get("model_config_name")
138
146
 
139
- sess = Session(
147
+ model_thinking_raw = raw.get("model_thinking")
148
+ model_thinking = llm_param.Thinking(**model_thinking_raw) if isinstance(model_thinking_raw, dict) else None # pyright: ignore[reportUnknownArgumentType]
149
+
150
+ return Session(
140
151
  id=id,
141
152
  work_dir=Path(work_dir_str),
142
153
  sub_agent_state=sub_agent_state,
@@ -146,8 +157,15 @@ class Session(BaseModel):
146
157
  created_at=created_at,
147
158
  updated_at=updated_at,
148
159
  model_name=model_name,
160
+ model_config_name=model_config_name,
161
+ model_thinking=model_thinking,
149
162
  )
150
163
 
164
+ @classmethod
165
+ def load(cls, id: str) -> "Session":
166
+ """Load an existing session or create a new one if not found."""
167
+ sess = cls.load_meta(id)
168
+
151
169
  # Load conversation history from messages JSONL
152
170
  messages_dir = cls._messages_dir()
153
171
  # Expect a single messages file per session (prefixed filenames only)
@@ -204,6 +222,8 @@ class Session(BaseModel):
204
222
  "updated_at": self.updated_at,
205
223
  "messages_count": self.messages_count,
206
224
  "model_name": self.model_name,
225
+ "model_config_name": self.model_config_name,
226
+ "model_thinking": self.model_thinking.model_dump() if self.model_thinking else None,
207
227
  }
208
228
  self._session_file().write_text(json.dumps(payload, ensure_ascii=False, indent=2))
209
229
 
@@ -6,42 +6,50 @@
6
6
  <title>Klaude Code - $first_user_message</title>
7
7
  <link
8
8
  rel="icon"
9
- href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%230851b2%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><polyline points=%2216 18 22 12 16 6%22></polyline><polyline points=%228 6 2 12 8 18%22></polyline></svg>"
9
+ href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22%230b5bd3%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22><polyline points=%2216 18 22 12 16 6%22></polyline><polyline points=%228 6 2 12 8 18%22></polyline></svg>"
10
10
  />
11
11
  <link
12
- href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans/latin-400.css"
12
+ href="https://cdn.jsdelivr.net/npm/@fontsource/geist/400.css"
13
13
  rel="stylesheet"
14
14
  />
15
15
  <link
16
- href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans/latin-500.css"
16
+ href="https://cdn.jsdelivr.net/npm/@fontsource/geist/800.css"
17
17
  rel="stylesheet"
18
18
  />
19
19
  <link
20
- href="https://cdn.jsdelivr.net/npm/@fontsource/geist-sans/latin-700.css"
20
+ href="https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono/400.css"
21
+ rel="stylesheet"
22
+ />
23
+ <link
24
+ href="https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono/800.css"
21
25
  rel="stylesheet"
22
26
  />
23
27
  <style>
24
28
  :root {
25
- --bg-body: #ededed;
26
- --bg-container: #f0f0f0;
27
- --bg-card: #f0f0f0;
28
- --border: #c8c8c8;
29
- --text: #111111;
30
- --text-dim: #64748b;
31
- --accent: #0851b2;
32
- --accent-dim: rgba(8, 145, 178, 0.08);
33
- --success: #15803d;
34
- --error: #dc2626;
35
- --bg-error: #ffebee;
36
- --bg-code: #f3f3f3;
37
- --fg-inline-code: #4f4fc7;
38
- --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
39
- --font-markdown-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
40
- --font-markdown: "Geist Sans", system-ui, sans-serif;
29
+ --bg-body: #f0efeb;
30
+ --bg-container: #f3f2ee;
31
+ --bg-card: #f3f2ee;
32
+ --bg-overlay: rgba(248, 246, 240, 0.98);
33
+ --bg-error: #fdecec;
34
+ --bg-success: #eaf6ed;
35
+ --bg-code: #f7f7f4;
36
+ --border: #ded8cf;
37
+ --text: #151515;
38
+ --text-dim: #5f6b7a;
39
+ --accent: #0b5bd3;
40
+ --accent-dim: rgba(11, 91, 211, 0.10);
41
+ --accent-underline: rgba(11, 91, 211, 0.25);
42
+ --accent-hover: rgba(11, 91, 211, 0.06);
43
+ --success: #1a7f37;
44
+ --error: #c62828;
45
+ --fg-inline-code: #1f4fbf;
46
+ --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
47
+ --font-markdown-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
48
+ --font-markdown: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
41
49
  --font-weight-bold: 800;
42
- --font-size-xs: 13px;
43
- --font-size-sm: 14px;
44
- --font-size-base: 15px;
50
+ --font-size-xs: 12px;
51
+ --font-size-sm: 13px;
52
+ --font-size-base: 14px;
45
53
  --font-size-lg: 16px;
46
54
  --spacing-xs: 4px;
47
55
  --spacing-sm: 8px;
@@ -62,7 +70,6 @@
62
70
  background-color: var(--bg-body);
63
71
  color: var(--text);
64
72
  font-family: var(--font-mono);
65
- font-feature-settings: "ss18";
66
73
  line-height: 1.6;
67
74
  font-size: var(--font-size-lg);
68
75
  -webkit-font-smoothing: antialiased;
@@ -340,7 +347,7 @@
340
347
  left: 0;
341
348
  width: 100vw;
342
349
  height: 100vh;
343
- background: rgba(255, 255, 255, 0.98);
350
+ background: var(--bg-overlay);
344
351
  z-index: 1000;
345
352
  display: flex;
346
353
  flex-direction: column;
@@ -697,12 +704,12 @@
697
704
  .markdown-body a {
698
705
  color: var(--accent);
699
706
  text-decoration: none;
700
- border-bottom: 1px solid rgba(8, 81, 178, 0.2);
707
+ border-bottom: 1px solid var(--accent-underline);
701
708
  transition: border-color 0.2s, background-color 0.2s;
702
709
  }
703
710
  .markdown-body a:hover {
704
711
  border-bottom-color: var(--accent);
705
- background-color: rgba(8, 81, 178, 0.05);
712
+ background-color: var(--accent-hover);
706
713
  border-radius: 2px;
707
714
  }
708
715
 
@@ -813,13 +820,13 @@
813
820
  white-space: pre;
814
821
  }
815
822
  .diff-plus {
816
- color: #166534;
817
- background: #e7f5e9;
823
+ color: var(--success);
824
+ background: var(--bg-success);
818
825
  display: block;
819
826
  }
820
827
  .diff-minus {
821
- color: #991b1b;
822
- background: #ffebee;
828
+ color: var(--error);
829
+ background: var(--bg-error);
823
830
  display: block;
824
831
  }
825
832
  .diff-ctx {
@@ -1267,10 +1274,16 @@
1267
1274
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1268
1275
  <script type="module">
1269
1276
  import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
1277
+ const markdownMonoFont = getComputedStyle(document.documentElement)
1278
+ .getPropertyValue("--font-markdown-mono")
1279
+ .trim();
1280
+
1270
1281
  mermaid.initialize({
1271
1282
  startOnLoad: true,
1272
1283
  theme: "neutral",
1273
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
1284
+ fontFamily:
1285
+ markdownMonoFont ||
1286
+ 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace',
1274
1287
  });
1275
1288
  </script>
1276
1289
  <script>
@@ -1,3 +1,21 @@
1
- from .log import DebugType, is_debug_enabled, log, log_debug, logger, prepare_debug_log_file, set_debug_logging
1
+ from .log import (
2
+ DebugType,
3
+ get_current_log_file,
4
+ is_debug_enabled,
5
+ log,
6
+ log_debug,
7
+ logger,
8
+ prepare_debug_log_file,
9
+ set_debug_logging,
10
+ )
2
11
 
3
- __all__ = ["DebugType", "is_debug_enabled", "log", "log_debug", "logger", "prepare_debug_log_file", "set_debug_logging"]
12
+ __all__ = [
13
+ "DebugType",
14
+ "get_current_log_file",
15
+ "is_debug_enabled",
16
+ "log",
17
+ "log_debug",
18
+ "logger",
19
+ "prepare_debug_log_file",
20
+ "set_debug_logging",
21
+ ]