klaude-code 1.8.0__py3-none-any.whl → 2.0.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 (142) hide show
  1. klaude_code/auth/base.py +97 -0
  2. klaude_code/auth/claude/__init__.py +6 -0
  3. klaude_code/auth/claude/exceptions.py +9 -0
  4. klaude_code/auth/claude/oauth.py +172 -0
  5. klaude_code/auth/claude/token_manager.py +26 -0
  6. klaude_code/auth/codex/token_manager.py +10 -50
  7. klaude_code/cli/auth_cmd.py +127 -46
  8. klaude_code/cli/config_cmd.py +4 -2
  9. klaude_code/cli/cost_cmd.py +14 -9
  10. klaude_code/cli/list_model.py +248 -200
  11. klaude_code/cli/main.py +1 -1
  12. klaude_code/cli/runtime.py +7 -5
  13. klaude_code/cli/self_update.py +1 -1
  14. klaude_code/cli/session_cmd.py +1 -1
  15. klaude_code/command/clear_cmd.py +6 -2
  16. klaude_code/command/command_abc.py +2 -2
  17. klaude_code/command/debug_cmd.py +4 -4
  18. klaude_code/command/export_cmd.py +2 -2
  19. klaude_code/command/export_online_cmd.py +12 -12
  20. klaude_code/command/fork_session_cmd.py +29 -23
  21. klaude_code/command/help_cmd.py +4 -4
  22. klaude_code/command/model_cmd.py +4 -4
  23. klaude_code/command/model_select.py +1 -1
  24. klaude_code/command/prompt-commit.md +82 -0
  25. klaude_code/command/prompt_command.py +3 -3
  26. klaude_code/command/refresh_cmd.py +2 -2
  27. klaude_code/command/registry.py +7 -5
  28. klaude_code/command/release_notes_cmd.py +4 -4
  29. klaude_code/command/resume_cmd.py +15 -11
  30. klaude_code/command/status_cmd.py +4 -4
  31. klaude_code/command/terminal_setup_cmd.py +8 -8
  32. klaude_code/command/thinking_cmd.py +4 -4
  33. klaude_code/config/assets/builtin_config.yaml +52 -3
  34. klaude_code/config/builtin_config.py +16 -5
  35. klaude_code/config/config.py +31 -7
  36. klaude_code/config/thinking.py +4 -4
  37. klaude_code/const.py +146 -91
  38. klaude_code/core/agent.py +3 -12
  39. klaude_code/core/executor.py +21 -13
  40. klaude_code/core/manager/sub_agent_manager.py +71 -7
  41. klaude_code/core/prompt.py +1 -1
  42. klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
  43. klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
  44. klaude_code/core/reminders.py +88 -69
  45. klaude_code/core/task.py +44 -45
  46. klaude_code/core/tool/file/apply_patch_tool.py +9 -9
  47. klaude_code/core/tool/file/diff_builder.py +3 -5
  48. klaude_code/core/tool/file/edit_tool.py +23 -23
  49. klaude_code/core/tool/file/move_tool.py +43 -43
  50. klaude_code/core/tool/file/read_tool.py +44 -39
  51. klaude_code/core/tool/file/write_tool.py +14 -14
  52. klaude_code/core/tool/report_back_tool.py +4 -4
  53. klaude_code/core/tool/shell/bash_tool.py +23 -23
  54. klaude_code/core/tool/skill/skill_tool.py +7 -7
  55. klaude_code/core/tool/sub_agent_tool.py +38 -9
  56. klaude_code/core/tool/todo/todo_write_tool.py +8 -8
  57. klaude_code/core/tool/todo/update_plan_tool.py +6 -6
  58. klaude_code/core/tool/tool_abc.py +2 -2
  59. klaude_code/core/tool/tool_context.py +27 -0
  60. klaude_code/core/tool/tool_runner.py +88 -42
  61. klaude_code/core/tool/truncation.py +38 -20
  62. klaude_code/core/tool/web/mermaid_tool.py +6 -7
  63. klaude_code/core/tool/web/web_fetch_tool.py +68 -30
  64. klaude_code/core/tool/web/web_search_tool.py +15 -17
  65. klaude_code/core/turn.py +120 -73
  66. klaude_code/llm/anthropic/client.py +104 -44
  67. klaude_code/llm/anthropic/input.py +116 -108
  68. klaude_code/llm/bedrock/client.py +8 -5
  69. klaude_code/llm/claude/__init__.py +3 -0
  70. klaude_code/llm/claude/client.py +105 -0
  71. klaude_code/llm/client.py +4 -3
  72. klaude_code/llm/codex/client.py +16 -10
  73. klaude_code/llm/google/client.py +122 -60
  74. klaude_code/llm/google/input.py +94 -108
  75. klaude_code/llm/image.py +123 -0
  76. klaude_code/llm/input_common.py +136 -189
  77. klaude_code/llm/openai_compatible/client.py +17 -7
  78. klaude_code/llm/openai_compatible/input.py +36 -66
  79. klaude_code/llm/openai_compatible/stream.py +119 -67
  80. klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
  81. klaude_code/llm/openrouter/client.py +34 -9
  82. klaude_code/llm/openrouter/input.py +63 -64
  83. klaude_code/llm/openrouter/reasoning.py +22 -24
  84. klaude_code/llm/registry.py +20 -15
  85. klaude_code/llm/responses/client.py +107 -45
  86. klaude_code/llm/responses/input.py +115 -98
  87. klaude_code/llm/usage.py +52 -25
  88. klaude_code/protocol/__init__.py +1 -0
  89. klaude_code/protocol/events.py +16 -12
  90. klaude_code/protocol/llm_param.py +22 -3
  91. klaude_code/protocol/message.py +250 -0
  92. klaude_code/protocol/model.py +94 -281
  93. klaude_code/protocol/op.py +2 -2
  94. klaude_code/protocol/sub_agent/__init__.py +2 -2
  95. klaude_code/protocol/sub_agent/explore.py +10 -0
  96. klaude_code/protocol/sub_agent/image_gen.py +119 -0
  97. klaude_code/protocol/sub_agent/task.py +10 -0
  98. klaude_code/protocol/sub_agent/web.py +10 -0
  99. klaude_code/session/codec.py +6 -6
  100. klaude_code/session/export.py +261 -62
  101. klaude_code/session/selector.py +7 -24
  102. klaude_code/session/session.py +125 -53
  103. klaude_code/session/store.py +5 -32
  104. klaude_code/session/templates/export_session.html +1 -1
  105. klaude_code/session/templates/mermaid_viewer.html +1 -1
  106. klaude_code/trace/log.py +11 -6
  107. klaude_code/ui/core/input.py +1 -1
  108. klaude_code/ui/core/stage_manager.py +1 -8
  109. klaude_code/ui/modes/debug/display.py +2 -2
  110. klaude_code/ui/modes/repl/clipboard.py +2 -2
  111. klaude_code/ui/modes/repl/completers.py +18 -10
  112. klaude_code/ui/modes/repl/event_handler.py +136 -127
  113. klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
  114. klaude_code/ui/modes/repl/key_bindings.py +1 -1
  115. klaude_code/ui/modes/repl/renderer.py +107 -15
  116. klaude_code/ui/renderers/assistant.py +2 -2
  117. klaude_code/ui/renderers/common.py +65 -7
  118. klaude_code/ui/renderers/developer.py +7 -6
  119. klaude_code/ui/renderers/diffs.py +11 -11
  120. klaude_code/ui/renderers/mermaid_viewer.py +49 -2
  121. klaude_code/ui/renderers/metadata.py +39 -31
  122. klaude_code/ui/renderers/sub_agent.py +57 -16
  123. klaude_code/ui/renderers/thinking.py +37 -2
  124. klaude_code/ui/renderers/tools.py +180 -165
  125. klaude_code/ui/rich/live.py +3 -1
  126. klaude_code/ui/rich/markdown.py +39 -7
  127. klaude_code/ui/rich/quote.py +76 -1
  128. klaude_code/ui/rich/status.py +14 -8
  129. klaude_code/ui/rich/theme.py +13 -6
  130. klaude_code/ui/terminal/image.py +34 -0
  131. klaude_code/ui/terminal/notifier.py +2 -1
  132. klaude_code/ui/terminal/progress_bar.py +4 -4
  133. klaude_code/ui/terminal/selector.py +22 -4
  134. klaude_code/ui/utils/common.py +55 -0
  135. {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/METADATA +28 -6
  136. klaude_code-2.0.0.dist-info/RECORD +229 -0
  137. klaude_code/command/prompt-jj-describe.md +0 -32
  138. klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -22
  139. klaude_code/protocol/sub_agent/oracle.py +0 -91
  140. klaude_code-1.8.0.dist-info/RECORD +0 -219
  141. {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/WHEEL +0 -0
  142. {klaude_code-1.8.0.dist-info → klaude_code-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -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}
@@ -259,7 +272,6 @@ provider_list:
259
272
  base_url: https://api.deepseek.com/anthropic
260
273
  model_list:
261
274
  - model_name: deepseek
262
- provider: deepseek
263
275
  model_params:
264
276
  model: deepseek-reasoner
265
277
  context_limit: 128000
@@ -290,14 +302,51 @@ provider_list:
290
302
  cache_read: 1.0
291
303
  currency: CNY
292
304
 
305
+ - provider_name: claude-max
306
+ protocol: claude_oauth
307
+ model_list:
308
+ - model_name: sonnet@claude-max
309
+ model_params:
310
+ model: claude-sonnet-4-5-20250929
311
+ context_limit: 200000
312
+ cost:
313
+ input: 3.0
314
+ output: 15.0
315
+ cache_read: 0.3
316
+ cache_write: 3.75
317
+ - model_name: opus@claude-max
318
+ model_params:
319
+ model: claude-opus-4-5-20251101
320
+ context_limit: 200000
321
+ verbosity: high
322
+ thinking:
323
+ type: enabled
324
+ budget_tokens: 2048
325
+ cost:
326
+ input: 5.0
327
+ output: 25.0
328
+ cache_read: 0.5
329
+ cache_write: 6.25
330
+ - model_name: haiku@claude-max
331
+ model_params:
332
+ model: claude-haiku-4-5-20251001
333
+ context_limit: 200000
334
+ cost:
335
+ input: 1.0
336
+ output: 5.0
337
+ cache_read: 0.1
338
+ cache_write: 1.25
339
+
293
340
  - provider_name: codex
294
- protocol: codex
341
+ protocol: codex_oauth
295
342
  model_list:
296
343
  - model_name: gpt-5.2-codex
297
- provider: codex
298
344
  model_params:
299
345
  model: gpt-5.2-codex
300
346
  thinking:
301
347
  reasoning_effort: medium
302
348
  context_limit: 400000
303
349
  max_tokens: 128000
350
+
351
+ sub_agent_models:
352
+ 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
@@ -81,14 +85,23 @@ class ProviderConfig(llm_param.LLMConfigProviderParameter):
81
85
  """
82
86
  from klaude_code.protocol.llm_param import LLMClientProtocol
83
87
 
84
- if self.protocol == LLMClientProtocol.CODEX:
88
+ if self.protocol == LLMClientProtocol.CODEX_OAUTH:
85
89
  # Codex uses OAuth authentication, not API key
86
90
  from klaude_code.auth.codex.token_manager import CodexTokenManager
87
91
 
88
92
  token_manager = CodexTokenManager()
89
93
  state = token_manager.get_state()
90
- # Consider available if logged in and token not expired
91
- return state is None or state.is_expired()
94
+ # Consider available if logged in. Token refresh happens on-demand.
95
+ return state is None
96
+
97
+ if self.protocol == LLMClientProtocol.CLAUDE_OAUTH:
98
+ # Claude uses OAuth authentication, not API key
99
+ from klaude_code.auth.claude.token_manager import ClaudeTokenManager
100
+
101
+ token_manager = ClaudeTokenManager()
102
+ state = token_manager.get_state()
103
+ # Consider available if logged in. Token refresh happens on-demand.
104
+ return state is None
92
105
 
93
106
  if self.protocol == LLMClientProtocol.BEDROCK:
94
107
  # Bedrock uses AWS credentials, not API key. Region is always required.
@@ -182,7 +195,17 @@ class Config(BaseModel):
182
195
  for provider in self.provider_list:
183
196
  # Resolve ${ENV_VAR} syntax for api_key
184
197
  api_key = provider.get_resolved_api_key()
185
- if not api_key:
198
+
199
+ # Some protocols do not use API keys for authentication.
200
+ if (
201
+ provider.protocol
202
+ not in {
203
+ llm_param.LLMClientProtocol.CODEX_OAUTH,
204
+ llm_param.LLMClientProtocol.CLAUDE_OAUTH,
205
+ llm_param.LLMClientProtocol.BEDROCK,
206
+ }
207
+ and not api_key
208
+ ):
186
209
  continue
187
210
  for model in provider.model_list:
188
211
  if model.model_name == model_name:
@@ -243,7 +266,7 @@ def get_example_config() -> UserConfig:
243
266
  """Generate example config for user reference (will be commented out)."""
244
267
  return UserConfig(
245
268
  main_model="opus",
246
- sub_agent_models={"explore": "haiku", "oracle": "gpt-5.2", "webagent": "sonnet", "task": "sonnet"},
269
+ sub_agent_models={"explore": "haiku", "webagent": "sonnet", "task": "sonnet"},
247
270
  provider_list=[
248
271
  UserProviderConfig(
249
272
  provider_name="my-provider",
@@ -275,7 +298,8 @@ def _get_builtin_config() -> Config:
275
298
  # Re-validate to ensure compatibility with current ProviderConfig class
276
299
  # (needed for tests that may monkeypatch the class)
277
300
  providers = [ProviderConfig.model_validate(p.model_dump()) for p in get_builtin_provider_configs()]
278
- 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)
279
303
 
280
304
 
281
305
  def _merge_provider(builtin: ProviderConfig, user: UserProviderConfig) -> ProviderConfig:
@@ -91,12 +91,12 @@ def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
91
91
 
92
92
  protocol = config.protocol
93
93
 
94
- if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX):
94
+ if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX_OAUTH):
95
95
  if thinking.reasoning_effort:
96
96
  return f"reasoning_effort={thinking.reasoning_effort}"
97
97
  return "not set"
98
98
 
99
- if protocol == llm_param.LLMClientProtocol.ANTHROPIC:
99
+ if protocol in (llm_param.LLMClientProtocol.ANTHROPIC, llm_param.LLMClientProtocol.CLAUDE_OAUTH):
100
100
  if thinking.type == "disabled":
101
101
  return "off"
102
102
  if thinking.type == "enabled":
@@ -201,7 +201,7 @@ def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPi
201
201
  model_name = config.model
202
202
  thinking = config.thinking
203
203
 
204
- if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX):
204
+ if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX_OAUTH):
205
205
  levels = get_levels_for_responses(model_name)
206
206
  return ThinkingPickerData(
207
207
  options=_build_effort_options(levels),
@@ -209,7 +209,7 @@ def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPi
209
209
  current_value=_get_current_effort_value(thinking),
210
210
  )
211
211
 
212
- if protocol == llm_param.LLMClientProtocol.ANTHROPIC:
212
+ if protocol in (llm_param.LLMClientProtocol.ANTHROPIC, llm_param.LLMClientProtocol.CLAUDE_OAUTH):
213
213
  return ThinkingPickerData(
214
214
  options=_build_budget_options(),
215
215
  message="Select thinking level:",
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,197 @@ 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 = 500 # Maximum length for invalid tool call display
126
+ TRUNCATE_DISPLAY_MAX_LINE_LENGTH = 1000 # Maximum line length for truncated display
127
+ TRUNCATE_DISPLAY_MAX_LINES = 6 # 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
+ URL_TRUNCATE_MAX_LENGTH = 400 # Maximum length for URL truncation in display
133
+ QUERY_DISPLAY_TRUNCATE_LENGTH = 80 # Maximum length for search query display
134
+ NOTIFY_COMPACT_LIMIT = 160 # Maximum length for notification body text
106
135
 
107
- # Maximum lines for truncated display output
108
- TRUNCATE_DISPLAY_MAX_LINES = 8
109
136
 
110
- # Maximum lines for sub-agent result display
111
- SUB_AGENT_RESULT_MAX_LINES = 50
137
+ # =============================================================================
138
+ # UI - Markdown Streaming
139
+ # =============================================================================
112
140
 
141
+ UI_REFRESH_RATE_FPS = 10 # UI refresh rate (frames per second)
142
+ CROP_ABOVE_LIVE_REFRESH_PER_SECOND = 4.0 # CropAboveLive default refresh rate
143
+ MARKDOWN_STREAM_LIVE_REPAINT_ENABLED = True # Enable live area for streaming markdown
144
+ STREAM_MAX_HEIGHT_SHRINK_RESET_LINES = 20 # Reset stream height ceiling after this shrinkage
145
+ MARKDOWN_LEFT_MARGIN = 2 # Left margin (columns) for markdown rendering
146
+ MARKDOWN_RIGHT_MARGIN = 2 # Right margin (columns) for markdown rendering
113
147
 
114
- # UI refresh rate (frames per second) for debounced content streaming
115
- UI_REFRESH_RATE_FPS = 10
116
148
 
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
149
+ # =============================================================================
150
+ # UI - Spinner / Status
151
+ # =============================================================================
120
152
 
121
- # Number of lines to keep visible at bottom of markdown streaming window
122
- MARKDOWN_STREAM_LIVE_WINDOW = 6
153
+ STATUS_HINT_TEXT = " (esc to interrupt)" # Status hint text shown after spinner
154
+ STATUS_DEFAULT_TEXT = "Thinking …" # Default spinner status text
155
+ SPINNER_BREATH_PERIOD_SECONDS: float = 2.0 # Spinner breathing animation period (seconds)
156
+ STATUS_SHIMMER_PADDING = 10 # Horizontal padding for shimmer band position
157
+ STATUS_SHIMMER_BAND_HALF_WIDTH = 5.0 # Half-width of shimmer band in characters
158
+ STATUS_SHIMMER_ALPHA_SCALE = 0.7 # Scale factor for shimmer intensity
123
159
 
124
- # Left margin (columns) to reserve when rendering markdown
125
- MARKDOWN_LEFT_MARGIN = 2
126
160
 
127
- # Right margin (columns) to reserve when rendering markdown
128
- MARKDOWN_RIGHT_MARGIN = 2
161
+ # =============================================================================
162
+ # UI - Completion System
163
+ # =============================================================================
129
164
 
130
- # Status hint text shown after spinner status
131
- STATUS_HINT_TEXT = " (esc to interrupt)"
165
+ COMPLETER_DEBOUNCE_SEC = 0.25 # Debounce time for file path completion (seconds)
166
+ COMPLETER_CACHE_TTL_SEC = 60.0 # Cache TTL for completion results (seconds)
167
+ COMPLETER_CMD_TIMEOUT_SEC = 3.0 # Timeout for completion subprocess commands (seconds)
132
168
 
133
- # Default spinner status text when idle/thinking
134
- STATUS_DEFAULT_TEXT = "Thinking …"
135
169
 
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
170
+ # =============================================================================
171
+ # Debug / Logging
172
+ # =============================================================================
143
173
 
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
174
+ DEFAULT_DEBUG_LOG_DIR = Path.home() / ".klaude" / "logs" # Default debug log directory
175
+ DEFAULT_DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_DIR / "debug.log" # Default debug log file path
176
+ LOG_MAX_BYTES = 10 * 1024 * 1024 # Maximum log file size before rotation (10MB)
177
+ LOG_BACKUP_COUNT = 3 # Number of backup log files to keep
148
178
 
149
179
 
150
180
  # =============================================================================
151
- # Debug / Logging
181
+ # Project Paths
152
182
  # =============================================================================
153
183
 
154
- # Default debug log directory (user cache)
155
- DEFAULT_DEBUG_LOG_DIR = Path.home() / ".klaude" / "logs"
156
184
 
157
- # Default debug log file path (symlink to latest session)
158
- DEFAULT_DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_DIR / "debug.log"
185
+ def project_key_from_cwd() -> str:
186
+ """Derive the project key from the current working directory."""
187
+ return str(Path.cwd()).strip("/").replace("/", "-")
188
+
189
+
190
+ @dataclass(frozen=True)
191
+ class ProjectPaths:
192
+ """Path utilities for project-scoped storage."""
193
+
194
+ project_key: str
195
+
196
+ @property
197
+ def base_dir(self) -> Path:
198
+ return Path.home() / ".klaude" / "projects" / self.project_key
199
+
200
+ @property
201
+ def sessions_dir(self) -> Path:
202
+ return self.base_dir / "sessions"
203
+
204
+ @property
205
+ def exports_dir(self) -> Path:
206
+ return self.base_dir / "exports"
207
+
208
+ def session_dir(self, session_id: str) -> Path:
209
+ return self.sessions_dir / session_id
210
+
211
+ def images_dir(self, session_id: str) -> Path:
212
+ """Return the directory for storing session-scoped image artifacts."""
213
+ return self.session_dir(session_id) / "images"
159
214
 
160
- # Maximum log file size before rotation (10MB)
161
- LOG_MAX_BYTES = 10 * 1024 * 1024
215
+ def events_file(self, session_id: str) -> Path:
216
+ return self.session_dir(session_id) / "events.jsonl"
162
217
 
163
- # Number of backup log files to keep
164
- LOG_BACKUP_COUNT = 3
218
+ def meta_file(self, session_id: str) -> Path:
219
+ 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",