klaude-code 1.9.0__py3-none-any.whl → 2.0.1__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 (132) hide show
  1. klaude_code/auth/base.py +2 -6
  2. klaude_code/cli/auth_cmd.py +4 -4
  3. klaude_code/cli/cost_cmd.py +1 -1
  4. klaude_code/cli/list_model.py +1 -1
  5. klaude_code/cli/main.py +1 -1
  6. klaude_code/cli/runtime.py +7 -5
  7. klaude_code/cli/self_update.py +1 -1
  8. klaude_code/cli/session_cmd.py +1 -1
  9. klaude_code/command/clear_cmd.py +6 -2
  10. klaude_code/command/command_abc.py +2 -2
  11. klaude_code/command/debug_cmd.py +4 -4
  12. klaude_code/command/export_cmd.py +2 -2
  13. klaude_code/command/export_online_cmd.py +12 -12
  14. klaude_code/command/fork_session_cmd.py +29 -23
  15. klaude_code/command/help_cmd.py +4 -4
  16. klaude_code/command/model_cmd.py +4 -4
  17. klaude_code/command/model_select.py +1 -1
  18. klaude_code/command/prompt-commit.md +11 -2
  19. klaude_code/command/prompt_command.py +3 -3
  20. klaude_code/command/refresh_cmd.py +2 -2
  21. klaude_code/command/registry.py +7 -5
  22. klaude_code/command/release_notes_cmd.py +4 -4
  23. klaude_code/command/resume_cmd.py +15 -11
  24. klaude_code/command/status_cmd.py +4 -4
  25. klaude_code/command/terminal_setup_cmd.py +8 -8
  26. klaude_code/command/thinking_cmd.py +4 -4
  27. klaude_code/config/assets/builtin_config.yaml +20 -0
  28. klaude_code/config/builtin_config.py +16 -5
  29. klaude_code/config/config.py +7 -2
  30. klaude_code/const.py +147 -91
  31. klaude_code/core/agent.py +3 -12
  32. klaude_code/core/executor.py +18 -39
  33. klaude_code/core/manager/sub_agent_manager.py +71 -7
  34. klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
  35. klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
  36. klaude_code/core/reminders.py +88 -69
  37. klaude_code/core/task.py +44 -45
  38. klaude_code/core/tool/file/apply_patch_tool.py +9 -9
  39. klaude_code/core/tool/file/diff_builder.py +3 -5
  40. klaude_code/core/tool/file/edit_tool.py +23 -23
  41. klaude_code/core/tool/file/move_tool.py +43 -43
  42. klaude_code/core/tool/file/read_tool.py +44 -39
  43. klaude_code/core/tool/file/write_tool.py +14 -14
  44. klaude_code/core/tool/report_back_tool.py +4 -4
  45. klaude_code/core/tool/shell/bash_tool.py +23 -23
  46. klaude_code/core/tool/skill/skill_tool.py +7 -7
  47. klaude_code/core/tool/sub_agent_tool.py +38 -9
  48. klaude_code/core/tool/todo/todo_write_tool.py +9 -10
  49. klaude_code/core/tool/todo/update_plan_tool.py +6 -6
  50. klaude_code/core/tool/tool_abc.py +2 -2
  51. klaude_code/core/tool/tool_context.py +27 -0
  52. klaude_code/core/tool/tool_runner.py +88 -42
  53. klaude_code/core/tool/truncation.py +38 -20
  54. klaude_code/core/tool/web/mermaid_tool.py +6 -7
  55. klaude_code/core/tool/web/web_fetch_tool.py +68 -30
  56. klaude_code/core/tool/web/web_search_tool.py +15 -17
  57. klaude_code/core/turn.py +120 -73
  58. klaude_code/llm/anthropic/client.py +79 -44
  59. klaude_code/llm/anthropic/input.py +116 -108
  60. klaude_code/llm/bedrock/client.py +8 -5
  61. klaude_code/llm/claude/client.py +18 -8
  62. klaude_code/llm/client.py +4 -3
  63. klaude_code/llm/codex/client.py +15 -9
  64. klaude_code/llm/google/client.py +122 -60
  65. klaude_code/llm/google/input.py +94 -108
  66. klaude_code/llm/image.py +123 -0
  67. klaude_code/llm/input_common.py +136 -189
  68. klaude_code/llm/openai_compatible/client.py +17 -7
  69. klaude_code/llm/openai_compatible/input.py +36 -66
  70. klaude_code/llm/openai_compatible/stream.py +119 -67
  71. klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
  72. klaude_code/llm/openrouter/client.py +34 -9
  73. klaude_code/llm/openrouter/input.py +63 -64
  74. klaude_code/llm/openrouter/reasoning.py +22 -24
  75. klaude_code/llm/registry.py +20 -17
  76. klaude_code/llm/responses/client.py +107 -45
  77. klaude_code/llm/responses/input.py +115 -98
  78. klaude_code/llm/usage.py +52 -25
  79. klaude_code/protocol/__init__.py +1 -0
  80. klaude_code/protocol/events.py +16 -12
  81. klaude_code/protocol/llm_param.py +20 -2
  82. klaude_code/protocol/message.py +250 -0
  83. klaude_code/protocol/model.py +95 -285
  84. klaude_code/protocol/op.py +2 -15
  85. klaude_code/protocol/op_handler.py +0 -5
  86. klaude_code/protocol/sub_agent/__init__.py +1 -0
  87. klaude_code/protocol/sub_agent/explore.py +10 -0
  88. klaude_code/protocol/sub_agent/image_gen.py +119 -0
  89. klaude_code/protocol/sub_agent/task.py +10 -0
  90. klaude_code/protocol/sub_agent/web.py +10 -0
  91. klaude_code/session/codec.py +6 -6
  92. klaude_code/session/export.py +261 -62
  93. klaude_code/session/selector.py +7 -24
  94. klaude_code/session/session.py +126 -54
  95. klaude_code/session/store.py +5 -32
  96. klaude_code/session/templates/export_session.html +1 -1
  97. klaude_code/session/templates/mermaid_viewer.html +1 -1
  98. klaude_code/trace/log.py +11 -6
  99. klaude_code/ui/core/input.py +1 -1
  100. klaude_code/ui/core/stage_manager.py +1 -8
  101. klaude_code/ui/modes/debug/display.py +2 -2
  102. klaude_code/ui/modes/repl/clipboard.py +2 -2
  103. klaude_code/ui/modes/repl/completers.py +18 -10
  104. klaude_code/ui/modes/repl/event_handler.py +138 -132
  105. klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
  106. klaude_code/ui/modes/repl/key_bindings.py +136 -2
  107. klaude_code/ui/modes/repl/renderer.py +107 -15
  108. klaude_code/ui/renderers/assistant.py +2 -2
  109. klaude_code/ui/renderers/bash_syntax.py +36 -4
  110. klaude_code/ui/renderers/common.py +70 -10
  111. klaude_code/ui/renderers/developer.py +7 -6
  112. klaude_code/ui/renderers/diffs.py +11 -11
  113. klaude_code/ui/renderers/mermaid_viewer.py +49 -2
  114. klaude_code/ui/renderers/metadata.py +33 -5
  115. klaude_code/ui/renderers/sub_agent.py +57 -16
  116. klaude_code/ui/renderers/thinking.py +37 -2
  117. klaude_code/ui/renderers/tools.py +188 -178
  118. klaude_code/ui/rich/live.py +3 -1
  119. klaude_code/ui/rich/markdown.py +39 -7
  120. klaude_code/ui/rich/quote.py +76 -1
  121. klaude_code/ui/rich/status.py +14 -8
  122. klaude_code/ui/rich/theme.py +20 -14
  123. klaude_code/ui/terminal/image.py +34 -0
  124. klaude_code/ui/terminal/notifier.py +2 -1
  125. klaude_code/ui/terminal/progress_bar.py +4 -4
  126. klaude_code/ui/terminal/selector.py +22 -4
  127. klaude_code/ui/utils/common.py +11 -2
  128. {klaude_code-1.9.0.dist-info → klaude_code-2.0.1.dist-info}/METADATA +4 -2
  129. klaude_code-2.0.1.dist-info/RECORD +229 -0
  130. klaude_code-1.9.0.dist-info/RECORD +0 -224
  131. {klaude_code-1.9.0.dist-info → klaude_code-2.0.1.dist-info}/WHEEL +0 -0
  132. {klaude_code-1.9.0.dist-info → klaude_code-2.0.1.dist-info}/entry_points.txt +0 -0
@@ -3,7 +3,7 @@ import asyncio
3
3
  from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
- from klaude_code.protocol import commands, events, model, op
6
+ from klaude_code.protocol import commands, events, message, model, op
7
7
  from klaude_code.session.selector import build_session_select_options, format_user_messages_display
8
8
  from klaude_code.trace import log
9
9
  from klaude_code.ui.terminal.selector import SelectItem, select_one
@@ -35,14 +35,16 @@ def select_session_sync() -> str | None:
35
35
  display_msgs = format_user_messages_display(opt.user_messages)
36
36
  title: list[tuple[str, str]] = []
37
37
  title.append(("fg:ansibrightblack", f"{idx:2}. "))
38
- title.append(
39
- ("class:meta", f"{opt.relative_time} · {opt.messages_count} · {opt.model_name} · {opt.session_id}\n")
40
- )
41
- for msg in display_msgs:
38
+ title.append(("class:meta", f"{opt.relative_time} · {opt.messages_count} · {opt.model_name}"))
39
+ title.append(("fg:ansibrightblack dim", f" · {opt.session_id}\n"))
40
+ for i, msg in enumerate(display_msgs):
41
+ is_last = i == len(display_msgs) - 1
42
42
  if msg == "⋮":
43
43
  title.append(("class:msg", f" {msg}\n"))
44
44
  else:
45
- title.append(("class:msg", f" > {msg}\n"))
45
+ prefix = "└─" if is_last else "├─"
46
+ title.append(("fg:ansibrightblack dim", f" {prefix} "))
47
+ title.append(("class:msg", f"{msg}\n"))
46
48
  title.append(("", "\n"))
47
49
 
48
50
  search_text = " ".join(opt.user_messages) + f" {opt.model_name} {opt.session_id}"
@@ -80,14 +82,16 @@ class ResumeCommand(CommandABC):
80
82
  def is_interactive(self) -> bool:
81
83
  return True
82
84
 
83
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
85
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
84
86
  del user_input # unused
85
87
 
86
88
  if agent.session.messages_count > 0:
87
89
  event = events.DeveloperMessageEvent(
88
90
  session_id=agent.session.id,
89
- item=model.DeveloperMessageItem(
90
- content="Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection.",
91
+ item=message.DeveloperMessage(
92
+ parts=message.text_parts_from_str(
93
+ "Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection."
94
+ ),
91
95
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
92
96
  ),
93
97
  )
@@ -97,8 +101,8 @@ class ResumeCommand(CommandABC):
97
101
  if selected_session_id is None:
98
102
  event = events.DeveloperMessageEvent(
99
103
  session_id=agent.session.id,
100
- item=model.DeveloperMessageItem(
101
- content="(no session selected)",
104
+ item=message.DeveloperMessage(
105
+ parts=message.text_parts_from_str("(no session selected)"),
102
106
  command_output=model.CommandOutput(command_name=self.name),
103
107
  ),
104
108
  )
@@ -1,5 +1,5 @@
1
1
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
2
- from klaude_code.protocol import commands, events, model
2
+ from klaude_code.protocol import commands, events, message, model
3
3
  from klaude_code.session.session import Session
4
4
 
5
5
 
@@ -132,15 +132,15 @@ class StatusCommand(CommandABC):
132
132
  def summary(self) -> str:
133
133
  return "Show session usage statistics"
134
134
 
135
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
135
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
136
136
  del user_input # unused
137
137
  session = agent.session
138
138
  aggregated = accumulate_session_usage(session)
139
139
 
140
140
  event = events.DeveloperMessageEvent(
141
141
  session_id=session.id,
142
- item=model.DeveloperMessageItem(
143
- content=format_status_content(aggregated),
142
+ item=message.DeveloperMessage(
143
+ parts=message.text_parts_from_str(format_status_content(aggregated)),
144
144
  command_output=model.CommandOutput(
145
145
  command_name=self.name,
146
146
  ui_extra=model.SessionStatusUIExtra(
@@ -3,7 +3,7 @@ import subprocess
3
3
  from pathlib import Path
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
- from klaude_code.protocol import commands, events, model
6
+ from klaude_code.protocol import commands, events, message, model
7
7
 
8
8
 
9
9
  class TerminalSetupCommand(CommandABC):
@@ -21,7 +21,7 @@ class TerminalSetupCommand(CommandABC):
21
21
  def is_interactive(self) -> bool:
22
22
  return False
23
23
 
24
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
24
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
25
25
  del user_input # unused
26
26
  term_program = os.environ.get("TERM_PROGRAM", "").lower()
27
27
 
@@ -221,28 +221,28 @@ class TerminalSetupCommand(CommandABC):
221
221
 
222
222
  return message
223
223
 
224
- def _create_success_result(self, agent: "Agent", message: str) -> CommandResult:
224
+ def _create_success_result(self, agent: "Agent", msg: str) -> CommandResult:
225
225
  """Create success result"""
226
226
  return CommandResult(
227
227
  events=[
228
228
  events.DeveloperMessageEvent(
229
229
  session_id=agent.session.id,
230
- item=model.DeveloperMessageItem(
231
- content=message,
230
+ item=message.DeveloperMessage(
231
+ parts=message.text_parts_from_str(msg),
232
232
  command_output=model.CommandOutput(command_name=self.name, is_error=False),
233
233
  ),
234
234
  )
235
235
  ]
236
236
  )
237
237
 
238
- def _create_error_result(self, agent: "Agent", message: str) -> CommandResult:
238
+ def _create_error_result(self, agent: "Agent", msg: str) -> CommandResult:
239
239
  """Create error result"""
240
240
  return CommandResult(
241
241
  events=[
242
242
  events.DeveloperMessageEvent(
243
243
  session_id=agent.session.id,
244
- item=model.DeveloperMessageItem(
245
- content=message,
244
+ item=message.DeveloperMessage(
245
+ parts=message.text_parts_from_str(msg),
246
246
  command_output=model.CommandOutput(command_name=self.name, is_error=True),
247
247
  ),
248
248
  )
@@ -4,7 +4,7 @@ from prompt_toolkit.styles import Style
4
4
 
5
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
6
6
  from klaude_code.config.thinking import get_thinking_picker_data, parse_thinking_value
7
- from klaude_code.protocol import commands, events, llm_param, model, op
7
+ from klaude_code.protocol import commands, events, llm_param, message, model, op
8
8
  from klaude_code.ui.terminal.selector import SelectItem, select_one
9
9
 
10
10
  SELECT_STYLE = Style(
@@ -67,7 +67,7 @@ class ThinkingCommand(CommandABC):
67
67
  def is_interactive(self) -> bool:
68
68
  return True
69
69
 
70
- async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
70
+ async def run(self, agent: Agent, user_input: message.UserInputPayload) -> CommandResult:
71
71
  del user_input # unused
72
72
  if agent.profile is None:
73
73
  return CommandResult(events=[])
@@ -80,8 +80,8 @@ class ThinkingCommand(CommandABC):
80
80
  events=[
81
81
  events.DeveloperMessageEvent(
82
82
  session_id=agent.session.id,
83
- item=model.DeveloperMessageItem(
84
- content="(no change)",
83
+ item=message.DeveloperMessage(
84
+ parts=message.text_parts_from_str("(no change)"),
85
85
  command_output=model.CommandOutput(command_name=self.name),
86
86
  ),
87
87
  )
@@ -187,6 +187,18 @@ provider_list:
187
187
  input: 0.5
188
188
  output: 3.0
189
189
  cache_read: 0.05
190
+ - model_name: nano-banana-pro@or
191
+ model_params:
192
+ model: google/gemini-3-pro-image-preview
193
+ context_limit: 1048576
194
+ modalities:
195
+ - image
196
+ - text
197
+ cost:
198
+ input: 2
199
+ output: 12
200
+ cache_read: 0.2
201
+ image: 120
190
202
  - model_name: grok
191
203
  model_params:
192
204
  model: x-ai/grok-4.1-fast
@@ -238,6 +250,7 @@ provider_list:
238
250
  input: 0.5
239
251
  output: 3.0
240
252
  cache_read: 0.05
253
+
241
254
  - provider_name: bedrock
242
255
  protocol: bedrock
243
256
  aws_access_key: ${AWS_ACCESS_KEY_ID}
@@ -334,3 +347,10 @@ provider_list:
334
347
  reasoning_effort: medium
335
348
  context_limit: 400000
336
349
  max_tokens: 128000
350
+ cost:
351
+ input: 1.75
352
+ output: 14.0
353
+ cache_read: 0.17
354
+
355
+ sub_agent_models:
356
+ ImageGen: nano-banana-pro@or
@@ -7,7 +7,7 @@ manually configuring providers.
7
7
 
8
8
  from functools import lru_cache
9
9
  from importlib import resources
10
- from typing import TYPE_CHECKING
10
+ from typing import TYPE_CHECKING, Any
11
11
 
12
12
  import yaml
13
13
 
@@ -26,13 +26,24 @@ SUPPORTED_API_KEY_ENVS = [
26
26
 
27
27
 
28
28
  @lru_cache(maxsize=1)
29
+ def _load_builtin_yaml() -> dict[str, Any]:
30
+ """Load the built-in config YAML asset."""
31
+ assets = resources.files("klaude_code.config.assets")
32
+ yaml_content = (assets / "builtin_config.yaml").read_text()
33
+ data: dict[str, Any] = yaml.safe_load(yaml_content)
34
+ return data
35
+
36
+
29
37
  def get_builtin_provider_configs() -> list["ProviderConfig"]:
30
38
  """Load built-in provider configurations from YAML asset."""
31
39
  # Import here to avoid circular import
32
40
  from klaude_code.config.config import ProviderConfig
33
41
 
34
- assets = resources.files("klaude_code.config.assets")
35
- yaml_content = (assets / "builtin_config.yaml").read_text()
36
- data = yaml.safe_load(yaml_content)
37
-
42
+ data = _load_builtin_yaml()
38
43
  return [ProviderConfig.model_validate(p) for p in data.get("provider_list", [])]
44
+
45
+
46
+ def get_builtin_sub_agent_models() -> dict[str, str]:
47
+ """Load built-in sub agent model mappings from YAML asset."""
48
+ data = _load_builtin_yaml()
49
+ return data.get("sub_agent_models", {})
@@ -8,7 +8,11 @@ from typing import Any, cast
8
8
  import yaml
9
9
  from pydantic import BaseModel, Field, ValidationError, model_validator
10
10
 
11
- from klaude_code.config.builtin_config import SUPPORTED_API_KEY_ENVS, get_builtin_provider_configs
11
+ from klaude_code.config.builtin_config import (
12
+ SUPPORTED_API_KEY_ENVS,
13
+ get_builtin_provider_configs,
14
+ get_builtin_sub_agent_models,
15
+ )
12
16
  from klaude_code.protocol import llm_param
13
17
  from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
14
18
  from klaude_code.trace import log
@@ -294,7 +298,8 @@ def _get_builtin_config() -> Config:
294
298
  # Re-validate to ensure compatibility with current ProviderConfig class
295
299
  # (needed for tests that may monkeypatch the class)
296
300
  providers = [ProviderConfig.model_validate(p.model_dump()) for p in get_builtin_provider_configs()]
297
- return Config(provider_list=providers)
301
+ sub_agent_models = get_builtin_sub_agent_models()
302
+ return Config(provider_list=providers, sub_agent_models=sub_agent_models)
298
303
 
299
304
 
300
305
  def _merge_provider(builtin: ProviderConfig, user: UserProviderConfig) -> ProviderConfig:
klaude_code/const.py CHANGED
@@ -4,7 +4,10 @@ This module consolidates all magic numbers and configuration values
4
4
  that were previously scattered across the codebase.
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  import os
10
+ from dataclasses import dataclass
8
11
  from pathlib import Path
9
12
 
10
13
 
@@ -20,145 +23,198 @@ def _get_int_env(name: str, default: int) -> int:
20
23
 
21
24
 
22
25
  # =============================================================================
23
- # Agent Configuration
26
+ # Agent / LLM Configuration
24
27
  # =============================================================================
25
28
 
26
- # Timeout for waiting for the first event from LLM (seconds)
27
- FIRST_EVENT_TIMEOUT_S = 200.0
29
+ MAX_FAILED_TURN_RETRIES = 10 # Maximum retry attempts for failed turns
30
+ LLM_HTTP_TIMEOUT_TOTAL = 300.0 # HTTP timeout for LLM API requests (seconds)
31
+ LLM_HTTP_TIMEOUT_CONNECT = 15.0 # HTTP connect timeout (seconds)
32
+ LLM_HTTP_TIMEOUT_READ = 285.0 # HTTP read timeout (seconds)
28
33
 
29
- # Maximum number of retry attempts for failed turns
30
- MAX_FAILED_TURN_RETRIES = 10
34
+ ANTHROPIC_BETA_INTERLEAVED_THINKING = "interleaved-thinking-2025-05-14" # Anthropic API beta flag
35
+ ANTHROPIC_BETA_OAUTH = "oauth-2025-04-20" # Anthropic OAuth beta flag
36
+ ANTHROPIC_BETA_FINE_GRAINED_TOOL_STREAMING = "fine-grained-tool-streaming-2025-05-14" # Anthropic streaming beta
37
+ CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude." # Claude identity string
31
38
 
32
- # Initial delay before retrying a failed turn (seconds)
33
- INITIAL_RETRY_DELAY_S = 1.0
39
+ OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1" # OpenRouter API base URL
34
40
 
35
- # Maximum delay between retries (seconds)
36
- MAX_RETRY_DELAY_S = 30.0
41
+ CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex" # Codex API base URL
42
+ CODEX_USER_AGENT = "codex_cli_rs/0.0.0-klaude" # Codex user agent string
37
43
 
38
- # Message shown when a tool call is cancelled by the user
39
- CANCEL_OUTPUT = "[Request interrupted by user for tool use]"
44
+ SUPPORTED_IMAGE_SIZES = {"1K", "2K", "4K"} # Supported image sizes for ImageGen tool
40
45
 
41
- # Default maximum tokens for LLM responses
42
- DEFAULT_MAX_TOKENS = 32000
46
+ THROUGHPUT_MIN_DURATION_SEC = 0.15 # Minimum duration (seconds) for throughput calculation
47
+ INITIAL_RETRY_DELAY_S = 1.0 # Initial delay before retrying a failed turn (seconds)
48
+ MAX_RETRY_DELAY_S = 30.0 # Maximum delay between retries (seconds)
49
+ CANCEL_OUTPUT = "[Request interrupted by user for tool use]" # Message shown when tool call is cancelled
50
+ INTERRUPT_MARKER = " <system>interrupted by user</system>" # Marker appended when assistant is interrupted
51
+ EMPTY_TOOL_OUTPUT_MESSAGE = (
52
+ "<system-reminder>Tool ran without output or errors</system-reminder>" # Tool output placeholder
53
+ )
54
+ DEFAULT_MAX_TOKENS = 32000 # Default maximum tokens for LLM responses
55
+ DEFAULT_TEMPERATURE = 1.0 # Default temperature for LLM requests
56
+ DEFAULT_ANTHROPIC_THINKING_BUDGET_TOKENS = 2048 # Default thinking budget tokens for Anthropic models
43
57
 
44
- # Default temperature for LLM requests
45
- DEFAULT_TEMPERATURE = 1.0
46
58
 
47
- # Default thinking budget tokens for Anthropic models
48
- DEFAULT_ANTHROPIC_THINKING_BUDGET_TOKENS = 2048
59
+ # =============================================================================
60
+ # Reminders
61
+ # =============================================================================
49
62
 
50
- # Tool call count threshold for todo reminder
51
- TODO_REMINDER_TOOL_CALL_THRESHOLD = 10
63
+ TODO_REMINDER_TOOL_CALL_THRESHOLD = 10 # Tool call count threshold for todo reminder
64
+ REMINDER_COOLDOWN_TURNS = 3 # Cooldown turns between reminder triggers
65
+ MEMORY_FILE_NAMES = ["CLAUDE.md", "AGENTS.md", "AGENT.md"] # Memory file names to search for
52
66
 
53
67
 
54
68
  # =============================================================================
55
- # Tool
69
+ # Tool - Read
56
70
  # =============================================================================
57
71
 
58
- # -- Read Tool --
59
- # Maximum characters per line before truncation
60
- READ_CHAR_LIMIT_PER_LINE = 2000
72
+ READ_CHAR_LIMIT_PER_LINE = 2000 # Maximum characters per line before truncation
73
+ READ_GLOBAL_LINE_CAP = _get_int_env("KLAUDE_READ_GLOBAL_LINE_CAP", 2000) # Maximum lines to read from a file
74
+ READ_MAX_CHARS = _get_int_env("KLAUDE_READ_MAX_CHARS", 50000) # Maximum total characters to read
75
+ READ_MAX_IMAGE_BYTES = _get_int_env("KLAUDE_READ_MAX_IMAGE_BYTES", 4 * 1024 * 1024) # Max image size (4MB)
76
+ IMAGE_OUTPUT_MAX_BYTES = _get_int_env("KLAUDE_IMAGE_OUTPUT_MAX_BYTES", 64 * 1024 * 1024) # Max decoded image (64MB)
77
+ BINARY_CHECK_SIZE = 8192 # Bytes to check for binary file detection
61
78
 
62
- # Maximum number of lines to read from a file
63
- # Can be overridden via KLAUDE_READ_GLOBAL_LINE_CAP environment variable
64
- READ_GLOBAL_LINE_CAP = _get_int_env("KLAUDE_READ_GLOBAL_LINE_CAP", 2000)
65
79
 
66
- # Maximum total characters to read (truncates beyond this limit)
67
- # Can be overridden via KLAUDE_READ_MAX_CHARS environment variable
68
- READ_MAX_CHARS = _get_int_env("KLAUDE_READ_MAX_CHARS", 50000)
80
+ # =============================================================================
81
+ # Tool - Bash / Shell
82
+ # =============================================================================
69
83
 
70
- # Maximum image file size in bytes (4MB)
71
- READ_MAX_IMAGE_BYTES = 4 * 1024 * 1024
84
+ BASH_DEFAULT_TIMEOUT_MS = 120000 # Default timeout for bash commands (milliseconds)
85
+ BASH_TERMINATE_TIMEOUT_SEC = 1.0 # Timeout before escalating to SIGKILL (seconds)
72
86
 
73
- # -- Bash Tool --
74
- # Default timeout for bash commands (milliseconds)
75
- BASH_DEFAULT_TIMEOUT_MS = 120000
76
87
 
77
- # -- Tool Output --
78
- # Maximum length for tool output before truncation
79
- TOOL_OUTPUT_MAX_LENGTH = 40000
88
+ # =============================================================================
89
+ # Tool - Web
90
+ # =============================================================================
80
91
 
81
- # Characters to show from the beginning of truncated output
82
- TOOL_OUTPUT_DISPLAY_HEAD = 10000
92
+ WEB_FETCH_DEFAULT_TIMEOUT_SEC = 30 # Default timeout for web fetch requests (seconds)
93
+ WEB_FETCH_USER_AGENT = "Mozilla/5.0 (compatible; KlaudeCode/1.0)" # User-Agent header for web requests
94
+ URL_FILENAME_MAX_LENGTH = 80 # Maximum length for extracting filename from URL
95
+ WEB_SEARCH_DEFAULT_MAX_RESULTS = 10 # Default number of search results
96
+ WEB_SEARCH_MAX_RESULTS_LIMIT = 20 # Maximum number of search results allowed
97
+ MERMAID_LIVE_PREFIX = "https://mermaid.live/view#pako:" # Mermaid.live URL prefix
83
98
 
84
- # Characters to show from the end of truncated output
85
- TOOL_OUTPUT_DISPLAY_TAIL = 10000
86
99
 
87
- # Directory for saving full truncated output
88
- TOOL_OUTPUT_TRUNCATION_DIR = "/tmp/klaude"
100
+ # =============================================================================
101
+ # Tool - Diff
102
+ # =============================================================================
103
+
104
+ DIFF_MAX_LINE_LENGTH_FOR_CHAR_DIFF = 2000 # Maximum line length for character-level diff
105
+ DIFF_DEFAULT_CONTEXT_LINES = 3 # Default number of context lines in diff output
89
106
 
90
107
 
91
108
  # =============================================================================
92
- # UI
109
+ # Tool - Output Truncation
93
110
  # =============================================================================
94
111
 
95
- # Width of line number prefix in diff display
96
- DIFF_PREFIX_WIDTH = 4
112
+ TOOL_OUTPUT_MAX_LENGTH = 40000 # Maximum length for tool output before truncation
113
+ TOOL_OUTPUT_DISPLAY_HEAD = 10000 # Characters to show from the beginning of truncated output
114
+ TOOL_OUTPUT_DISPLAY_TAIL = 10000 # Characters to show from the end of truncated output
115
+ TOOL_OUTPUT_TRUNCATION_DIR = "/tmp/klaude" # Directory for saving full truncated output
97
116
 
98
- # Maximum lines to show in diff output
99
- MAX_DIFF_LINES = 1000
100
117
 
101
- # Maximum length for invalid tool call display
102
- INVALID_TOOL_CALL_MAX_LENGTH = 500
118
+ # =============================================================================
119
+ # UI - Display
120
+ # =============================================================================
103
121
 
104
- # Maximum line length for truncated display output
105
- TRUNCATE_DISPLAY_MAX_LINE_LENGTH = 1000
122
+ TAB_EXPAND_WIDTH = 8 # Tab expansion width for text rendering
123
+ DIFF_PREFIX_WIDTH = 4 # Width of line number prefix in diff display
124
+ MAX_DIFF_LINES = 500 # Maximum lines to show in diff output
125
+ INVALID_TOOL_CALL_MAX_LENGTH = 200 # Maximum length for invalid tool call display
126
+ TRUNCATE_DISPLAY_MAX_LINE_LENGTH = 200 # Maximum line length for truncated display
127
+ TRUNCATE_DISPLAY_MAX_LINES = 4 # Maximum lines for truncated display
128
+ MIN_HIDDEN_LINES_FOR_INDICATOR = 5 # Minimum hidden lines before showing truncation indicator
129
+ SUB_AGENT_RESULT_MAX_LINES = 10 # Maximum lines for sub-agent result display
130
+ TRUNCATE_HEAD_MAX_LINES = 2 # Maximum lines for sub-agent error display
131
+ BASH_OUTPUT_PANEL_THRESHOLD = 10 # Bash output line threshold for CodePanel display
132
+ BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES = 2 # Max lines shown for heredoc / multiline string tokens in bash tool calls
133
+ URL_TRUNCATE_MAX_LENGTH = 400 # Maximum length for URL truncation in display
134
+ QUERY_DISPLAY_TRUNCATE_LENGTH = 80 # Maximum length for search query display
135
+ NOTIFY_COMPACT_LIMIT = 160 # Maximum length for notification body text
106
136
 
107
- # Maximum lines for truncated display output
108
- TRUNCATE_DISPLAY_MAX_LINES = 8
109
137
 
110
- # Maximum lines for sub-agent result display
111
- SUB_AGENT_RESULT_MAX_LINES = 50
138
+ # =============================================================================
139
+ # UI - Markdown Streaming
140
+ # =============================================================================
112
141
 
142
+ UI_REFRESH_RATE_FPS = 10 # UI refresh rate (frames per second)
143
+ CROP_ABOVE_LIVE_REFRESH_PER_SECOND = 4.0 # CropAboveLive default refresh rate
144
+ MARKDOWN_STREAM_LIVE_REPAINT_ENABLED = True # Enable live area for streaming markdown
145
+ STREAM_MAX_HEIGHT_SHRINK_RESET_LINES = 20 # Reset stream height ceiling after this shrinkage
146
+ MARKDOWN_LEFT_MARGIN = 2 # Left margin (columns) for markdown rendering
147
+ MARKDOWN_RIGHT_MARGIN = 2 # Right margin (columns) for markdown rendering
113
148
 
114
- # UI refresh rate (frames per second) for debounced content streaming
115
- UI_REFRESH_RATE_FPS = 10
116
149
 
117
- # Enable live area for streaming markdown (shows incomplete blocks being typed)
118
- # When False, only completed markdown blocks are displayed (more stable, less flicker)
119
- MARKDOWN_STREAM_LIVE_REPAINT_ENABLED = False
150
+ # =============================================================================
151
+ # UI - Spinner / Status
152
+ # =============================================================================
120
153
 
121
- # Number of lines to keep visible at bottom of markdown streaming window
122
- MARKDOWN_STREAM_LIVE_WINDOW = 6
154
+ STATUS_HINT_TEXT = " (esc to interrupt)" # Status hint text shown after spinner
155
+ STATUS_DEFAULT_TEXT = "Thinking …" # Default spinner status text
156
+ SPINNER_BREATH_PERIOD_SECONDS: float = 2.0 # Spinner breathing animation period (seconds)
157
+ STATUS_SHIMMER_PADDING = 10 # Horizontal padding for shimmer band position
158
+ STATUS_SHIMMER_BAND_HALF_WIDTH = 5.0 # Half-width of shimmer band in characters
159
+ STATUS_SHIMMER_ALPHA_SCALE = 0.7 # Scale factor for shimmer intensity
123
160
 
124
- # Left margin (columns) to reserve when rendering markdown
125
- MARKDOWN_LEFT_MARGIN = 2
126
161
 
127
- # Right margin (columns) to reserve when rendering markdown
128
- MARKDOWN_RIGHT_MARGIN = 2
162
+ # =============================================================================
163
+ # UI - Completion System
164
+ # =============================================================================
129
165
 
130
- # Status hint text shown after spinner status
131
- STATUS_HINT_TEXT = " (esc to interrupt)"
166
+ COMPLETER_DEBOUNCE_SEC = 0.25 # Debounce time for file path completion (seconds)
167
+ COMPLETER_CACHE_TTL_SEC = 60.0 # Cache TTL for completion results (seconds)
168
+ COMPLETER_CMD_TIMEOUT_SEC = 3.0 # Timeout for completion subprocess commands (seconds)
132
169
 
133
- # Default spinner status text when idle/thinking
134
- STATUS_DEFAULT_TEXT = "Thinking …"
135
170
 
136
- # Status shimmer animation
137
- # Horizontal padding used when computing shimmer band position
138
- STATUS_SHIMMER_PADDING = 10
139
- # Half-width of the shimmer band in characters
140
- STATUS_SHIMMER_BAND_HALF_WIDTH = 5.0
141
- # Scale factor applied to shimmer intensity when blending colors
142
- STATUS_SHIMMER_ALPHA_SCALE = 0.7
171
+ # =============================================================================
172
+ # Debug / Logging
173
+ # =============================================================================
143
174
 
144
- # Spinner breathing and shimmer animation period
145
- # Duration in seconds for one full breathe-in + breathe-out cycle (breathing)
146
- # and one full shimmer sweep across the text (shimmer)
147
- SPINNER_BREATH_PERIOD_SECONDS: float = 2.0
175
+ DEFAULT_DEBUG_LOG_DIR = Path.home() / ".klaude" / "logs" # Default debug log directory
176
+ DEFAULT_DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_DIR / "debug.log" # Default debug log file path
177
+ LOG_MAX_BYTES = 10 * 1024 * 1024 # Maximum log file size before rotation (10MB)
178
+ LOG_BACKUP_COUNT = 3 # Number of backup log files to keep
148
179
 
149
180
 
150
181
  # =============================================================================
151
- # Debug / Logging
182
+ # Project Paths
152
183
  # =============================================================================
153
184
 
154
- # Default debug log directory (user cache)
155
- DEFAULT_DEBUG_LOG_DIR = Path.home() / ".klaude" / "logs"
156
185
 
157
- # Default debug log file path (symlink to latest session)
158
- DEFAULT_DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_DIR / "debug.log"
186
+ def project_key_from_cwd() -> str:
187
+ """Derive the project key from the current working directory."""
188
+ return str(Path.cwd()).strip("/").replace("/", "-")
189
+
190
+
191
+ @dataclass(frozen=True)
192
+ class ProjectPaths:
193
+ """Path utilities for project-scoped storage."""
194
+
195
+ project_key: str
196
+
197
+ @property
198
+ def base_dir(self) -> Path:
199
+ return Path.home() / ".klaude" / "projects" / self.project_key
200
+
201
+ @property
202
+ def sessions_dir(self) -> Path:
203
+ return self.base_dir / "sessions"
204
+
205
+ @property
206
+ def exports_dir(self) -> Path:
207
+ return self.base_dir / "exports"
208
+
209
+ def session_dir(self, session_id: str) -> Path:
210
+ return self.sessions_dir / session_id
211
+
212
+ def images_dir(self, session_id: str) -> Path:
213
+ """Return the directory for storing session-scoped image artifacts."""
214
+ return self.session_dir(session_id) / "images"
159
215
 
160
- # Maximum log file size before rotation (10MB)
161
- LOG_MAX_BYTES = 10 * 1024 * 1024
216
+ def events_file(self, session_id: str) -> Path:
217
+ return self.session_dir(session_id) / "events.jsonl"
162
218
 
163
- # Number of backup log files to keep
164
- LOG_BACKUP_COUNT = 3
219
+ def meta_file(self, session_id: str) -> Path:
220
+ return self.session_dir(session_id) / "meta.json"
klaude_code/core/agent.py CHANGED
@@ -9,8 +9,8 @@ from klaude_code.core.reminders import Reminder, load_agent_reminders
9
9
  from klaude_code.core.task import SessionContext, TaskExecutionContext, TaskExecutor
10
10
  from klaude_code.core.tool import build_todo_context, get_registry, load_agent_tools
11
11
  from klaude_code.llm import LLMClientABC
12
- from klaude_code.protocol import events, llm_param, model, tools
13
- from klaude_code.protocol.model import UserInputPayload
12
+ from klaude_code.protocol import events, llm_param, tools
13
+ from klaude_code.protocol.message import UserInputPayload
14
14
  from klaude_code.session import Session
15
15
  from klaude_code.trace import DebugType, log_debug
16
16
 
@@ -82,21 +82,12 @@ class Agent:
82
82
  self.session.model_name = profile.llm_client.model_name
83
83
 
84
84
  def cancel(self) -> Iterable[events.Event]:
85
- """Handle agent cancellation and persist an interrupt marker and tool cancellations.
86
-
87
- - Appends an `InterruptItem` into the session history so interruptions are reflected
88
- in persisted conversation logs.
89
- - For any tool calls that are pending or in-progress in the current task, delegate to
90
- the active TaskExecutor to append synthetic ToolResultItem entries with error status
91
- to indicate cancellation.
92
- """
85
+ """Handle agent cancellation and tool cancellations."""
93
86
  # First, cancel any running task so it stops emitting events.
94
87
  if self._current_task is not None:
95
88
  yield from self._current_task.cancel()
96
89
  self._current_task = None
97
90
 
98
- # Record an interrupt marker in the session history
99
- self.session.append_history([model.InterruptItem()])
100
91
  log_debug(
101
92
  f"Session {self.session.id} interrupted",
102
93
  style="yellow",