klaude-code 1.2.6__py3-none-any.whl → 1.8.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.
- klaude_code/auth/__init__.py +24 -0
- klaude_code/auth/codex/__init__.py +20 -0
- klaude_code/auth/codex/exceptions.py +17 -0
- klaude_code/auth/codex/jwt_utils.py +45 -0
- klaude_code/auth/codex/oauth.py +229 -0
- klaude_code/auth/codex/token_manager.py +84 -0
- klaude_code/cli/auth_cmd.py +73 -0
- klaude_code/cli/config_cmd.py +91 -0
- klaude_code/cli/cost_cmd.py +338 -0
- klaude_code/cli/debug.py +78 -0
- klaude_code/cli/list_model.py +307 -0
- klaude_code/cli/main.py +233 -134
- klaude_code/cli/runtime.py +309 -117
- klaude_code/{version.py → cli/self_update.py} +114 -5
- klaude_code/cli/session_cmd.py +37 -21
- klaude_code/command/__init__.py +88 -27
- klaude_code/command/clear_cmd.py +8 -7
- klaude_code/command/command_abc.py +31 -31
- klaude_code/command/debug_cmd.py +79 -0
- klaude_code/command/export_cmd.py +19 -53
- klaude_code/command/export_online_cmd.py +154 -0
- klaude_code/command/fork_session_cmd.py +267 -0
- klaude_code/command/help_cmd.py +7 -8
- klaude_code/command/model_cmd.py +60 -10
- klaude_code/command/model_select.py +84 -0
- klaude_code/command/prompt-jj-describe.md +32 -0
- klaude_code/command/prompt_command.py +19 -11
- klaude_code/command/refresh_cmd.py +8 -10
- klaude_code/command/registry.py +139 -40
- klaude_code/command/release_notes_cmd.py +84 -0
- klaude_code/command/resume_cmd.py +111 -0
- klaude_code/command/status_cmd.py +104 -60
- klaude_code/command/terminal_setup_cmd.py +7 -9
- klaude_code/command/thinking_cmd.py +98 -0
- klaude_code/config/__init__.py +14 -6
- klaude_code/config/assets/__init__.py +1 -0
- klaude_code/config/assets/builtin_config.yaml +303 -0
- klaude_code/config/builtin_config.py +38 -0
- klaude_code/config/config.py +378 -109
- klaude_code/config/select_model.py +117 -53
- klaude_code/config/thinking.py +269 -0
- klaude_code/{const/__init__.py → const.py} +50 -19
- klaude_code/core/agent.py +20 -28
- klaude_code/core/executor.py +327 -112
- klaude_code/core/manager/__init__.py +2 -4
- klaude_code/core/manager/llm_clients.py +1 -15
- klaude_code/core/manager/llm_clients_builder.py +10 -11
- klaude_code/core/manager/sub_agent_manager.py +37 -6
- klaude_code/core/prompt.py +63 -44
- klaude_code/core/prompts/prompt-claude-code.md +2 -13
- klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
- klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
- klaude_code/core/prompts/prompt-codex.md +9 -42
- klaude_code/core/prompts/prompt-minimal.md +12 -0
- klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
- klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
- klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
- klaude_code/core/reminders.py +283 -95
- klaude_code/core/task.py +113 -75
- klaude_code/core/tool/__init__.py +24 -31
- klaude_code/core/tool/file/_utils.py +36 -0
- klaude_code/core/tool/file/apply_patch.py +17 -25
- klaude_code/core/tool/file/apply_patch_tool.py +57 -77
- klaude_code/core/tool/file/diff_builder.py +151 -0
- klaude_code/core/tool/file/edit_tool.py +50 -63
- klaude_code/core/tool/file/move_tool.md +41 -0
- klaude_code/core/tool/file/move_tool.py +435 -0
- klaude_code/core/tool/file/read_tool.md +1 -1
- klaude_code/core/tool/file/read_tool.py +86 -86
- klaude_code/core/tool/file/write_tool.py +59 -69
- klaude_code/core/tool/report_back_tool.py +84 -0
- klaude_code/core/tool/shell/bash_tool.py +265 -22
- klaude_code/core/tool/shell/command_safety.py +3 -6
- klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
- klaude_code/core/tool/sub_agent_tool.py +13 -2
- klaude_code/core/tool/todo/todo_write_tool.md +0 -157
- klaude_code/core/tool/todo/todo_write_tool.py +1 -1
- klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
- klaude_code/core/tool/todo/update_plan_tool.py +1 -1
- klaude_code/core/tool/tool_abc.py +18 -0
- klaude_code/core/tool/tool_context.py +27 -12
- klaude_code/core/tool/tool_registry.py +7 -7
- klaude_code/core/tool/tool_runner.py +44 -36
- klaude_code/core/tool/truncation.py +29 -14
- klaude_code/core/tool/web/mermaid_tool.md +43 -0
- klaude_code/core/tool/web/mermaid_tool.py +2 -5
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +112 -22
- klaude_code/core/tool/web/web_search_tool.md +23 -0
- klaude_code/core/tool/web/web_search_tool.py +130 -0
- klaude_code/core/turn.py +168 -66
- klaude_code/llm/__init__.py +2 -10
- klaude_code/llm/anthropic/client.py +190 -178
- klaude_code/llm/anthropic/input.py +39 -15
- klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code/llm/bedrock/client.py +60 -0
- klaude_code/llm/client.py +7 -21
- klaude_code/llm/codex/__init__.py +5 -0
- klaude_code/llm/codex/client.py +149 -0
- klaude_code/llm/google/__init__.py +3 -0
- klaude_code/llm/google/client.py +309 -0
- klaude_code/llm/google/input.py +215 -0
- klaude_code/llm/input_common.py +3 -9
- klaude_code/llm/openai_compatible/client.py +72 -164
- klaude_code/llm/openai_compatible/input.py +6 -4
- klaude_code/llm/openai_compatible/stream.py +273 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- klaude_code/llm/openrouter/client.py +89 -160
- klaude_code/llm/openrouter/input.py +18 -30
- klaude_code/llm/openrouter/reasoning.py +118 -0
- klaude_code/llm/registry.py +39 -7
- klaude_code/llm/responses/client.py +184 -171
- klaude_code/llm/responses/input.py +20 -1
- klaude_code/llm/usage.py +17 -12
- klaude_code/protocol/commands.py +17 -1
- klaude_code/protocol/events.py +31 -4
- klaude_code/protocol/llm_param.py +13 -10
- klaude_code/protocol/model.py +232 -29
- klaude_code/protocol/op.py +90 -1
- klaude_code/protocol/op_handler.py +35 -1
- klaude_code/protocol/sub_agent/__init__.py +117 -0
- klaude_code/protocol/sub_agent/explore.py +63 -0
- klaude_code/protocol/sub_agent/oracle.py +91 -0
- klaude_code/protocol/sub_agent/task.py +61 -0
- klaude_code/protocol/sub_agent/web.py +79 -0
- klaude_code/protocol/tools.py +4 -2
- klaude_code/session/__init__.py +2 -2
- klaude_code/session/codec.py +71 -0
- klaude_code/session/export.py +293 -86
- klaude_code/session/selector.py +89 -67
- klaude_code/session/session.py +320 -309
- klaude_code/session/store.py +220 -0
- klaude_code/session/templates/export_session.html +595 -83
- klaude_code/session/templates/mermaid_viewer.html +926 -0
- klaude_code/skill/__init__.py +27 -0
- klaude_code/skill/assets/deslop/SKILL.md +17 -0
- klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
- klaude_code/skill/assets/handoff/SKILL.md +39 -0
- klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
- klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
- klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
- klaude_code/skill/manager.py +70 -0
- klaude_code/skill/system_skills.py +192 -0
- klaude_code/trace/__init__.py +20 -2
- klaude_code/trace/log.py +150 -5
- klaude_code/ui/__init__.py +4 -9
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +7 -7
- klaude_code/ui/modes/debug/display.py +2 -1
- klaude_code/ui/modes/repl/__init__.py +3 -48
- klaude_code/ui/modes/repl/clipboard.py +5 -5
- klaude_code/ui/modes/repl/completers.py +487 -123
- klaude_code/ui/modes/repl/display.py +5 -4
- klaude_code/ui/modes/repl/event_handler.py +370 -117
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
- klaude_code/ui/modes/repl/key_bindings.py +146 -23
- klaude_code/ui/modes/repl/renderer.py +189 -99
- klaude_code/ui/renderers/assistant.py +9 -2
- klaude_code/ui/renderers/bash_syntax.py +178 -0
- klaude_code/ui/renderers/common.py +78 -0
- klaude_code/ui/renderers/developer.py +104 -48
- klaude_code/ui/renderers/diffs.py +87 -6
- klaude_code/ui/renderers/errors.py +11 -6
- klaude_code/ui/renderers/mermaid_viewer.py +57 -0
- klaude_code/ui/renderers/metadata.py +112 -76
- klaude_code/ui/renderers/sub_agent.py +92 -7
- klaude_code/ui/renderers/thinking.py +40 -18
- klaude_code/ui/renderers/tools.py +405 -227
- klaude_code/ui/renderers/user_input.py +73 -13
- klaude_code/ui/rich/__init__.py +10 -1
- klaude_code/ui/rich/cjk_wrap.py +228 -0
- klaude_code/ui/rich/code_panel.py +131 -0
- klaude_code/ui/rich/live.py +17 -0
- klaude_code/ui/rich/markdown.py +305 -170
- klaude_code/ui/rich/searchable_text.py +10 -13
- klaude_code/ui/rich/status.py +190 -49
- klaude_code/ui/rich/theme.py +135 -39
- klaude_code/ui/terminal/__init__.py +55 -0
- klaude_code/ui/terminal/color.py +1 -1
- klaude_code/ui/terminal/control.py +13 -22
- klaude_code/ui/terminal/notifier.py +44 -4
- klaude_code/ui/terminal/selector.py +658 -0
- klaude_code/ui/utils/common.py +0 -18
- klaude_code-1.8.0.dist-info/METADATA +377 -0
- klaude_code-1.8.0.dist-info/RECORD +219 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
- klaude_code/command/diff_cmd.py +0 -138
- klaude_code/command/prompt-dev-docs-update.md +0 -56
- klaude_code/command/prompt-dev-docs.md +0 -46
- klaude_code/config/list_model.py +0 -162
- klaude_code/core/manager/agent_manager.py +0 -127
- klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
- klaude_code/core/tool/file/multi_edit_tool.md +0 -42
- klaude_code/core/tool/file/multi_edit_tool.py +0 -199
- klaude_code/core/tool/memory/memory_tool.md +0 -16
- klaude_code/core/tool/memory/memory_tool.py +0 -462
- klaude_code/llm/openrouter/reasoning_handler.py +0 -209
- klaude_code/protocol/sub_agent.py +0 -348
- klaude_code/ui/utils/debouncer.py +0 -42
- klaude_code-1.2.6.dist-info/METADATA +0 -178
- klaude_code-1.2.6.dist-info/RECORD +0 -167
- /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
- /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
- /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
|
@@ -1,67 +1,131 @@
|
|
|
1
|
-
from
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from klaude_code.config.config import ModelEntry, load_config, print_no_available_models_hint
|
|
2
4
|
from klaude_code.trace import log
|
|
3
|
-
from klaude_code.ui.rich.searchable_text import SearchableFormattedList
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
def
|
|
7
|
+
def _normalize_model_key(value: str) -> str:
|
|
8
|
+
"""Normalize a model identifier for loose matching.
|
|
9
|
+
|
|
10
|
+
This enables aliases like:
|
|
11
|
+
- gpt52 -> gpt-5.2
|
|
12
|
+
- gpt5.2 -> gpt-5.2
|
|
13
|
+
|
|
14
|
+
Strategy: case-fold + keep only alphanumeric characters.
|
|
7
15
|
"""
|
|
8
|
-
|
|
9
|
-
for
|
|
16
|
+
|
|
17
|
+
return "".join(ch for ch in value.casefold() if ch.isalnum())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ModelMatchResult:
|
|
22
|
+
"""Result of model matching.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
matched_model: The single matched model name, or None if ambiguous/no match.
|
|
26
|
+
filtered_models: List of filtered models for interactive selection.
|
|
27
|
+
filter_hint: The filter hint to show (original preferred value), or None.
|
|
28
|
+
error_message: Error message if no models available, or None.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
matched_model: str | None
|
|
32
|
+
filtered_models: list[ModelEntry]
|
|
33
|
+
filter_hint: str | None
|
|
34
|
+
error_message: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
38
|
+
"""Match model from config without interactive selection.
|
|
39
|
+
|
|
40
|
+
If preferred is provided:
|
|
41
|
+
- Exact match: returns matched_model
|
|
42
|
+
- Single partial match (case-insensitive): returns matched_model
|
|
43
|
+
- Multiple matches: returns filtered_models for interactive selection
|
|
44
|
+
- No matches: returns all models with filter_hint=None
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
ModelMatchResult with match state.
|
|
10
48
|
"""
|
|
11
49
|
config = load_config()
|
|
12
|
-
|
|
13
|
-
models
|
|
50
|
+
|
|
51
|
+
# Only show models from providers with valid API keys
|
|
52
|
+
models: list[ModelEntry] = sorted(
|
|
53
|
+
config.iter_model_entries(only_available=True), key=lambda m: m.model_name.lower()
|
|
54
|
+
)
|
|
14
55
|
|
|
15
56
|
if not models:
|
|
16
|
-
|
|
57
|
+
print_no_available_models_hint()
|
|
58
|
+
return ModelMatchResult(
|
|
59
|
+
matched_model=None,
|
|
60
|
+
filtered_models=[],
|
|
61
|
+
filter_hint=None,
|
|
62
|
+
error_message="No models available",
|
|
63
|
+
)
|
|
17
64
|
|
|
18
65
|
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
66
|
|
|
23
|
-
|
|
24
|
-
|
|
67
|
+
# Try to match preferred model name
|
|
68
|
+
filter_hint = preferred
|
|
69
|
+
if preferred and preferred.strip():
|
|
70
|
+
preferred = preferred.strip()
|
|
71
|
+
# Exact match
|
|
72
|
+
if preferred in names:
|
|
73
|
+
return ModelMatchResult(matched_model=preferred, filtered_models=models, filter_hint=None)
|
|
25
74
|
|
|
26
|
-
|
|
75
|
+
preferred_lower = preferred.lower()
|
|
76
|
+
# Case-insensitive exact match (model_name or model_params.model)
|
|
77
|
+
exact_ci_matches = [
|
|
78
|
+
m
|
|
79
|
+
for m in models
|
|
80
|
+
if preferred_lower == m.model_name.lower() or preferred_lower == (m.model_params.model or "").lower()
|
|
81
|
+
]
|
|
82
|
+
if len(exact_ci_matches) == 1:
|
|
83
|
+
return ModelMatchResult(
|
|
84
|
+
matched_model=exact_ci_matches[0].model_name, filtered_models=models, filter_hint=None
|
|
85
|
+
)
|
|
27
86
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
87
|
+
# Normalized matching (e.g. gpt52 == gpt-5.2, gpt52 in gpt-5.2-2025-...)
|
|
88
|
+
preferred_norm = _normalize_model_key(preferred)
|
|
89
|
+
normalized_matches: list[ModelEntry] = []
|
|
90
|
+
if preferred_norm:
|
|
91
|
+
normalized_matches = [
|
|
92
|
+
m
|
|
93
|
+
for m in models
|
|
94
|
+
if preferred_norm == _normalize_model_key(m.model_name)
|
|
95
|
+
or preferred_norm == _normalize_model_key(m.model_params.model or "")
|
|
35
96
|
]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return None
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
97
|
+
if len(normalized_matches) == 1:
|
|
98
|
+
return ModelMatchResult(
|
|
99
|
+
matched_model=normalized_matches[0].model_name, filtered_models=models, filter_hint=None
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if not normalized_matches and len(preferred_norm) >= 4:
|
|
103
|
+
normalized_matches = [
|
|
104
|
+
m
|
|
105
|
+
for m in models
|
|
106
|
+
if preferred_norm in _normalize_model_key(m.model_name)
|
|
107
|
+
or preferred_norm in _normalize_model_key(m.model_params.model or "")
|
|
108
|
+
]
|
|
109
|
+
if len(normalized_matches) == 1:
|
|
110
|
+
return ModelMatchResult(
|
|
111
|
+
matched_model=normalized_matches[0].model_name, filtered_models=models, filter_hint=None
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Partial match (case-insensitive) on model_name or model_params.model.
|
|
115
|
+
# If normalized matching found candidates (even if multiple), prefer those as the filter set.
|
|
116
|
+
matches = normalized_matches or [
|
|
117
|
+
m
|
|
118
|
+
for m in models
|
|
119
|
+
if preferred_lower in m.model_name.lower() or preferred_lower in (m.model_params.model or "").lower()
|
|
120
|
+
]
|
|
121
|
+
if len(matches) == 1:
|
|
122
|
+
return ModelMatchResult(matched_model=matches[0].model_name, filtered_models=models, filter_hint=None)
|
|
123
|
+
if matches:
|
|
124
|
+
# Multiple matches: filter the list for interactive selection
|
|
125
|
+
return ModelMatchResult(matched_model=None, filtered_models=matches, filter_hint=filter_hint)
|
|
126
|
+
else:
|
|
127
|
+
# No matches: show all models without filter hint
|
|
128
|
+
log(("No matching models found. Showing all models.", "yellow"))
|
|
129
|
+
return ModelMatchResult(matched_model=None, filtered_models=models, filter_hint=None)
|
|
130
|
+
|
|
131
|
+
return ModelMatchResult(matched_model=None, filtered_models=models, filter_hint=None)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Thinking level configuration data and helpers.
|
|
2
|
+
|
|
3
|
+
This module contains thinking level definitions and helper functions
|
|
4
|
+
that are shared between command layer and UI layer.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from klaude_code.protocol import llm_param
|
|
11
|
+
|
|
12
|
+
ReasoningEffort = Literal["high", "medium", "low", "minimal", "none", "xhigh"]
|
|
13
|
+
|
|
14
|
+
# Thinking level options for different protocols
|
|
15
|
+
RESPONSES_LEVELS = ["low", "medium", "high"]
|
|
16
|
+
RESPONSES_GPT51_LEVELS = ["none", "low", "medium", "high"]
|
|
17
|
+
RESPONSES_GPT52_LEVELS = ["none", "low", "medium", "high", "xhigh"]
|
|
18
|
+
RESPONSES_CODEX_MAX_LEVELS = ["medium", "high", "xhigh"]
|
|
19
|
+
RESPONSES_GEMINI_FLASH_LEVELS = ["minimal", "low", "medium", "high"]
|
|
20
|
+
|
|
21
|
+
ANTHROPIC_LEVELS: list[tuple[str, int | None]] = [
|
|
22
|
+
("off", 0),
|
|
23
|
+
("low (2048 tokens)", 2048),
|
|
24
|
+
("medium (8192 tokens)", 8192),
|
|
25
|
+
("high (31999 tokens)", 31999),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_openrouter_model_with_reasoning_effort(model_name: str | None) -> bool:
|
|
30
|
+
"""Check if the model is GPT series, Grok or Gemini 3."""
|
|
31
|
+
if not model_name:
|
|
32
|
+
return False
|
|
33
|
+
model_lower = model_name.lower()
|
|
34
|
+
return model_lower.startswith(("openai/gpt-", "x-ai/grok-", "google/gemini-3"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_gpt51_model(model_name: str | None) -> bool:
|
|
38
|
+
"""Check if the model is GPT-5.1."""
|
|
39
|
+
if not model_name:
|
|
40
|
+
return False
|
|
41
|
+
return model_name.lower() in ["gpt-5.1", "openai/gpt-5.1", "gpt-5.1-codex-2025-11-13"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _is_gpt52_model(model_name: str | None) -> bool:
|
|
45
|
+
"""Check if the model is GPT-5.2."""
|
|
46
|
+
if not model_name:
|
|
47
|
+
return False
|
|
48
|
+
return model_name.lower() in ["gpt-5.2", "openai/gpt-5.2"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _is_codex_max_model(model_name: str | None) -> bool:
|
|
52
|
+
"""Check if the model is GPT-5.1-codex-max."""
|
|
53
|
+
if not model_name:
|
|
54
|
+
return False
|
|
55
|
+
return "codex-max" in model_name.lower()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _is_gemini_flash_model(model_name: str | None) -> bool:
|
|
59
|
+
"""Check if the model is Gemini 3 Flash."""
|
|
60
|
+
if not model_name:
|
|
61
|
+
return False
|
|
62
|
+
return "gemini-3-flash" in model_name.lower()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def should_auto_trigger_thinking(model_name: str | None) -> bool:
|
|
66
|
+
"""Check if model should auto-trigger thinking selection on switch."""
|
|
67
|
+
if not model_name:
|
|
68
|
+
return False
|
|
69
|
+
model_lower = model_name.lower()
|
|
70
|
+
return "gpt-5" in model_lower or "gemini-3" in model_lower or "opus" in model_lower
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_levels_for_responses(model_name: str | None) -> list[str]:
|
|
74
|
+
"""Get thinking levels for responses protocol."""
|
|
75
|
+
if _is_codex_max_model(model_name):
|
|
76
|
+
return RESPONSES_CODEX_MAX_LEVELS
|
|
77
|
+
if _is_gpt52_model(model_name):
|
|
78
|
+
return RESPONSES_GPT52_LEVELS
|
|
79
|
+
if _is_gpt51_model(model_name):
|
|
80
|
+
return RESPONSES_GPT51_LEVELS
|
|
81
|
+
if _is_gemini_flash_model(model_name):
|
|
82
|
+
return RESPONSES_GEMINI_FLASH_LEVELS
|
|
83
|
+
return RESPONSES_LEVELS
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def format_current_thinking(config: llm_param.LLMConfigParameter) -> str:
|
|
87
|
+
"""Format the current thinking configuration for display."""
|
|
88
|
+
thinking = config.thinking
|
|
89
|
+
if not thinking:
|
|
90
|
+
return "not configured"
|
|
91
|
+
|
|
92
|
+
protocol = config.protocol
|
|
93
|
+
|
|
94
|
+
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX):
|
|
95
|
+
if thinking.reasoning_effort:
|
|
96
|
+
return f"reasoning_effort={thinking.reasoning_effort}"
|
|
97
|
+
return "not set"
|
|
98
|
+
|
|
99
|
+
if protocol == llm_param.LLMClientProtocol.ANTHROPIC:
|
|
100
|
+
if thinking.type == "disabled":
|
|
101
|
+
return "off"
|
|
102
|
+
if thinking.type == "enabled":
|
|
103
|
+
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
104
|
+
return "not set"
|
|
105
|
+
|
|
106
|
+
if protocol == llm_param.LLMClientProtocol.OPENROUTER:
|
|
107
|
+
if is_openrouter_model_with_reasoning_effort(config.model):
|
|
108
|
+
if thinking.reasoning_effort:
|
|
109
|
+
return f"reasoning_effort={thinking.reasoning_effort}"
|
|
110
|
+
else:
|
|
111
|
+
if thinking.type == "disabled":
|
|
112
|
+
return "off"
|
|
113
|
+
if thinking.type == "enabled":
|
|
114
|
+
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
115
|
+
return "not set"
|
|
116
|
+
|
|
117
|
+
if protocol == llm_param.LLMClientProtocol.OPENAI:
|
|
118
|
+
if thinking.type == "disabled":
|
|
119
|
+
return "off"
|
|
120
|
+
if thinking.type == "enabled":
|
|
121
|
+
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
122
|
+
return "not set"
|
|
123
|
+
|
|
124
|
+
if protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
125
|
+
if thinking.type == "disabled":
|
|
126
|
+
return "off"
|
|
127
|
+
if thinking.type == "enabled":
|
|
128
|
+
return f"enabled (budget_tokens={thinking.budget_tokens})"
|
|
129
|
+
return "not set"
|
|
130
|
+
|
|
131
|
+
return "unknown protocol"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# Thinking picker data structures
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class ThinkingOption:
|
|
141
|
+
"""A thinking option for selection.
|
|
142
|
+
|
|
143
|
+
Attributes:
|
|
144
|
+
label: Display label for this option (e.g., "low", "medium (8192 tokens)").
|
|
145
|
+
value: Encoded value string (e.g., "effort:low", "budget:2048").
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
label: str
|
|
149
|
+
value: str
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class ThinkingPickerData:
|
|
154
|
+
"""Data for building thinking picker UI.
|
|
155
|
+
|
|
156
|
+
Attributes:
|
|
157
|
+
options: List of thinking options.
|
|
158
|
+
message: Prompt message (e.g., "Select reasoning effort:").
|
|
159
|
+
current_value: Currently selected value, or None.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
options: list[ThinkingOption]
|
|
163
|
+
message: str
|
|
164
|
+
current_value: str | None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _build_effort_options(levels: list[str]) -> list[ThinkingOption]:
|
|
168
|
+
"""Build effort-based thinking options."""
|
|
169
|
+
return [ThinkingOption(label=level, value=f"effort:{level}") for level in levels]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _build_budget_options() -> list[ThinkingOption]:
|
|
173
|
+
"""Build budget-based thinking options."""
|
|
174
|
+
return [ThinkingOption(label=label, value=f"budget:{tokens or 0}") for label, tokens in ANTHROPIC_LEVELS]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _get_current_effort_value(thinking: llm_param.Thinking | None) -> str | None:
|
|
178
|
+
"""Get current value for effort-based thinking."""
|
|
179
|
+
if thinking and thinking.reasoning_effort:
|
|
180
|
+
return f"effort:{thinking.reasoning_effort}"
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _get_current_budget_value(thinking: llm_param.Thinking | None) -> str | None:
|
|
185
|
+
"""Get current value for budget-based thinking."""
|
|
186
|
+
if thinking:
|
|
187
|
+
if thinking.type == "disabled":
|
|
188
|
+
return "budget:0"
|
|
189
|
+
if thinking.budget_tokens:
|
|
190
|
+
return f"budget:{thinking.budget_tokens}"
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_thinking_picker_data(config: llm_param.LLMConfigParameter) -> ThinkingPickerData | None:
|
|
195
|
+
"""Get thinking picker data based on LLM config.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
ThinkingPickerData with options and current value, or None if protocol doesn't support thinking.
|
|
199
|
+
"""
|
|
200
|
+
protocol = config.protocol
|
|
201
|
+
model_name = config.model
|
|
202
|
+
thinking = config.thinking
|
|
203
|
+
|
|
204
|
+
if protocol in (llm_param.LLMClientProtocol.RESPONSES, llm_param.LLMClientProtocol.CODEX):
|
|
205
|
+
levels = get_levels_for_responses(model_name)
|
|
206
|
+
return ThinkingPickerData(
|
|
207
|
+
options=_build_effort_options(levels),
|
|
208
|
+
message="Select reasoning effort:",
|
|
209
|
+
current_value=_get_current_effort_value(thinking),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if protocol == llm_param.LLMClientProtocol.ANTHROPIC:
|
|
213
|
+
return ThinkingPickerData(
|
|
214
|
+
options=_build_budget_options(),
|
|
215
|
+
message="Select thinking level:",
|
|
216
|
+
current_value=_get_current_budget_value(thinking),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if protocol == llm_param.LLMClientProtocol.OPENROUTER:
|
|
220
|
+
if is_openrouter_model_with_reasoning_effort(model_name):
|
|
221
|
+
levels = get_levels_for_responses(model_name)
|
|
222
|
+
return ThinkingPickerData(
|
|
223
|
+
options=_build_effort_options(levels),
|
|
224
|
+
message="Select reasoning effort:",
|
|
225
|
+
current_value=_get_current_effort_value(thinking),
|
|
226
|
+
)
|
|
227
|
+
return ThinkingPickerData(
|
|
228
|
+
options=_build_budget_options(),
|
|
229
|
+
message="Select thinking level:",
|
|
230
|
+
current_value=_get_current_budget_value(thinking),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if protocol == llm_param.LLMClientProtocol.OPENAI:
|
|
234
|
+
return ThinkingPickerData(
|
|
235
|
+
options=_build_budget_options(),
|
|
236
|
+
message="Select thinking level:",
|
|
237
|
+
current_value=_get_current_budget_value(thinking),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if protocol == llm_param.LLMClientProtocol.GOOGLE:
|
|
241
|
+
return ThinkingPickerData(
|
|
242
|
+
options=_build_budget_options(),
|
|
243
|
+
message="Select thinking level:",
|
|
244
|
+
current_value=_get_current_budget_value(thinking),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def parse_thinking_value(value: str) -> llm_param.Thinking | None:
|
|
251
|
+
"""Parse a thinking value string into a Thinking object.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
value: Encoded value string (e.g., "effort:low", "budget:2048").
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Thinking object, or None if invalid format.
|
|
258
|
+
"""
|
|
259
|
+
if value.startswith("effort:"):
|
|
260
|
+
effort = value[7:]
|
|
261
|
+
return llm_param.Thinking(reasoning_effort=effort) # type: ignore[arg-type]
|
|
262
|
+
|
|
263
|
+
if value.startswith("budget:"):
|
|
264
|
+
budget = int(value[7:])
|
|
265
|
+
if budget == 0:
|
|
266
|
+
return llm_param.Thinking(type="disabled", budget_tokens=0)
|
|
267
|
+
return llm_param.Thinking(type="enabled", budget_tokens=budget)
|
|
268
|
+
|
|
269
|
+
return None
|
|
@@ -4,6 +4,21 @@ This module consolidates all magic numbers and configuration values
|
|
|
4
4
|
that were previously scattered across the codebase.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_int_env(name: str, default: int) -> int:
|
|
12
|
+
"""Get an integer value from environment variable, or return default."""
|
|
13
|
+
val = os.environ.get(name)
|
|
14
|
+
if val is None:
|
|
15
|
+
return default
|
|
16
|
+
try:
|
|
17
|
+
return int(val)
|
|
18
|
+
except ValueError:
|
|
19
|
+
return default
|
|
20
|
+
|
|
21
|
+
|
|
7
22
|
# =============================================================================
|
|
8
23
|
# Agent Configuration
|
|
9
24
|
# =============================================================================
|
|
@@ -45,13 +60,12 @@ TODO_REMINDER_TOOL_CALL_THRESHOLD = 10
|
|
|
45
60
|
READ_CHAR_LIMIT_PER_LINE = 2000
|
|
46
61
|
|
|
47
62
|
# Maximum number of lines to read from a file
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# Maximum total characters to read
|
|
51
|
-
READ_MAX_CHARS = 60000
|
|
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)
|
|
52
65
|
|
|
53
|
-
# Maximum
|
|
54
|
-
|
|
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)
|
|
55
69
|
|
|
56
70
|
# Maximum image file size in bytes (4MB)
|
|
57
71
|
READ_MAX_IMAGE_BYTES = 4 * 1024 * 1024
|
|
@@ -62,7 +76,7 @@ BASH_DEFAULT_TIMEOUT_MS = 120000
|
|
|
62
76
|
|
|
63
77
|
# -- Tool Output --
|
|
64
78
|
# Maximum length for tool output before truncation
|
|
65
|
-
TOOL_OUTPUT_MAX_LENGTH =
|
|
79
|
+
TOOL_OUTPUT_MAX_LENGTH = 40000
|
|
66
80
|
|
|
67
81
|
# Characters to show from the beginning of truncated output
|
|
68
82
|
TOOL_OUTPUT_DISPLAY_HEAD = 10000
|
|
@@ -91,40 +105,57 @@ INVALID_TOOL_CALL_MAX_LENGTH = 500
|
|
|
91
105
|
TRUNCATE_DISPLAY_MAX_LINE_LENGTH = 1000
|
|
92
106
|
|
|
93
107
|
# Maximum lines for truncated display output
|
|
94
|
-
TRUNCATE_DISPLAY_MAX_LINES =
|
|
108
|
+
TRUNCATE_DISPLAY_MAX_LINES = 8
|
|
95
109
|
|
|
96
110
|
# Maximum lines for sub-agent result display
|
|
97
|
-
SUB_AGENT_RESULT_MAX_LINES =
|
|
111
|
+
SUB_AGENT_RESULT_MAX_LINES = 50
|
|
98
112
|
|
|
99
113
|
|
|
100
114
|
# UI refresh rate (frames per second) for debounced content streaming
|
|
101
|
-
UI_REFRESH_RATE_FPS =
|
|
115
|
+
UI_REFRESH_RATE_FPS = 10
|
|
116
|
+
|
|
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
|
|
102
120
|
|
|
103
121
|
# Number of lines to keep visible at bottom of markdown streaming window
|
|
104
|
-
MARKDOWN_STREAM_LIVE_WINDOW =
|
|
122
|
+
MARKDOWN_STREAM_LIVE_WINDOW = 6
|
|
123
|
+
|
|
124
|
+
# Left margin (columns) to reserve when rendering markdown
|
|
125
|
+
MARKDOWN_LEFT_MARGIN = 2
|
|
126
|
+
|
|
127
|
+
# Right margin (columns) to reserve when rendering markdown
|
|
128
|
+
MARKDOWN_RIGHT_MARGIN = 2
|
|
129
|
+
|
|
130
|
+
# Status hint text shown after spinner status
|
|
131
|
+
STATUS_HINT_TEXT = " (esc to interrupt)"
|
|
132
|
+
|
|
133
|
+
# Default spinner status text when idle/thinking
|
|
134
|
+
STATUS_DEFAULT_TEXT = "Thinking …"
|
|
105
135
|
|
|
106
136
|
# Status shimmer animation
|
|
107
137
|
# Horizontal padding used when computing shimmer band position
|
|
108
138
|
STATUS_SHIMMER_PADDING = 10
|
|
109
|
-
# Duration in seconds for one full shimmer sweep across the text
|
|
110
|
-
STATUS_SHIMMER_SWEEP_SECONDS = 2
|
|
111
139
|
# Half-width of the shimmer band in characters
|
|
112
140
|
STATUS_SHIMMER_BAND_HALF_WIDTH = 5.0
|
|
113
141
|
# Scale factor applied to shimmer intensity when blending colors
|
|
114
142
|
STATUS_SHIMMER_ALPHA_SCALE = 0.7
|
|
115
143
|
|
|
116
|
-
# Spinner breathing animation
|
|
117
|
-
# Duration in seconds for one full breathe-in + breathe-out cycle
|
|
118
|
-
#
|
|
119
|
-
SPINNER_BREATH_PERIOD_SECONDS = 2
|
|
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
|
|
120
148
|
|
|
121
149
|
|
|
122
150
|
# =============================================================================
|
|
123
151
|
# Debug / Logging
|
|
124
152
|
# =============================================================================
|
|
125
153
|
|
|
126
|
-
# Default debug log
|
|
127
|
-
|
|
154
|
+
# Default debug log directory (user cache)
|
|
155
|
+
DEFAULT_DEBUG_LOG_DIR = Path.home() / ".klaude" / "logs"
|
|
156
|
+
|
|
157
|
+
# Default debug log file path (symlink to latest session)
|
|
158
|
+
DEFAULT_DEBUG_LOG_FILE = DEFAULT_DEBUG_LOG_DIR / "debug.log"
|
|
128
159
|
|
|
129
160
|
# Maximum log file size before rotation (10MB)
|
|
130
161
|
LOG_MAX_BYTES = 10 * 1024 * 1024
|
klaude_code/core/agent.py
CHANGED
|
@@ -4,10 +4,10 @@ from collections.abc import AsyncGenerator, Iterable
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import Protocol
|
|
6
6
|
|
|
7
|
-
from klaude_code.core.prompt import
|
|
7
|
+
from klaude_code.core.prompt import load_system_prompt
|
|
8
8
|
from klaude_code.core.reminders import Reminder, load_agent_reminders
|
|
9
|
-
from klaude_code.core.task import TaskExecutionContext, TaskExecutor
|
|
10
|
-
from klaude_code.core.tool import
|
|
9
|
+
from klaude_code.core.task import SessionContext, TaskExecutionContext, TaskExecutor
|
|
10
|
+
from klaude_code.core.tool import build_todo_context, get_registry, load_agent_tools
|
|
11
11
|
from klaude_code.llm import LLMClientABC
|
|
12
12
|
from klaude_code.protocol import events, llm_param, model, tools
|
|
13
13
|
from klaude_code.protocol.model import UserInputPayload
|
|
@@ -46,7 +46,7 @@ class DefaultModelProfileProvider(ModelProfileProvider):
|
|
|
46
46
|
model_name = llm_client.model_name
|
|
47
47
|
return AgentProfile(
|
|
48
48
|
llm_client=llm_client,
|
|
49
|
-
system_prompt=load_system_prompt(model_name, sub_agent_type),
|
|
49
|
+
system_prompt=load_system_prompt(model_name, llm_client.protocol, sub_agent_type),
|
|
50
50
|
tools=load_agent_tools(model_name, sub_agent_type),
|
|
51
51
|
reminders=load_agent_reminders(model_name, sub_agent_type),
|
|
52
52
|
)
|
|
@@ -76,11 +76,10 @@ class Agent:
|
|
|
76
76
|
profile: AgentProfile,
|
|
77
77
|
):
|
|
78
78
|
self.session: Session = session
|
|
79
|
-
self.profile: AgentProfile
|
|
80
|
-
# Active task executor, if any
|
|
79
|
+
self.profile: AgentProfile = profile
|
|
81
80
|
self._current_task: TaskExecutor | None = None
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
if not self.session.model_name:
|
|
82
|
+
self.session.model_name = profile.llm_client.model_name
|
|
84
83
|
|
|
85
84
|
def cancel(self) -> Iterable[events.Event]:
|
|
86
85
|
"""Handle agent cancellation and persist an interrupt marker and tool cancellations.
|
|
@@ -93,8 +92,7 @@ class Agent:
|
|
|
93
92
|
"""
|
|
94
93
|
# First, cancel any running task so it stops emitting events.
|
|
95
94
|
if self._current_task is not None:
|
|
96
|
-
|
|
97
|
-
yield ui_event
|
|
95
|
+
yield from self._current_task.cancel()
|
|
98
96
|
self._current_task = None
|
|
99
97
|
|
|
100
98
|
# Record an interrupt marker in the session history
|
|
@@ -105,18 +103,18 @@ class Agent:
|
|
|
105
103
|
debug_type=DebugType.EXECUTION,
|
|
106
104
|
)
|
|
107
105
|
|
|
108
|
-
async def run_task(self, user_input: UserInputPayload) -> AsyncGenerator[events.Event
|
|
109
|
-
|
|
106
|
+
async def run_task(self, user_input: UserInputPayload) -> AsyncGenerator[events.Event]:
|
|
107
|
+
session_ctx = SessionContext(
|
|
110
108
|
session_id=self.session.id,
|
|
111
|
-
profile=self._require_profile(),
|
|
112
109
|
get_conversation_history=lambda: self.session.conversation_history,
|
|
113
110
|
append_history=self.session.append_history,
|
|
114
|
-
tool_registry=get_registry(),
|
|
115
111
|
file_tracker=self.session.file_tracker,
|
|
116
|
-
todo_context=
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
todo_context=build_todo_context(self.session),
|
|
113
|
+
)
|
|
114
|
+
context = TaskExecutionContext(
|
|
115
|
+
session_ctx=session_ctx,
|
|
116
|
+
profile=self.profile,
|
|
117
|
+
tool_registry=get_registry(),
|
|
120
118
|
process_reminder=self._process_reminder,
|
|
121
119
|
sub_agent_state=self.session.sub_agent_state,
|
|
122
120
|
)
|
|
@@ -130,7 +128,7 @@ class Agent:
|
|
|
130
128
|
finally:
|
|
131
129
|
self._current_task = None
|
|
132
130
|
|
|
133
|
-
async def replay_history(self) -> AsyncGenerator[events.Event
|
|
131
|
+
async def replay_history(self) -> AsyncGenerator[events.Event]:
|
|
134
132
|
"""Yield UI events reconstructed from saved conversation history."""
|
|
135
133
|
|
|
136
134
|
if len(self.session.conversation_history) == 0:
|
|
@@ -142,7 +140,7 @@ class Agent:
|
|
|
142
140
|
session_id=self.session.id,
|
|
143
141
|
)
|
|
144
142
|
|
|
145
|
-
async def _process_reminder(self, reminder: Reminder) -> AsyncGenerator[events.DeveloperMessageEvent
|
|
143
|
+
async def _process_reminder(self, reminder: Reminder) -> AsyncGenerator[events.DeveloperMessageEvent]:
|
|
146
144
|
"""Process a single reminder and yield events if it produces output."""
|
|
147
145
|
item = await reminder(self.session)
|
|
148
146
|
if item is not None:
|
|
@@ -153,13 +151,7 @@ class Agent:
|
|
|
153
151
|
"""Apply a fully constructed profile to the agent."""
|
|
154
152
|
|
|
155
153
|
self.profile = profile
|
|
156
|
-
|
|
157
|
-
self.session.model_name = profile.llm_client.model_name
|
|
154
|
+
self.session.model_name = profile.llm_client.model_name
|
|
158
155
|
|
|
159
156
|
def get_llm_client(self) -> LLMClientABC:
|
|
160
|
-
return self.
|
|
161
|
-
|
|
162
|
-
def _require_profile(self) -> AgentProfile:
|
|
163
|
-
if self.profile is None:
|
|
164
|
-
raise RuntimeError("Agent profile is not initialized")
|
|
165
|
-
return self.profile
|
|
157
|
+
return self.profile.llm_client
|