klaude-code 1.2.16__py3-none-any.whl → 1.2.18__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 +3 -9
  4. klaude_code/cli/runtime.py +20 -13
  5. klaude_code/command/__init__.py +7 -1
  6. klaude_code/command/clear_cmd.py +2 -7
  7. klaude_code/command/command_abc.py +33 -5
  8. klaude_code/command/debug_cmd.py +79 -0
  9. klaude_code/command/diff_cmd.py +2 -6
  10. klaude_code/command/export_cmd.py +7 -7
  11. klaude_code/command/export_online_cmd.py +145 -0
  12. klaude_code/command/help_cmd.py +4 -9
  13. klaude_code/command/model_cmd.py +10 -6
  14. klaude_code/command/prompt_command.py +2 -6
  15. klaude_code/command/refresh_cmd.py +2 -7
  16. klaude_code/command/registry.py +2 -4
  17. klaude_code/command/release_notes_cmd.py +2 -6
  18. klaude_code/command/status_cmd.py +2 -7
  19. klaude_code/command/terminal_setup_cmd.py +2 -6
  20. klaude_code/command/thinking_cmd.py +13 -8
  21. klaude_code/config/config.py +16 -17
  22. klaude_code/config/select_model.py +81 -5
  23. klaude_code/const/__init__.py +1 -1
  24. klaude_code/core/executor.py +236 -109
  25. klaude_code/core/manager/__init__.py +2 -4
  26. klaude_code/core/manager/sub_agent_manager.py +1 -1
  27. klaude_code/core/prompts/prompt-claude-code.md +1 -1
  28. klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -1
  29. klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
  30. klaude_code/core/reminders.py +9 -35
  31. klaude_code/core/task.py +8 -0
  32. klaude_code/core/tool/__init__.py +2 -0
  33. klaude_code/core/tool/file/read_tool.py +38 -10
  34. klaude_code/core/tool/report_back_tool.py +28 -2
  35. klaude_code/core/tool/shell/bash_tool.py +22 -2
  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/tool/web/web_search_tool.md +23 -0
  41. klaude_code/core/tool/web/web_search_tool.py +126 -0
  42. klaude_code/core/turn.py +28 -0
  43. klaude_code/protocol/commands.py +2 -0
  44. klaude_code/protocol/events.py +8 -0
  45. klaude_code/protocol/sub_agent/__init__.py +1 -1
  46. klaude_code/protocol/sub_agent/explore.py +1 -1
  47. klaude_code/protocol/sub_agent/web.py +79 -0
  48. klaude_code/protocol/tools.py +1 -0
  49. klaude_code/session/session.py +2 -2
  50. klaude_code/session/templates/export_session.html +123 -37
  51. klaude_code/trace/__init__.py +20 -2
  52. klaude_code/ui/modes/repl/completers.py +19 -2
  53. klaude_code/ui/modes/repl/event_handler.py +44 -15
  54. klaude_code/ui/modes/repl/renderer.py +3 -3
  55. klaude_code/ui/renderers/metadata.py +2 -4
  56. klaude_code/ui/renderers/sub_agent.py +14 -10
  57. klaude_code/ui/renderers/thinking.py +24 -8
  58. klaude_code/ui/renderers/tools.py +83 -20
  59. klaude_code/ui/rich/code_panel.py +112 -0
  60. klaude_code/ui/rich/markdown.py +3 -4
  61. klaude_code/ui/rich/status.py +30 -6
  62. klaude_code/ui/rich/theme.py +10 -1
  63. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/METADATA +126 -25
  64. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/RECORD +67 -63
  65. klaude_code/core/manager/agent_manager.py +0 -132
  66. klaude_code/core/prompts/prompt-sub-agent-webfetch.md +0 -46
  67. klaude_code/protocol/sub_agent/web_fetch.py +0 -74
  68. /klaude_code/{config → cli}/list_model.py +0 -0
  69. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/WHEEL +0 -0
  70. {klaude_code-1.2.16.dist-info → klaude_code-1.2.18.dist-info}/entry_points.txt +0 -0
@@ -1,14 +1,12 @@
1
1
  from importlib.resources import files
2
2
  from typing import TYPE_CHECKING
3
3
 
4
- from klaude_code.command.command_abc import CommandResult, InputAction
4
+ from klaude_code.command.command_abc import Agent, CommandResult, InputAction
5
5
  from klaude_code.command.prompt_command import PromptCommand
6
6
  from klaude_code.protocol import commands, events, model
7
7
  from klaude_code.trace import log_debug
8
8
 
9
9
  if TYPE_CHECKING:
10
- from klaude_code.core.agent import Agent
11
-
12
10
  from .command_abc import CommandABC
13
11
 
14
12
  _COMMANDS: dict[commands.CommandName | str, "CommandABC"] = {}
@@ -50,7 +48,7 @@ def is_slash_command_name(name: str) -> bool:
50
48
  return name in _COMMANDS
51
49
 
52
50
 
53
- async def dispatch_command(raw: str, agent: "Agent") -> CommandResult:
51
+ async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
54
52
  _ensure_commands_loaded()
55
53
  # Detect command name
56
54
  if not raw.startswith("/"):
@@ -1,12 +1,8 @@
1
1
  from pathlib import Path
2
- from typing import TYPE_CHECKING
3
2
 
4
- from klaude_code.command.command_abc import CommandABC, CommandResult
3
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
5
4
  from klaude_code.protocol import commands, events, model
6
5
 
7
- if TYPE_CHECKING:
8
- from klaude_code.core.agent import Agent
9
-
10
6
 
11
7
  def _read_changelog() -> str:
12
8
  """Read CHANGELOG.md from project root."""
@@ -72,7 +68,7 @@ class ReleaseNotesCommand(CommandABC):
72
68
  def summary(self) -> str:
73
69
  return "Show the latest release notes"
74
70
 
75
- async def run(self, raw: str, agent: "Agent") -> CommandResult:
71
+ async def run(self, raw: str, agent: Agent) -> CommandResult:
76
72
  changelog = _read_changelog()
77
73
  content = _extract_releases(changelog, count=10)
78
74
 
@@ -1,12 +1,7 @@
1
- from typing import TYPE_CHECKING
2
-
3
- from klaude_code.command.command_abc import CommandABC, CommandResult
1
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
4
2
  from klaude_code.protocol import commands, events, model
5
3
  from klaude_code.session.session import Session
6
4
 
7
- if TYPE_CHECKING:
8
- from klaude_code.core.agent import Agent
9
-
10
5
 
11
6
  class AggregatedUsage(model.BaseModel):
12
7
  """Aggregated usage statistics including per-model breakdown."""
@@ -137,7 +132,7 @@ class StatusCommand(CommandABC):
137
132
  def summary(self) -> str:
138
133
  return "Show session usage statistics"
139
134
 
140
- async def run(self, raw: str, agent: "Agent") -> CommandResult:
135
+ async def run(self, raw: str, agent: Agent) -> CommandResult:
141
136
  session = agent.session
142
137
  aggregated = accumulate_session_usage(session)
143
138
 
@@ -1,14 +1,10 @@
1
1
  import os
2
2
  import subprocess
3
3
  from pathlib import Path
4
- from typing import TYPE_CHECKING
5
4
 
6
- from klaude_code.command.command_abc import CommandABC, CommandResult
5
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
7
6
  from klaude_code.protocol import commands, events, model
8
7
 
9
- if TYPE_CHECKING:
10
- from klaude_code.core.agent import Agent
11
-
12
8
 
13
9
  class TerminalSetupCommand(CommandABC):
14
10
  """Setup shift+enter newline functionality in terminal"""
@@ -25,7 +21,7 @@ class TerminalSetupCommand(CommandABC):
25
21
  def is_interactive(self) -> bool:
26
22
  return False
27
23
 
28
- async def run(self, raw: str, agent: "Agent") -> CommandResult:
24
+ async def run(self, raw: str, agent: Agent) -> CommandResult:
29
25
  term_program = os.environ.get("TERM_PROGRAM", "").lower()
30
26
 
31
27
  try:
@@ -1,18 +1,14 @@
1
1
  import asyncio
2
- from typing import TYPE_CHECKING
3
2
 
4
3
  import questionary
5
4
 
6
- from klaude_code.command.command_abc import CommandABC, CommandResult
5
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
7
6
  from klaude_code.protocol import commands, events, llm_param, model
8
7
 
9
- if TYPE_CHECKING:
10
- from klaude_code.core.agent import Agent
11
-
12
-
13
8
  # Thinking level options for different protocols
14
9
  RESPONSES_LEVELS = ["minimal", "low", "medium", "high"]
15
10
  RESPONSES_GPT51_LEVELS = ["none", "minimal", "low", "medium", "high"]
11
+ RESPONSES_GPT52_LEVELS = ["none", "minimal", "low", "medium", "high", "xhigh"]
16
12
  RESPONSES_CODEX_MAX_LEVELS = ["medium", "high", "xhigh"]
17
13
 
18
14
  ANTHROPIC_LEVELS: list[tuple[str, int | None]] = [
@@ -35,7 +31,14 @@ def _is_gpt51_model(model_name: str | None) -> bool:
35
31
  """Check if the model is GPT-5.1."""
36
32
  if not model_name:
37
33
  return False
38
- return model_name.lower() in ["gpt5.1", "openai/gpt-5.1", "gpt-5.1-codex-2025-11-13"]
34
+ return model_name.lower() in ["gpt-5.1", "openai/gpt-5.1", "gpt-5.1-codex-2025-11-13"]
35
+
36
+
37
+ def _is_gpt52_model(model_name: str | None) -> bool:
38
+ """Check if the model is GPT-5.2."""
39
+ if not model_name:
40
+ return False
41
+ return model_name.lower() in ["gpt-5.2", "openai/gpt-5.2"]
39
42
 
40
43
 
41
44
  def _is_codex_max_model(model_name: str | None) -> bool:
@@ -49,6 +52,8 @@ def _get_levels_for_responses(model_name: str | None) -> list[str]:
49
52
  """Get thinking levels for responses protocol."""
50
53
  if _is_codex_max_model(model_name):
51
54
  return RESPONSES_CODEX_MAX_LEVELS
55
+ if _is_gpt52_model(model_name):
56
+ return RESPONSES_GPT52_LEVELS
52
57
  if _is_gpt51_model(model_name):
53
58
  return RESPONSES_GPT51_LEVELS
54
59
  return RESPONSES_LEVELS
@@ -164,7 +169,7 @@ class ThinkingCommand(CommandABC):
164
169
  def is_interactive(self) -> bool:
165
170
  return True
166
171
 
167
- async def run(self, raw: str, agent: "Agent") -> CommandResult:
172
+ async def run(self, raw: str, agent: Agent) -> CommandResult:
168
173
  if not agent.profile:
169
174
  return self._no_change_result(agent, "No profile configured")
170
175
 
@@ -79,8 +79,8 @@ class Config(BaseModel):
79
79
 
80
80
  def get_example_config() -> Config:
81
81
  return Config(
82
- main_model="gpt-5.1",
83
- sub_agent_models={"explore": "haiku", "oracle": "gpt-5.1-high"},
82
+ main_model="opus",
83
+ sub_agent_models={"explore": "haiku", "oracle": "gpt-5.1", "webagent": "haiku", "task": "opus"},
84
84
  provider_list=[
85
85
  llm_param.LLMConfigProviderParameter(
86
86
  provider_name="openai",
@@ -93,6 +93,11 @@ def get_example_config() -> Config:
93
93
  protocol=llm_param.LLMClientProtocol.OPENROUTER,
94
94
  api_key="your-openrouter-api-key",
95
95
  ),
96
+ llm_param.LLMConfigProviderParameter(
97
+ provider_name="anthropic",
98
+ protocol=llm_param.LLMClientProtocol.ANTHROPIC,
99
+ api_key="your-anthropic-api-key",
100
+ ),
96
101
  ],
97
102
  model_list=[
98
103
  ModelConfig(
@@ -100,31 +105,25 @@ def get_example_config() -> Config:
100
105
  provider="openai",
101
106
  model_params=llm_param.LLMConfigModelParameter(
102
107
  model="gpt-5.1-2025-11-13",
103
- max_tokens=32000,
104
108
  verbosity="medium",
105
109
  thinking=llm_param.Thinking(
106
- reasoning_effort="medium",
110
+ reasoning_effort="high",
107
111
  reasoning_summary="auto",
108
- type="enabled",
109
- budget_tokens=None,
110
112
  ),
111
- context_limit=368000,
113
+ context_limit=400000,
112
114
  ),
113
115
  ),
114
116
  ModelConfig(
115
- model_name="gpt-5.1-high",
116
- provider="openai",
117
+ model_name="opus",
118
+ provider="anthropic",
117
119
  model_params=llm_param.LLMConfigModelParameter(
118
- model="gpt-5.1-2025-11-13",
119
- max_tokens=32000,
120
- verbosity="medium",
120
+ model="claude-opus-4-5-20251101",
121
+ verbosity="high",
121
122
  thinking=llm_param.Thinking(
122
- reasoning_effort="high",
123
- reasoning_summary="auto",
124
123
  type="enabled",
125
- budget_tokens=None,
124
+ budget_tokens=31999,
126
125
  ),
127
- context_limit=368000,
126
+ context_limit=200000,
128
127
  ),
129
128
  ),
130
129
  ModelConfig(
@@ -136,7 +135,7 @@ def get_example_config() -> Config:
136
135
  provider_routing=llm_param.OpenRouterProviderRouting(
137
136
  sort="throughput",
138
137
  ),
139
- context_limit=168000,
138
+ context_limit=200000,
140
139
  ),
141
140
  ),
142
141
  ],
@@ -1,35 +1,111 @@
1
- from klaude_code.config.config import load_config
1
+ from klaude_code.config.config import ModelConfig, load_config
2
2
  from klaude_code.trace import log
3
3
 
4
4
 
5
+ def _normalize_model_key(value: str) -> str:
6
+ """Normalize a model identifier for loose matching.
7
+
8
+ This enables aliases like:
9
+ - gpt52 -> gpt-5.2
10
+ - gpt5.2 -> gpt-5.2
11
+
12
+ Strategy: case-fold + keep only alphanumeric characters.
13
+ """
14
+
15
+ return "".join(ch for ch in value.casefold() if ch.isalnum())
16
+
17
+
5
18
  def select_model_from_config(preferred: str | None = None) -> str | None:
6
19
  """
7
20
  Interactive single-choice model selector.
8
21
  for `--select-model`
22
+
23
+ If preferred is provided:
24
+ - Exact match: return immediately
25
+ - Single partial match (case-insensitive): return immediately
26
+ - Otherwise: fall through to interactive selection
9
27
  """
10
28
  config = load_config()
11
29
  assert config is not None
12
- models = sorted(config.model_list, key=lambda m: m.model_name.lower())
30
+ models: list[ModelConfig] = sorted(config.model_list, key=lambda m: m.model_name.lower())
13
31
 
14
32
  if not models:
15
33
  raise ValueError("No models configured. Please update your config.yaml")
16
34
 
17
35
  names: list[str] = [m.model_name for m in models]
18
36
 
37
+ # Try to match preferred model name
38
+ filtered_models = models
39
+ if preferred and preferred.strip():
40
+ preferred = preferred.strip()
41
+ # Exact match
42
+ if preferred in names:
43
+ return preferred
44
+
45
+ preferred_lower = preferred.lower()
46
+ # Case-insensitive exact match (model_name or model_params.model)
47
+ exact_ci_matches = [
48
+ m
49
+ for m in models
50
+ if preferred_lower == m.model_name.lower() or preferred_lower == (m.model_params.model or "").lower()
51
+ ]
52
+ if len(exact_ci_matches) == 1:
53
+ return exact_ci_matches[0].model_name
54
+
55
+ # Normalized matching (e.g. gpt52 == gpt-5.2, gpt52 in gpt-5.2-2025-...)
56
+ preferred_norm = _normalize_model_key(preferred)
57
+ normalized_matches: list[ModelConfig] = []
58
+ if preferred_norm:
59
+ normalized_matches = [
60
+ m
61
+ for m in models
62
+ if preferred_norm == _normalize_model_key(m.model_name)
63
+ or preferred_norm == _normalize_model_key(m.model_params.model or "")
64
+ ]
65
+ if len(normalized_matches) == 1:
66
+ return normalized_matches[0].model_name
67
+
68
+ if not normalized_matches and len(preferred_norm) >= 4:
69
+ normalized_matches = [
70
+ m
71
+ for m in models
72
+ if preferred_norm in _normalize_model_key(m.model_name)
73
+ or preferred_norm in _normalize_model_key(m.model_params.model or "")
74
+ ]
75
+ if len(normalized_matches) == 1:
76
+ return normalized_matches[0].model_name
77
+
78
+ # Partial match (case-insensitive) on model_name or model_params.model.
79
+ # If normalized matching found candidates (even if multiple), prefer those as the filter set.
80
+ matches = normalized_matches or [
81
+ m
82
+ for m in models
83
+ if preferred_lower in m.model_name.lower() or preferred_lower in (m.model_params.model or "").lower()
84
+ ]
85
+ if len(matches) == 1:
86
+ return matches[0].model_name
87
+ if matches:
88
+ # Multiple matches: filter the list for interactive selection
89
+ filtered_models = matches
90
+ else:
91
+ # No matches: show all models without filter hint
92
+ preferred = None
93
+
19
94
  try:
20
95
  import questionary
21
96
 
22
97
  choices: list[questionary.Choice] = []
23
98
 
24
- max_model_name_length = max(len(m.model_name) for m in models)
25
- for m in models:
99
+ max_model_name_length = max(len(m.model_name) for m in filtered_models)
100
+ for m in filtered_models:
26
101
  star = "★ " if m.model_name == config.main_model else " "
27
102
  title = f"{star}{m.model_name:<{max_model_name_length}} → {m.model_params.model or 'N/A'} @ {m.provider}"
28
103
  choices.append(questionary.Choice(title=title, value=m.model_name))
29
104
 
30
105
  try:
106
+ message = f"Select a model (filtered by '{preferred}'):" if preferred else "Select a model:"
31
107
  result = questionary.select(
32
- message="Select a model:",
108
+ message=message,
33
109
  choices=choices,
34
110
  pointer="→",
35
111
  instruction="↑↓ to move • Enter to select",
@@ -50,7 +50,7 @@ READ_CHAR_LIMIT_PER_LINE = 2000
50
50
  READ_GLOBAL_LINE_CAP = 2000
51
51
 
52
52
  # Maximum total characters to read
53
- READ_MAX_CHARS = 60000
53
+ READ_MAX_CHARS = 50000
54
54
 
55
55
  # Maximum file size in KB for text files
56
56
  READ_MAX_KB = 256