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.
- klaude_code/cli/config_cmd.py +1 -1
- klaude_code/cli/debug.py +1 -1
- klaude_code/cli/main.py +45 -31
- klaude_code/cli/runtime.py +49 -13
- klaude_code/{version.py → cli/self_update.py} +110 -2
- klaude_code/command/__init__.py +4 -1
- klaude_code/command/clear_cmd.py +2 -7
- klaude_code/command/command_abc.py +33 -5
- klaude_code/command/debug_cmd.py +79 -0
- klaude_code/command/diff_cmd.py +2 -6
- klaude_code/command/export_cmd.py +7 -7
- klaude_code/command/export_online_cmd.py +9 -8
- klaude_code/command/help_cmd.py +4 -9
- klaude_code/command/model_cmd.py +10 -6
- klaude_code/command/prompt_command.py +2 -6
- klaude_code/command/refresh_cmd.py +2 -7
- klaude_code/command/registry.py +69 -26
- klaude_code/command/release_notes_cmd.py +2 -6
- klaude_code/command/status_cmd.py +2 -7
- klaude_code/command/terminal_setup_cmd.py +2 -6
- klaude_code/command/thinking_cmd.py +16 -10
- klaude_code/config/select_model.py +81 -5
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/executor.py +257 -110
- klaude_code/core/manager/__init__.py +2 -4
- klaude_code/core/prompts/prompt-claude-code.md +1 -1
- klaude_code/core/prompts/prompt-sub-agent-explore.md +14 -2
- klaude_code/core/prompts/prompt-sub-agent-web.md +8 -5
- klaude_code/core/reminders.py +9 -35
- klaude_code/core/task.py +9 -7
- klaude_code/core/tool/file/read_tool.md +1 -1
- klaude_code/core/tool/file/read_tool.py +41 -12
- klaude_code/core/tool/memory/skill_loader.py +12 -10
- klaude_code/core/tool/shell/bash_tool.py +22 -2
- klaude_code/core/tool/tool_registry.py +1 -1
- klaude_code/core/tool/tool_runner.py +26 -23
- klaude_code/core/tool/truncation.py +23 -9
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +36 -1
- klaude_code/core/turn.py +28 -0
- klaude_code/llm/anthropic/client.py +25 -9
- klaude_code/llm/openai_compatible/client.py +5 -2
- klaude_code/llm/openrouter/client.py +7 -3
- klaude_code/llm/responses/client.py +6 -1
- klaude_code/protocol/commands.py +1 -0
- klaude_code/protocol/sub_agent/web.py +3 -2
- klaude_code/session/session.py +35 -15
- klaude_code/session/templates/export_session.html +45 -32
- klaude_code/trace/__init__.py +20 -2
- klaude_code/ui/modes/repl/completers.py +231 -73
- klaude_code/ui/modes/repl/event_handler.py +8 -6
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
- klaude_code/ui/modes/repl/renderer.py +2 -2
- klaude_code/ui/renderers/common.py +54 -0
- klaude_code/ui/renderers/developer.py +2 -3
- klaude_code/ui/renderers/errors.py +1 -1
- klaude_code/ui/renderers/metadata.py +12 -5
- klaude_code/ui/renderers/thinking.py +24 -8
- klaude_code/ui/renderers/tools.py +82 -14
- klaude_code/ui/rich/code_panel.py +112 -0
- klaude_code/ui/rich/markdown.py +3 -4
- klaude_code/ui/rich/status.py +0 -2
- klaude_code/ui/rich/theme.py +10 -1
- klaude_code/ui/utils/common.py +0 -18
- {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/METADATA +32 -7
- {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/RECORD +69 -68
- klaude_code/core/manager/agent_manager.py +0 -132
- /klaude_code/{config → cli}/list_model.py +0 -0
- {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.17.dist-info → klaude_code-1.2.19.dist-info}/entry_points.txt +0 -0
|
@@ -5,15 +5,14 @@ import shutil
|
|
|
5
5
|
import subprocess
|
|
6
6
|
import tempfile
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
8
|
|
|
10
|
-
from
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
11
13
|
from klaude_code.protocol import commands, events, model
|
|
12
14
|
from klaude_code.session.export import build_export_html
|
|
13
15
|
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from klaude_code.core.agent import Agent
|
|
16
|
-
|
|
17
16
|
|
|
18
17
|
class ExportOnlineCommand(CommandABC):
|
|
19
18
|
"""Export and deploy the current session to surge.sh as a static webpage."""
|
|
@@ -60,9 +59,11 @@ class ExportOnlineCommand(CommandABC):
|
|
|
60
59
|
return CommandResult(events=[event])
|
|
61
60
|
|
|
62
61
|
try:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
console = Console()
|
|
63
|
+
with console.status(Text("Deploying to surge.sh...", style="dim"), spinner_style="dim"):
|
|
64
|
+
html_doc = self._build_html(agent)
|
|
65
|
+
domain = self._generate_domain()
|
|
66
|
+
url = self._deploy_to_surge(surge_cmd, html_doc, domain)
|
|
66
67
|
|
|
67
68
|
event = events.DeveloperMessageEvent(
|
|
68
69
|
session_id=agent.session.id,
|
klaude_code/command/help_cmd.py
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
from
|
|
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
|
|
|
6
|
-
if TYPE_CHECKING:
|
|
7
|
-
from klaude_code.core.agent import Agent
|
|
8
|
-
|
|
9
4
|
|
|
10
5
|
class HelpCommand(CommandABC):
|
|
11
6
|
"""Display help information for all available slash commands."""
|
|
@@ -18,7 +13,7 @@ class HelpCommand(CommandABC):
|
|
|
18
13
|
def summary(self) -> str:
|
|
19
14
|
return "Show help and available commands"
|
|
20
15
|
|
|
21
|
-
async def run(self, raw: str, agent:
|
|
16
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
22
17
|
lines: list[str] = [
|
|
23
18
|
"""
|
|
24
19
|
Usage:
|
|
@@ -39,8 +34,8 @@ Available slash commands:"""
|
|
|
39
34
|
|
|
40
35
|
if commands:
|
|
41
36
|
for cmd_name, cmd_obj in sorted(commands.items()):
|
|
42
|
-
|
|
43
|
-
lines.append(f" [b]/{cmd_name}[/b]{
|
|
37
|
+
placeholder = f" \\[{cmd_obj.placeholder}]" if cmd_obj.support_addition_params else ""
|
|
38
|
+
lines.append(f" [b]/{cmd_name}[/b]{placeholder} — {cmd_obj.summary}")
|
|
44
39
|
|
|
45
40
|
event = events.DeveloperMessageEvent(
|
|
46
41
|
session_id=agent.session.id,
|
klaude_code/command/model_cmd.py
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
2
|
|
|
4
|
-
from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
|
|
3
|
+
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult, InputAction
|
|
5
4
|
from klaude_code.config.select_model import select_model_from_config
|
|
6
5
|
from klaude_code.protocol import commands, events, model
|
|
7
6
|
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from klaude_code.core.agent import Agent
|
|
10
|
-
|
|
11
7
|
|
|
12
8
|
class ModelCommand(CommandABC):
|
|
13
9
|
"""Display or change the model configuration."""
|
|
@@ -24,7 +20,15 @@ class ModelCommand(CommandABC):
|
|
|
24
20
|
def is_interactive(self) -> bool:
|
|
25
21
|
return True
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
@property
|
|
24
|
+
def support_addition_params(self) -> bool:
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def placeholder(self) -> str:
|
|
29
|
+
return "model name"
|
|
30
|
+
|
|
31
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
28
32
|
selected_model = await asyncio.to_thread(select_model_from_config, preferred=raw)
|
|
29
33
|
|
|
30
34
|
current_model = agent.profile.llm_client.model_name if agent.profile else None
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
from importlib.resources import files
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
2
|
|
|
4
3
|
import yaml
|
|
5
4
|
|
|
6
|
-
from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
|
|
5
|
+
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult, InputAction
|
|
7
6
|
from klaude_code.protocol import commands
|
|
8
7
|
from klaude_code.trace import log_debug
|
|
9
8
|
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from klaude_code.core.agent import Agent
|
|
12
|
-
|
|
13
9
|
|
|
14
10
|
class PromptCommand(CommandABC):
|
|
15
11
|
"""Command that loads a prompt from a markdown file."""
|
|
@@ -59,7 +55,7 @@ class PromptCommand(CommandABC):
|
|
|
59
55
|
def support_addition_params(self) -> bool:
|
|
60
56
|
return True
|
|
61
57
|
|
|
62
|
-
async def run(self, raw: str, agent:
|
|
58
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
63
59
|
self._ensure_loaded()
|
|
64
60
|
template_content = self._content or ""
|
|
65
61
|
user_input = raw.strip() or "<none>"
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
from
|
|
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
|
|
5
3
|
|
|
6
|
-
if TYPE_CHECKING:
|
|
7
|
-
from klaude_code.core.agent import Agent
|
|
8
|
-
|
|
9
4
|
|
|
10
5
|
class RefreshTerminalCommand(CommandABC):
|
|
11
6
|
"""Refresh terminal display"""
|
|
@@ -22,7 +17,7 @@ class RefreshTerminalCommand(CommandABC):
|
|
|
22
17
|
def is_interactive(self) -> bool:
|
|
23
18
|
return True
|
|
24
19
|
|
|
25
|
-
async def run(self, raw: str, agent:
|
|
20
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
26
21
|
import os
|
|
27
22
|
|
|
28
23
|
os.system("cls" if os.name == "nt" else "clear")
|
klaude_code/command/registry.py
CHANGED
|
@@ -1,19 +1,79 @@
|
|
|
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"] = {}
|
|
15
13
|
|
|
16
14
|
|
|
15
|
+
def _command_key_to_str(key: commands.CommandName | str) -> str:
|
|
16
|
+
if isinstance(key, commands.CommandName):
|
|
17
|
+
return key.value
|
|
18
|
+
return key
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _resolve_command_key(command_name_raw: str) -> commands.CommandName | str | None:
|
|
22
|
+
"""Resolve raw command token to a registered command key.
|
|
23
|
+
|
|
24
|
+
Resolution order:
|
|
25
|
+
1) Exact match
|
|
26
|
+
2) Enum conversion (for standard commands)
|
|
27
|
+
3) Prefix match (supports abbreviations like `exp` -> `export`)
|
|
28
|
+
|
|
29
|
+
Prefix match rules:
|
|
30
|
+
- If there's exactly one prefix match, use it.
|
|
31
|
+
- If multiple matches exist and one command name is a prefix of all others,
|
|
32
|
+
treat it as the base command and use it (e.g. `export` over `export-online`).
|
|
33
|
+
- Otherwise, consider it ambiguous and return None.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if not command_name_raw:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Exact string match (works for both Enum and str keys because CommandName is a str Enum)
|
|
40
|
+
if command_name_raw in _COMMANDS:
|
|
41
|
+
return command_name_raw
|
|
42
|
+
|
|
43
|
+
# Enum conversion for standard commands
|
|
44
|
+
try:
|
|
45
|
+
enum_key = commands.CommandName(command_name_raw)
|
|
46
|
+
except ValueError:
|
|
47
|
+
enum_key = None
|
|
48
|
+
else:
|
|
49
|
+
if enum_key in _COMMANDS:
|
|
50
|
+
return enum_key
|
|
51
|
+
|
|
52
|
+
# Prefix match across all registered names
|
|
53
|
+
matching_keys: list[commands.CommandName | str] = []
|
|
54
|
+
matching_names: list[str] = []
|
|
55
|
+
for key in _COMMANDS:
|
|
56
|
+
key_str = _command_key_to_str(key)
|
|
57
|
+
if key_str.startswith(command_name_raw):
|
|
58
|
+
matching_keys.append(key)
|
|
59
|
+
matching_names.append(key_str)
|
|
60
|
+
|
|
61
|
+
if len(matching_keys) == 1:
|
|
62
|
+
return matching_keys[0]
|
|
63
|
+
|
|
64
|
+
if len(matching_keys) > 1:
|
|
65
|
+
# Prefer the base command when one is a prefix of all other matches.
|
|
66
|
+
base_matches = [
|
|
67
|
+
key
|
|
68
|
+
for key, key_name in zip(matching_keys, matching_names, strict=True)
|
|
69
|
+
if all(other.startswith(key_name) for other in matching_names if other != key_name)
|
|
70
|
+
]
|
|
71
|
+
if len(base_matches) == 1:
|
|
72
|
+
return base_matches[0]
|
|
73
|
+
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
17
77
|
def register(cmd: "CommandABC") -> None:
|
|
18
78
|
"""Register a command instance. Order of registration determines display order."""
|
|
19
79
|
_COMMANDS[cmd.name] = cmd
|
|
@@ -47,10 +107,10 @@ def get_commands() -> dict[commands.CommandName | str, "CommandABC"]:
|
|
|
47
107
|
|
|
48
108
|
def is_slash_command_name(name: str) -> bool:
|
|
49
109
|
_ensure_commands_loaded()
|
|
50
|
-
return name
|
|
110
|
+
return _resolve_command_key(name) is not None
|
|
51
111
|
|
|
52
112
|
|
|
53
|
-
async def dispatch_command(raw: str, agent:
|
|
113
|
+
async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
|
|
54
114
|
_ensure_commands_loaded()
|
|
55
115
|
# Detect command name
|
|
56
116
|
if not raw.startswith("/"):
|
|
@@ -60,21 +120,7 @@ async def dispatch_command(raw: str, agent: "Agent") -> CommandResult:
|
|
|
60
120
|
command_name_raw = splits[0][1:]
|
|
61
121
|
rest = " ".join(splits[1:]) if len(splits) > 1 else ""
|
|
62
122
|
|
|
63
|
-
|
|
64
|
-
command_key = None
|
|
65
|
-
|
|
66
|
-
# First try exact string match
|
|
67
|
-
if command_name_raw in _COMMANDS:
|
|
68
|
-
command_key = command_name_raw
|
|
69
|
-
else:
|
|
70
|
-
# Then try Enum conversion for standard commands
|
|
71
|
-
try:
|
|
72
|
-
enum_key = commands.CommandName(command_name_raw)
|
|
73
|
-
if enum_key in _COMMANDS:
|
|
74
|
-
command_key = enum_key
|
|
75
|
-
except ValueError:
|
|
76
|
-
pass
|
|
77
|
-
|
|
123
|
+
command_key = _resolve_command_key(command_name_raw)
|
|
78
124
|
if command_key is None:
|
|
79
125
|
return CommandResult(actions=[InputAction.run_agent(raw)])
|
|
80
126
|
|
|
@@ -108,11 +154,8 @@ def has_interactive_command(raw: str) -> bool:
|
|
|
108
154
|
return False
|
|
109
155
|
splits = raw.split(" ", maxsplit=1)
|
|
110
156
|
command_name_raw = splits[0][1:]
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
except ValueError:
|
|
114
|
-
return False
|
|
115
|
-
if command_name not in _COMMANDS:
|
|
157
|
+
command_key = _resolve_command_key(command_name_raw)
|
|
158
|
+
if command_key is None:
|
|
116
159
|
return False
|
|
117
|
-
command = _COMMANDS[
|
|
160
|
+
command = _COMMANDS[command_key]
|
|
118
161
|
return command.is_interactive
|
|
@@ -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:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
RESPONSES_LEVELS = ["
|
|
15
|
-
RESPONSES_GPT51_LEVELS = ["none", "
|
|
9
|
+
RESPONSES_LEVELS = ["low", "medium", "high"]
|
|
10
|
+
RESPONSES_GPT51_LEVELS = ["none", "low", "medium", "high"]
|
|
11
|
+
RESPONSES_GPT52_LEVELS = ["none", "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 ["
|
|
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:
|
|
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
|
|
|
@@ -201,6 +206,7 @@ class ThinkingCommand(CommandABC):
|
|
|
201
206
|
|
|
202
207
|
# Apply the new thinking configuration
|
|
203
208
|
config.thinking = new_thinking
|
|
209
|
+
agent.session.model_thinking = new_thinking
|
|
204
210
|
new_status = _format_current_thinking(config)
|
|
205
211
|
|
|
206
212
|
return CommandResult(
|
|
@@ -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
|
|
25
|
-
for m in
|
|
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=
|
|
108
|
+
message=message,
|
|
33
109
|
choices=choices,
|
|
34
110
|
pointer="→",
|
|
35
111
|
instruction="↑↓ to move • Enter to select",
|
klaude_code/const/__init__.py
CHANGED