klaude-code 1.2.6__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/__init__.py +0 -0
- klaude_code/cli/__init__.py +1 -0
- klaude_code/cli/main.py +298 -0
- klaude_code/cli/runtime.py +331 -0
- klaude_code/cli/session_cmd.py +80 -0
- klaude_code/command/__init__.py +43 -0
- klaude_code/command/clear_cmd.py +20 -0
- klaude_code/command/command_abc.py +92 -0
- klaude_code/command/diff_cmd.py +138 -0
- klaude_code/command/export_cmd.py +86 -0
- klaude_code/command/help_cmd.py +51 -0
- klaude_code/command/model_cmd.py +43 -0
- klaude_code/command/prompt-dev-docs-update.md +56 -0
- klaude_code/command/prompt-dev-docs.md +46 -0
- klaude_code/command/prompt-init.md +45 -0
- klaude_code/command/prompt_command.py +69 -0
- klaude_code/command/refresh_cmd.py +43 -0
- klaude_code/command/registry.py +110 -0
- klaude_code/command/status_cmd.py +111 -0
- klaude_code/command/terminal_setup_cmd.py +252 -0
- klaude_code/config/__init__.py +11 -0
- klaude_code/config/config.py +177 -0
- klaude_code/config/list_model.py +162 -0
- klaude_code/config/select_model.py +67 -0
- klaude_code/const/__init__.py +133 -0
- klaude_code/core/__init__.py +0 -0
- klaude_code/core/agent.py +165 -0
- klaude_code/core/executor.py +485 -0
- klaude_code/core/manager/__init__.py +19 -0
- klaude_code/core/manager/agent_manager.py +127 -0
- klaude_code/core/manager/llm_clients.py +42 -0
- klaude_code/core/manager/llm_clients_builder.py +49 -0
- klaude_code/core/manager/sub_agent_manager.py +86 -0
- klaude_code/core/prompt.py +89 -0
- klaude_code/core/prompts/prompt-claude-code.md +98 -0
- klaude_code/core/prompts/prompt-codex.md +331 -0
- klaude_code/core/prompts/prompt-gemini.md +43 -0
- klaude_code/core/prompts/prompt-subagent-explore.md +27 -0
- klaude_code/core/prompts/prompt-subagent-oracle.md +23 -0
- klaude_code/core/prompts/prompt-subagent-webfetch.md +46 -0
- klaude_code/core/prompts/prompt-subagent.md +8 -0
- klaude_code/core/reminders.py +445 -0
- klaude_code/core/task.py +237 -0
- klaude_code/core/tool/__init__.py +75 -0
- klaude_code/core/tool/file/__init__.py +0 -0
- klaude_code/core/tool/file/apply_patch.py +492 -0
- klaude_code/core/tool/file/apply_patch_tool.md +1 -0
- klaude_code/core/tool/file/apply_patch_tool.py +204 -0
- klaude_code/core/tool/file/edit_tool.md +9 -0
- klaude_code/core/tool/file/edit_tool.py +274 -0
- klaude_code/core/tool/file/multi_edit_tool.md +42 -0
- klaude_code/core/tool/file/multi_edit_tool.py +199 -0
- klaude_code/core/tool/file/read_tool.md +14 -0
- klaude_code/core/tool/file/read_tool.py +326 -0
- klaude_code/core/tool/file/write_tool.md +8 -0
- klaude_code/core/tool/file/write_tool.py +146 -0
- klaude_code/core/tool/memory/__init__.py +0 -0
- klaude_code/core/tool/memory/memory_tool.md +16 -0
- klaude_code/core/tool/memory/memory_tool.py +462 -0
- klaude_code/core/tool/memory/skill_loader.py +245 -0
- klaude_code/core/tool/memory/skill_tool.md +24 -0
- klaude_code/core/tool/memory/skill_tool.py +97 -0
- klaude_code/core/tool/shell/__init__.py +0 -0
- klaude_code/core/tool/shell/bash_tool.md +43 -0
- klaude_code/core/tool/shell/bash_tool.py +123 -0
- klaude_code/core/tool/shell/command_safety.py +363 -0
- klaude_code/core/tool/sub_agent_tool.py +83 -0
- klaude_code/core/tool/todo/__init__.py +0 -0
- klaude_code/core/tool/todo/todo_write_tool.md +182 -0
- klaude_code/core/tool/todo/todo_write_tool.py +121 -0
- klaude_code/core/tool/todo/update_plan_tool.md +3 -0
- klaude_code/core/tool/todo/update_plan_tool.py +104 -0
- klaude_code/core/tool/tool_abc.py +25 -0
- klaude_code/core/tool/tool_context.py +106 -0
- klaude_code/core/tool/tool_registry.py +78 -0
- klaude_code/core/tool/tool_runner.py +252 -0
- klaude_code/core/tool/truncation.py +170 -0
- klaude_code/core/tool/web/__init__.py +0 -0
- klaude_code/core/tool/web/mermaid_tool.md +21 -0
- klaude_code/core/tool/web/mermaid_tool.py +76 -0
- klaude_code/core/tool/web/web_fetch_tool.md +8 -0
- klaude_code/core/tool/web/web_fetch_tool.py +159 -0
- klaude_code/core/turn.py +220 -0
- klaude_code/llm/__init__.py +21 -0
- klaude_code/llm/anthropic/__init__.py +3 -0
- klaude_code/llm/anthropic/client.py +221 -0
- klaude_code/llm/anthropic/input.py +200 -0
- klaude_code/llm/client.py +49 -0
- klaude_code/llm/input_common.py +239 -0
- klaude_code/llm/openai_compatible/__init__.py +3 -0
- klaude_code/llm/openai_compatible/client.py +211 -0
- klaude_code/llm/openai_compatible/input.py +109 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +80 -0
- klaude_code/llm/openrouter/__init__.py +3 -0
- klaude_code/llm/openrouter/client.py +200 -0
- klaude_code/llm/openrouter/input.py +160 -0
- klaude_code/llm/openrouter/reasoning_handler.py +209 -0
- klaude_code/llm/registry.py +22 -0
- klaude_code/llm/responses/__init__.py +3 -0
- klaude_code/llm/responses/client.py +216 -0
- klaude_code/llm/responses/input.py +167 -0
- klaude_code/llm/usage.py +109 -0
- klaude_code/protocol/__init__.py +4 -0
- klaude_code/protocol/commands.py +21 -0
- klaude_code/protocol/events.py +163 -0
- klaude_code/protocol/llm_param.py +147 -0
- klaude_code/protocol/model.py +287 -0
- klaude_code/protocol/op.py +89 -0
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/protocol/sub_agent.py +348 -0
- klaude_code/protocol/tools.py +15 -0
- klaude_code/session/__init__.py +4 -0
- klaude_code/session/export.py +624 -0
- klaude_code/session/selector.py +76 -0
- klaude_code/session/session.py +474 -0
- klaude_code/session/templates/export_session.html +1434 -0
- klaude_code/trace/__init__.py +3 -0
- klaude_code/trace/log.py +168 -0
- klaude_code/ui/__init__.py +91 -0
- klaude_code/ui/core/__init__.py +1 -0
- klaude_code/ui/core/display.py +103 -0
- klaude_code/ui/core/input.py +71 -0
- klaude_code/ui/core/stage_manager.py +55 -0
- klaude_code/ui/modes/__init__.py +1 -0
- klaude_code/ui/modes/debug/__init__.py +1 -0
- klaude_code/ui/modes/debug/display.py +36 -0
- klaude_code/ui/modes/exec/__init__.py +1 -0
- klaude_code/ui/modes/exec/display.py +63 -0
- klaude_code/ui/modes/repl/__init__.py +51 -0
- klaude_code/ui/modes/repl/clipboard.py +152 -0
- klaude_code/ui/modes/repl/completers.py +429 -0
- klaude_code/ui/modes/repl/display.py +60 -0
- klaude_code/ui/modes/repl/event_handler.py +375 -0
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
- klaude_code/ui/modes/repl/key_bindings.py +170 -0
- klaude_code/ui/modes/repl/renderer.py +281 -0
- klaude_code/ui/renderers/__init__.py +0 -0
- klaude_code/ui/renderers/assistant.py +21 -0
- klaude_code/ui/renderers/common.py +8 -0
- klaude_code/ui/renderers/developer.py +158 -0
- klaude_code/ui/renderers/diffs.py +215 -0
- klaude_code/ui/renderers/errors.py +16 -0
- klaude_code/ui/renderers/metadata.py +190 -0
- klaude_code/ui/renderers/sub_agent.py +71 -0
- klaude_code/ui/renderers/thinking.py +39 -0
- klaude_code/ui/renderers/tools.py +551 -0
- klaude_code/ui/renderers/user_input.py +65 -0
- klaude_code/ui/rich/__init__.py +1 -0
- klaude_code/ui/rich/live.py +65 -0
- klaude_code/ui/rich/markdown.py +308 -0
- klaude_code/ui/rich/quote.py +34 -0
- klaude_code/ui/rich/searchable_text.py +71 -0
- klaude_code/ui/rich/status.py +240 -0
- klaude_code/ui/rich/theme.py +274 -0
- klaude_code/ui/terminal/__init__.py +1 -0
- klaude_code/ui/terminal/color.py +244 -0
- klaude_code/ui/terminal/control.py +147 -0
- klaude_code/ui/terminal/notifier.py +107 -0
- klaude_code/ui/terminal/progress_bar.py +87 -0
- klaude_code/ui/utils/__init__.py +1 -0
- klaude_code/ui/utils/common.py +108 -0
- klaude_code/ui/utils/debouncer.py +42 -0
- klaude_code/version.py +163 -0
- klaude_code-1.2.6.dist-info/METADATA +178 -0
- klaude_code-1.2.6.dist-info/RECORD +167 -0
- klaude_code-1.2.6.dist-info/WHEEL +4 -0
- klaude_code-1.2.6.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from pydantic import BaseModel, Field, ValidationError, model_validator
|
|
8
|
+
|
|
9
|
+
from klaude_code.protocol import llm_param
|
|
10
|
+
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
11
|
+
from klaude_code.trace import log
|
|
12
|
+
|
|
13
|
+
config_path = Path.home() / ".klaude" / "klaude-config.yaml"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ModelConfig(BaseModel):
|
|
17
|
+
model_name: str
|
|
18
|
+
provider: str
|
|
19
|
+
model_params: llm_param.LLMConfigModelParameter
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Config(BaseModel):
|
|
23
|
+
provider_list: list[llm_param.LLMConfigProviderParameter]
|
|
24
|
+
model_list: list[ModelConfig]
|
|
25
|
+
main_model: str
|
|
26
|
+
subagent_models: dict[str, str] = Field(default_factory=dict)
|
|
27
|
+
theme: str | None = None
|
|
28
|
+
|
|
29
|
+
@model_validator(mode="before")
|
|
30
|
+
@classmethod
|
|
31
|
+
def _normalize_subagent_models(cls, data: dict[str, Any]) -> dict[str, Any]:
|
|
32
|
+
raw_val: Any = data.get("subagent_models") or {}
|
|
33
|
+
raw_models: dict[str, Any] = cast(dict[str, Any], raw_val) if isinstance(raw_val, dict) else {}
|
|
34
|
+
normalized: dict[str, str] = {}
|
|
35
|
+
key_map = {p.name.lower(): p.name for p in iter_sub_agent_profiles()}
|
|
36
|
+
for key, value in dict(raw_models).items():
|
|
37
|
+
canonical = key_map.get(str(key).lower(), str(key))
|
|
38
|
+
normalized[canonical] = str(value)
|
|
39
|
+
data["subagent_models"] = normalized
|
|
40
|
+
return data
|
|
41
|
+
|
|
42
|
+
def get_main_model_config(self) -> llm_param.LLMConfigParameter:
|
|
43
|
+
return self.get_model_config(self.main_model)
|
|
44
|
+
|
|
45
|
+
def get_model_config(self, model_name: str) -> llm_param.LLMConfigParameter:
|
|
46
|
+
model = next(
|
|
47
|
+
(model for model in self.model_list if model.model_name == model_name),
|
|
48
|
+
None,
|
|
49
|
+
)
|
|
50
|
+
if model is None:
|
|
51
|
+
raise ValueError(f"Unknown model: {model_name}")
|
|
52
|
+
|
|
53
|
+
provider = next(
|
|
54
|
+
(provider for provider in self.provider_list if provider.provider_name == model.provider),
|
|
55
|
+
None,
|
|
56
|
+
)
|
|
57
|
+
if provider is None:
|
|
58
|
+
raise ValueError(f"Unknown provider: {model.provider}")
|
|
59
|
+
|
|
60
|
+
return llm_param.LLMConfigParameter(
|
|
61
|
+
**provider.model_dump(),
|
|
62
|
+
**model.model_params.model_dump(),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def save(self) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Save config to file.
|
|
68
|
+
Notice: it won't preserve comments in the config file.
|
|
69
|
+
"""
|
|
70
|
+
config_dict = self.model_dump(mode="json", exclude_none=True)
|
|
71
|
+
|
|
72
|
+
def _save_config() -> None:
|
|
73
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
_ = config_path.write_text(yaml.dump(config_dict, default_flow_style=False, sort_keys=False))
|
|
75
|
+
|
|
76
|
+
await asyncio.to_thread(_save_config)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_example_config() -> Config:
|
|
80
|
+
return Config(
|
|
81
|
+
main_model="gpt-5.1",
|
|
82
|
+
subagent_models={"explore": "haiku", "oracle": "gpt-5.1-high"},
|
|
83
|
+
provider_list=[
|
|
84
|
+
llm_param.LLMConfigProviderParameter(
|
|
85
|
+
provider_name="openai",
|
|
86
|
+
protocol=llm_param.LLMClientProtocol.RESPONSES,
|
|
87
|
+
api_key="your-openai-api-key",
|
|
88
|
+
base_url="https://api.openai.com/v1",
|
|
89
|
+
),
|
|
90
|
+
llm_param.LLMConfigProviderParameter(
|
|
91
|
+
provider_name="openrouter",
|
|
92
|
+
protocol=llm_param.LLMClientProtocol.OPENROUTER,
|
|
93
|
+
api_key="your-openrouter-api-key",
|
|
94
|
+
),
|
|
95
|
+
],
|
|
96
|
+
model_list=[
|
|
97
|
+
ModelConfig(
|
|
98
|
+
model_name="gpt-5.1",
|
|
99
|
+
provider="openai",
|
|
100
|
+
model_params=llm_param.LLMConfigModelParameter(
|
|
101
|
+
model="gpt-5.1-2025-11-13",
|
|
102
|
+
max_tokens=32000,
|
|
103
|
+
verbosity="medium",
|
|
104
|
+
thinking=llm_param.Thinking(
|
|
105
|
+
reasoning_effort="medium",
|
|
106
|
+
reasoning_summary="auto",
|
|
107
|
+
type="enabled",
|
|
108
|
+
budget_tokens=None,
|
|
109
|
+
),
|
|
110
|
+
context_limit=368000,
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
ModelConfig(
|
|
114
|
+
model_name="gpt-5.1-high",
|
|
115
|
+
provider="openai",
|
|
116
|
+
model_params=llm_param.LLMConfigModelParameter(
|
|
117
|
+
model="gpt-5.1-2025-11-13",
|
|
118
|
+
max_tokens=32000,
|
|
119
|
+
verbosity="medium",
|
|
120
|
+
thinking=llm_param.Thinking(
|
|
121
|
+
reasoning_effort="high",
|
|
122
|
+
reasoning_summary="auto",
|
|
123
|
+
type="enabled",
|
|
124
|
+
budget_tokens=None,
|
|
125
|
+
),
|
|
126
|
+
context_limit=368000,
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
ModelConfig(
|
|
130
|
+
model_name="haiku",
|
|
131
|
+
provider="openrouter",
|
|
132
|
+
model_params=llm_param.LLMConfigModelParameter(
|
|
133
|
+
model="anthropic/claude-haiku-4.5",
|
|
134
|
+
max_tokens=32000,
|
|
135
|
+
provider_routing=llm_param.OpenRouterProviderRouting(
|
|
136
|
+
sort="throughput",
|
|
137
|
+
),
|
|
138
|
+
context_limit=168000,
|
|
139
|
+
),
|
|
140
|
+
),
|
|
141
|
+
],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@lru_cache(maxsize=1)
|
|
146
|
+
def load_config() -> Config | None:
|
|
147
|
+
if not config_path.exists():
|
|
148
|
+
log(f"Config file not found: {config_path}")
|
|
149
|
+
example_config = get_example_config()
|
|
150
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
config_dict = example_config.model_dump(mode="json", exclude_none=True)
|
|
152
|
+
|
|
153
|
+
# Comment out all example config lines
|
|
154
|
+
yaml_str = yaml.dump(config_dict, default_flow_style=False, sort_keys=False)
|
|
155
|
+
commented_yaml = "\n".join(f"# {line}" if line.strip() else "#" for line in yaml_str.splitlines())
|
|
156
|
+
_ = config_path.write_text(commented_yaml)
|
|
157
|
+
|
|
158
|
+
log(f"Example config created at: {config_path}")
|
|
159
|
+
log("Please edit the config file to set up your models", style="yellow bold")
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
config_yaml = config_path.read_text()
|
|
163
|
+
config_dict = yaml.safe_load(config_yaml)
|
|
164
|
+
|
|
165
|
+
if config_dict is None:
|
|
166
|
+
log(f"Config file is empty or all commented: {config_path}", style="red bold")
|
|
167
|
+
log("Please edit the config file to set up your models", style="yellow bold")
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
config = Config.model_validate(config_dict)
|
|
172
|
+
except ValidationError as e:
|
|
173
|
+
log(f"Invalid config file: {config_path}", style="red bold")
|
|
174
|
+
log(str(e), style="red")
|
|
175
|
+
raise ValueError(f"Invalid config file: {config_path}") from e
|
|
176
|
+
|
|
177
|
+
return config
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from rich.console import Console, Group
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.text import Text
|
|
5
|
+
|
|
6
|
+
from klaude_code.config import Config
|
|
7
|
+
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
8
|
+
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def mask_api_key(api_key: str | None) -> str:
|
|
12
|
+
"""Mask API key to show only first 6 and last 6 characters with *** in between"""
|
|
13
|
+
if not api_key or api_key == "N/A":
|
|
14
|
+
return "N/A"
|
|
15
|
+
|
|
16
|
+
if len(api_key) <= 12:
|
|
17
|
+
return api_key
|
|
18
|
+
|
|
19
|
+
return f"{api_key[:6]} … {api_key[-6:]}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def display_models_and_providers(config: Config):
|
|
23
|
+
"""Display models and providers configuration using rich formatting"""
|
|
24
|
+
themes = get_theme(config.theme)
|
|
25
|
+
console = Console(theme=themes.app_theme)
|
|
26
|
+
|
|
27
|
+
# Display providers section
|
|
28
|
+
providers_table = Table.grid(padding=(0, 1), expand=True)
|
|
29
|
+
providers_table.add_column(width=2, no_wrap=True) # Status
|
|
30
|
+
providers_table.add_column(overflow="fold") # Name
|
|
31
|
+
providers_table.add_column(overflow="fold") # Protocol
|
|
32
|
+
providers_table.add_column(overflow="fold") # Base URL
|
|
33
|
+
providers_table.add_column(overflow="fold") # API Key
|
|
34
|
+
|
|
35
|
+
# Add header
|
|
36
|
+
providers_table.add_row(
|
|
37
|
+
Text("", style="bold"),
|
|
38
|
+
Text("Name", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
39
|
+
Text("Protocol", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
40
|
+
Text("Base URL", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
41
|
+
Text("API Key", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Add providers
|
|
45
|
+
for provider in config.provider_list:
|
|
46
|
+
status = Text("✔", style=f"bold {ThemeKey.CONFIG_STATUS_OK}")
|
|
47
|
+
name = Text(provider.provider_name, style=ThemeKey.CONFIG_ITEM_NAME)
|
|
48
|
+
protocol = Text(str(provider.protocol.value), style="")
|
|
49
|
+
base_url = Text(provider.base_url or "N/A", style="")
|
|
50
|
+
api_key = Text(mask_api_key(provider.api_key), style="")
|
|
51
|
+
|
|
52
|
+
providers_table.add_row(status, name, protocol, base_url, api_key)
|
|
53
|
+
|
|
54
|
+
# Display models section
|
|
55
|
+
models_table = Table.grid(padding=(0, 1), expand=True)
|
|
56
|
+
models_table.add_column(width=2, no_wrap=True) # Status
|
|
57
|
+
models_table.add_column(overflow="fold", ratio=1) # Name
|
|
58
|
+
models_table.add_column(overflow="fold", ratio=2) # Model
|
|
59
|
+
models_table.add_column(overflow="fold", ratio=2) # Provider
|
|
60
|
+
models_table.add_column(overflow="fold", ratio=3) # Params
|
|
61
|
+
|
|
62
|
+
# Add header
|
|
63
|
+
models_table.add_row(
|
|
64
|
+
Text("", style="bold"),
|
|
65
|
+
Text("Name", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
66
|
+
Text("Model", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
67
|
+
Text("Provider", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
68
|
+
Text("Params", style=f"bold {ThemeKey.CONFIG_TABLE_HEADER}"),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Add models
|
|
72
|
+
for model in config.model_list:
|
|
73
|
+
status = Text("✔", style=f"bold {ThemeKey.CONFIG_STATUS_OK}")
|
|
74
|
+
if model.model_name == config.main_model:
|
|
75
|
+
status = Text("★", style=f"bold {ThemeKey.CONFIG_STATUS_PRIMARY}") # Mark main model
|
|
76
|
+
|
|
77
|
+
name = Text(
|
|
78
|
+
model.model_name,
|
|
79
|
+
style=ThemeKey.CONFIG_STATUS_PRIMARY
|
|
80
|
+
if model.model_name == config.main_model
|
|
81
|
+
else ThemeKey.CONFIG_ITEM_NAME,
|
|
82
|
+
)
|
|
83
|
+
model_name = Text(model.model_params.model or "N/A", style="")
|
|
84
|
+
provider = Text(model.provider, style="")
|
|
85
|
+
params: list[Text] = []
|
|
86
|
+
if model.model_params.thinking:
|
|
87
|
+
if model.model_params.thinking.reasoning_effort is not None:
|
|
88
|
+
params.append(
|
|
89
|
+
Text.assemble(
|
|
90
|
+
("reason-effort", ThemeKey.CONFIG_PARAM_LABEL),
|
|
91
|
+
": ",
|
|
92
|
+
model.model_params.thinking.reasoning_effort,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
if model.model_params.thinking.reasoning_summary is not None:
|
|
96
|
+
params.append(
|
|
97
|
+
Text.assemble(
|
|
98
|
+
("reason-summary", ThemeKey.CONFIG_PARAM_LABEL),
|
|
99
|
+
": ",
|
|
100
|
+
model.model_params.thinking.reasoning_summary,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
if model.model_params.thinking.budget_tokens is not None:
|
|
104
|
+
params.append(
|
|
105
|
+
Text.assemble(
|
|
106
|
+
("thinking-budget-tokens", ThemeKey.CONFIG_PARAM_LABEL),
|
|
107
|
+
": ",
|
|
108
|
+
str(model.model_params.thinking.budget_tokens),
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
if model.model_params.provider_routing:
|
|
112
|
+
params.append(
|
|
113
|
+
Text.assemble(
|
|
114
|
+
("provider-routing", ThemeKey.CONFIG_PARAM_LABEL),
|
|
115
|
+
": ",
|
|
116
|
+
model.model_params.provider_routing.model_dump_json(exclude_none=True),
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
if len(params) == 0:
|
|
120
|
+
params.append(Text("N/A", style=ThemeKey.CONFIG_PARAM_LABEL))
|
|
121
|
+
models_table.add_row(status, name, model_name, provider, Group(*params))
|
|
122
|
+
|
|
123
|
+
# Create panels and display
|
|
124
|
+
providers_panel = Panel(
|
|
125
|
+
providers_table,
|
|
126
|
+
title=Text("Providers Configuration", style="white bold"),
|
|
127
|
+
border_style=ThemeKey.CONFIG_PANEL_BORDER,
|
|
128
|
+
padding=(0, 1),
|
|
129
|
+
title_align="left",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
models_panel = Panel(
|
|
133
|
+
models_table,
|
|
134
|
+
title=Text("Models Configuration", style="white bold"),
|
|
135
|
+
border_style=ThemeKey.CONFIG_PANEL_BORDER,
|
|
136
|
+
padding=(0, 1),
|
|
137
|
+
title_align="left",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
console.print(providers_panel)
|
|
141
|
+
console.print()
|
|
142
|
+
console.print(models_panel)
|
|
143
|
+
|
|
144
|
+
# Display main model info
|
|
145
|
+
console.print()
|
|
146
|
+
console.print(
|
|
147
|
+
Text.assemble(
|
|
148
|
+
("Default Model: ", "bold"),
|
|
149
|
+
(config.main_model, ThemeKey.CONFIG_STATUS_PRIMARY),
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
for profile in iter_sub_agent_profiles():
|
|
154
|
+
sub_model_name = config.subagent_models.get(profile.name)
|
|
155
|
+
if not sub_model_name:
|
|
156
|
+
continue
|
|
157
|
+
console.print(
|
|
158
|
+
Text.assemble(
|
|
159
|
+
(f"{profile.name} Model: ", "bold"),
|
|
160
|
+
(sub_model_name, ThemeKey.CONFIG_STATUS_PRIMARY),
|
|
161
|
+
)
|
|
162
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from klaude_code.config.config import load_config
|
|
2
|
+
from klaude_code.trace import log
|
|
3
|
+
from klaude_code.ui.rich.searchable_text import SearchableFormattedList
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def select_model_from_config(preferred: str | None = None) -> str | None:
|
|
7
|
+
"""
|
|
8
|
+
Interactive single-choice model selector.
|
|
9
|
+
for `--select-model`
|
|
10
|
+
"""
|
|
11
|
+
config = load_config()
|
|
12
|
+
assert config is not None
|
|
13
|
+
models = sorted(config.model_list, key=lambda m: m.model_name.lower())
|
|
14
|
+
|
|
15
|
+
if not models:
|
|
16
|
+
raise ValueError("No models configured. Please update your config.yaml")
|
|
17
|
+
|
|
18
|
+
names: list[str] = [m.model_name for m in models]
|
|
19
|
+
default_name: str | None = (
|
|
20
|
+
preferred if preferred in names else (config.main_model if config.main_model in names else None)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import questionary
|
|
25
|
+
|
|
26
|
+
choices: list[questionary.Choice] = []
|
|
27
|
+
|
|
28
|
+
max_model_name_length = max(len(m.model_name) for m in models)
|
|
29
|
+
for m in models:
|
|
30
|
+
star = "★ " if m.model_name == config.main_model else " "
|
|
31
|
+
fragments = [
|
|
32
|
+
("class:t", f"{star}{m.model_name:<{max_model_name_length}} → "),
|
|
33
|
+
("class:b", m.model_params.model or "N/A"),
|
|
34
|
+
("class:d", f" {m.provider}"),
|
|
35
|
+
]
|
|
36
|
+
# Provide a formatted title for display and a plain text for search.
|
|
37
|
+
title = SearchableFormattedList(fragments)
|
|
38
|
+
choices.append(questionary.Choice(title=title, value=m.model_name))
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
result = questionary.select(
|
|
42
|
+
message="Select a model:",
|
|
43
|
+
choices=choices,
|
|
44
|
+
default=default_name,
|
|
45
|
+
pointer="→",
|
|
46
|
+
instruction="↑↓ to move • Enter to select",
|
|
47
|
+
use_jk_keys=False,
|
|
48
|
+
use_search_filter=True,
|
|
49
|
+
style=questionary.Style(
|
|
50
|
+
[
|
|
51
|
+
("t", ""),
|
|
52
|
+
("b", "bold"),
|
|
53
|
+
("d", "dim"),
|
|
54
|
+
# search filter colors at the bottom
|
|
55
|
+
("search_success", "noinherit fg:ansigreen"),
|
|
56
|
+
("search_none", "noinherit fg:ansired"),
|
|
57
|
+
("question-mark", "fg:ansigreen"),
|
|
58
|
+
]
|
|
59
|
+
),
|
|
60
|
+
).ask()
|
|
61
|
+
if isinstance(result, str) and result in names:
|
|
62
|
+
return result
|
|
63
|
+
except KeyboardInterrupt:
|
|
64
|
+
return None
|
|
65
|
+
except Exception as e:
|
|
66
|
+
log(f"Failed to use questionary, falling back to default model, {e}")
|
|
67
|
+
return preferred
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Centralized configuration constants for klaude_code.
|
|
2
|
+
|
|
3
|
+
This module consolidates all magic numbers and configuration values
|
|
4
|
+
that were previously scattered across the codebase.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# =============================================================================
|
|
8
|
+
# Agent Configuration
|
|
9
|
+
# =============================================================================
|
|
10
|
+
|
|
11
|
+
# Timeout for waiting for the first event from LLM (seconds)
|
|
12
|
+
FIRST_EVENT_TIMEOUT_S = 200.0
|
|
13
|
+
|
|
14
|
+
# Maximum number of retry attempts for failed turns
|
|
15
|
+
MAX_FAILED_TURN_RETRIES = 10
|
|
16
|
+
|
|
17
|
+
# Initial delay before retrying a failed turn (seconds)
|
|
18
|
+
INITIAL_RETRY_DELAY_S = 1.0
|
|
19
|
+
|
|
20
|
+
# Maximum delay between retries (seconds)
|
|
21
|
+
MAX_RETRY_DELAY_S = 30.0
|
|
22
|
+
|
|
23
|
+
# Message shown when a tool call is cancelled by the user
|
|
24
|
+
CANCEL_OUTPUT = "[Request interrupted by user for tool use]"
|
|
25
|
+
|
|
26
|
+
# Default maximum tokens for LLM responses
|
|
27
|
+
DEFAULT_MAX_TOKENS = 32000
|
|
28
|
+
|
|
29
|
+
# Default temperature for LLM requests
|
|
30
|
+
DEFAULT_TEMPERATURE = 1.0
|
|
31
|
+
|
|
32
|
+
# Default thinking budget tokens for Anthropic models
|
|
33
|
+
DEFAULT_ANTHROPIC_THINKING_BUDGET_TOKENS = 2048
|
|
34
|
+
|
|
35
|
+
# Tool call count threshold for todo reminder
|
|
36
|
+
TODO_REMINDER_TOOL_CALL_THRESHOLD = 10
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Tool
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
# -- Read Tool --
|
|
44
|
+
# Maximum characters per line before truncation
|
|
45
|
+
READ_CHAR_LIMIT_PER_LINE = 2000
|
|
46
|
+
|
|
47
|
+
# Maximum number of lines to read from a file
|
|
48
|
+
READ_GLOBAL_LINE_CAP = 2000
|
|
49
|
+
|
|
50
|
+
# Maximum total characters to read
|
|
51
|
+
READ_MAX_CHARS = 60000
|
|
52
|
+
|
|
53
|
+
# Maximum file size in KB for text files
|
|
54
|
+
READ_MAX_KB = 256
|
|
55
|
+
|
|
56
|
+
# Maximum image file size in bytes (4MB)
|
|
57
|
+
READ_MAX_IMAGE_BYTES = 4 * 1024 * 1024
|
|
58
|
+
|
|
59
|
+
# -- Bash Tool --
|
|
60
|
+
# Default timeout for bash commands (milliseconds)
|
|
61
|
+
BASH_DEFAULT_TIMEOUT_MS = 120000
|
|
62
|
+
|
|
63
|
+
# -- Tool Output --
|
|
64
|
+
# Maximum length for tool output before truncation
|
|
65
|
+
TOOL_OUTPUT_MAX_LENGTH = 50000
|
|
66
|
+
|
|
67
|
+
# Characters to show from the beginning of truncated output
|
|
68
|
+
TOOL_OUTPUT_DISPLAY_HEAD = 10000
|
|
69
|
+
|
|
70
|
+
# Characters to show from the end of truncated output
|
|
71
|
+
TOOL_OUTPUT_DISPLAY_TAIL = 10000
|
|
72
|
+
|
|
73
|
+
# Directory for saving full truncated output
|
|
74
|
+
TOOL_OUTPUT_TRUNCATION_DIR = "/tmp/klaude"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# =============================================================================
|
|
78
|
+
# UI
|
|
79
|
+
# =============================================================================
|
|
80
|
+
|
|
81
|
+
# Width of line number prefix in diff display
|
|
82
|
+
DIFF_PREFIX_WIDTH = 4
|
|
83
|
+
|
|
84
|
+
# Maximum lines to show in diff output
|
|
85
|
+
MAX_DIFF_LINES = 1000
|
|
86
|
+
|
|
87
|
+
# Maximum length for invalid tool call display
|
|
88
|
+
INVALID_TOOL_CALL_MAX_LENGTH = 500
|
|
89
|
+
|
|
90
|
+
# Maximum line length for truncated display output
|
|
91
|
+
TRUNCATE_DISPLAY_MAX_LINE_LENGTH = 1000
|
|
92
|
+
|
|
93
|
+
# Maximum lines for truncated display output
|
|
94
|
+
TRUNCATE_DISPLAY_MAX_LINES = 10
|
|
95
|
+
|
|
96
|
+
# Maximum lines for sub-agent result display
|
|
97
|
+
SUB_AGENT_RESULT_MAX_LINES = 12
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# UI refresh rate (frames per second) for debounced content streaming
|
|
101
|
+
UI_REFRESH_RATE_FPS = 20
|
|
102
|
+
|
|
103
|
+
# Number of lines to keep visible at bottom of markdown streaming window
|
|
104
|
+
MARKDOWN_STREAM_LIVE_WINDOW = 20
|
|
105
|
+
|
|
106
|
+
# Status shimmer animation
|
|
107
|
+
# Horizontal padding used when computing shimmer band position
|
|
108
|
+
STATUS_SHIMMER_PADDING = 10
|
|
109
|
+
# Duration in seconds for one full shimmer sweep across the text
|
|
110
|
+
STATUS_SHIMMER_SWEEP_SECONDS = 2
|
|
111
|
+
# Half-width of the shimmer band in characters
|
|
112
|
+
STATUS_SHIMMER_BAND_HALF_WIDTH = 5.0
|
|
113
|
+
# Scale factor applied to shimmer intensity when blending colors
|
|
114
|
+
STATUS_SHIMMER_ALPHA_SCALE = 0.7
|
|
115
|
+
|
|
116
|
+
# Spinner breathing animation
|
|
117
|
+
# Duration in seconds for one full breathe-in + breathe-out cycle
|
|
118
|
+
# Keep in sync with STATUS_SHIMMER_SWEEP_SECONDS for visual consistency
|
|
119
|
+
SPINNER_BREATH_PERIOD_SECONDS = 2
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# Debug / Logging
|
|
124
|
+
# =============================================================================
|
|
125
|
+
|
|
126
|
+
# Default debug log file path
|
|
127
|
+
DEFAULT_DEBUG_LOG_FILE = "debug.log"
|
|
128
|
+
|
|
129
|
+
# Maximum log file size before rotation (10MB)
|
|
130
|
+
LOG_MAX_BYTES = 10 * 1024 * 1024
|
|
131
|
+
|
|
132
|
+
# Number of backup log files to keep
|
|
133
|
+
LOG_BACKUP_COUNT = 3
|
|
File without changes
|