klaude-code 2.5.2__py3-none-any.whl → 2.6.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 +10 -0
- klaude_code/auth/env.py +77 -0
- klaude_code/cli/auth_cmd.py +89 -21
- klaude_code/cli/config_cmd.py +5 -5
- klaude_code/cli/cost_cmd.py +167 -68
- klaude_code/cli/main.py +51 -27
- klaude_code/cli/self_update.py +7 -7
- klaude_code/config/assets/builtin_config.yaml +45 -24
- klaude_code/config/builtin_config.py +23 -9
- klaude_code/config/config.py +19 -9
- klaude_code/config/model_matcher.py +1 -1
- klaude_code/const.py +2 -1
- klaude_code/core/tool/file/edit_tool.py +1 -1
- klaude_code/core/tool/file/read_tool.py +2 -2
- klaude_code/core/tool/file/write_tool.py +1 -1
- klaude_code/core/turn.py +21 -4
- klaude_code/llm/anthropic/client.py +75 -50
- klaude_code/llm/anthropic/input.py +20 -9
- klaude_code/llm/google/client.py +235 -148
- klaude_code/llm/google/input.py +44 -36
- klaude_code/llm/openai_compatible/stream.py +114 -100
- klaude_code/llm/openrouter/client.py +1 -0
- klaude_code/llm/openrouter/reasoning.py +4 -29
- klaude_code/llm/partial_message.py +2 -32
- klaude_code/llm/responses/client.py +99 -81
- klaude_code/llm/responses/input.py +11 -25
- klaude_code/llm/stream_parts.py +94 -0
- klaude_code/log.py +57 -0
- klaude_code/protocol/events.py +214 -0
- klaude_code/protocol/sub_agent/image_gen.py +0 -4
- klaude_code/session/session.py +51 -18
- klaude_code/tui/command/fork_session_cmd.py +14 -23
- klaude_code/tui/command/model_picker.py +2 -17
- klaude_code/tui/command/resume_cmd.py +2 -18
- klaude_code/tui/command/sub_agent_model_cmd.py +5 -19
- klaude_code/tui/command/thinking_cmd.py +2 -14
- klaude_code/tui/commands.py +0 -5
- klaude_code/tui/components/common.py +1 -1
- klaude_code/tui/components/metadata.py +21 -21
- klaude_code/tui/components/rich/quote.py +36 -8
- klaude_code/tui/components/rich/theme.py +2 -0
- klaude_code/tui/components/sub_agent.py +6 -0
- klaude_code/tui/display.py +11 -1
- klaude_code/tui/input/completers.py +11 -7
- klaude_code/tui/input/prompt_toolkit.py +3 -1
- klaude_code/tui/machine.py +108 -56
- klaude_code/tui/renderer.py +4 -65
- klaude_code/tui/terminal/selector.py +174 -31
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/METADATA +23 -31
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/RECORD +52 -58
- klaude_code/cli/session_cmd.py +0 -96
- klaude_code/protocol/events/__init__.py +0 -63
- klaude_code/protocol/events/base.py +0 -18
- klaude_code/protocol/events/chat.py +0 -30
- klaude_code/protocol/events/lifecycle.py +0 -23
- klaude_code/protocol/events/metadata.py +0 -16
- klaude_code/protocol/events/streaming.py +0 -43
- klaude_code/protocol/events/system.py +0 -56
- klaude_code/protocol/events/tools.py +0 -27
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/entry_points.txt +0 -0
klaude_code/cli/main.py
CHANGED
|
@@ -7,19 +7,38 @@ from klaude_code.cli.auth_cmd import register_auth_commands
|
|
|
7
7
|
from klaude_code.cli.config_cmd import register_config_commands
|
|
8
8
|
from klaude_code.cli.cost_cmd import register_cost_commands
|
|
9
9
|
from klaude_code.cli.debug import DEBUG_FILTER_HELP, prepare_debug_logging
|
|
10
|
-
from klaude_code.cli.self_update import
|
|
11
|
-
from klaude_code.cli.session_cmd import register_session_commands
|
|
10
|
+
from klaude_code.cli.self_update import register_self_upgrade_commands, version_option_callback
|
|
12
11
|
from klaude_code.session import Session
|
|
13
12
|
from klaude_code.tui.command.resume_cmd import select_session_sync
|
|
14
13
|
from klaude_code.ui.terminal.title import update_terminal_title
|
|
15
14
|
|
|
16
|
-
ENV_HELP = """\
|
|
17
|
-
Environment Variables:
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
def _build_env_help() -> str:
|
|
17
|
+
from klaude_code.config.builtin_config import SUPPORTED_API_KEYS
|
|
18
|
+
|
|
19
|
+
lines = [
|
|
20
|
+
"Environment Variables:",
|
|
21
|
+
"",
|
|
22
|
+
"Provider API keys (built-in config):",
|
|
23
|
+
]
|
|
24
|
+
# Calculate max env_var length for alignment
|
|
25
|
+
max_len = max(len(k.env_var) for k in SUPPORTED_API_KEYS)
|
|
26
|
+
for k in SUPPORTED_API_KEYS:
|
|
27
|
+
lines.append(f" {k.env_var:<{max_len}} {k.description}")
|
|
28
|
+
lines.extend(
|
|
29
|
+
[
|
|
30
|
+
"",
|
|
31
|
+
"Tool limits (Read):",
|
|
32
|
+
" KLAUDE_READ_GLOBAL_LINE_CAP Max lines to read (default: 2000)",
|
|
33
|
+
" KLAUDE_READ_MAX_CHARS Max total chars to read (default: 50000)",
|
|
34
|
+
" KLAUDE_READ_MAX_IMAGE_BYTES Max image bytes to read (default: 4MB)",
|
|
35
|
+
" KLAUDE_IMAGE_OUTPUT_MAX_BYTES Max decoded image bytes (default: 64MB)",
|
|
36
|
+
]
|
|
37
|
+
)
|
|
38
|
+
return "\n\n".join(lines)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
ENV_HELP = _build_env_help()
|
|
23
42
|
|
|
24
43
|
app = typer.Typer(
|
|
25
44
|
add_completion=False,
|
|
@@ -27,55 +46,51 @@ app = typer.Typer(
|
|
|
27
46
|
no_args_is_help=False,
|
|
28
47
|
rich_markup_mode="rich",
|
|
29
48
|
epilog=ENV_HELP,
|
|
49
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
30
50
|
)
|
|
31
51
|
|
|
32
52
|
# Register subcommands from modules
|
|
33
|
-
register_session_commands(app)
|
|
34
53
|
register_auth_commands(app)
|
|
35
54
|
register_config_commands(app)
|
|
36
55
|
register_cost_commands(app)
|
|
56
|
+
register_self_upgrade_commands(app)
|
|
57
|
+
|
|
37
58
|
|
|
38
|
-
|
|
59
|
+
@app.command("help", hidden=True)
|
|
60
|
+
def help_command(ctx: typer.Context) -> None:
|
|
61
|
+
"""Show help message."""
|
|
62
|
+
print(ctx.parent.get_help() if ctx.parent else ctx.get_help())
|
|
39
63
|
|
|
40
64
|
|
|
41
65
|
@app.callback(invoke_without_command=True)
|
|
42
66
|
def main_callback(
|
|
43
67
|
ctx: typer.Context,
|
|
44
|
-
version: bool = typer.Option(
|
|
45
|
-
False,
|
|
46
|
-
"--version",
|
|
47
|
-
"-V",
|
|
48
|
-
"-v",
|
|
49
|
-
help="Show version and exit",
|
|
50
|
-
callback=version_option_callback,
|
|
51
|
-
is_eager=True,
|
|
52
|
-
),
|
|
53
68
|
model: str | None = typer.Option(
|
|
54
69
|
None,
|
|
55
70
|
"--model",
|
|
56
71
|
"-m",
|
|
57
|
-
help="
|
|
72
|
+
help="Select model by name",
|
|
58
73
|
rich_help_panel="LLM",
|
|
59
74
|
),
|
|
60
|
-
continue_: bool = typer.Option(False, "--continue", "-c", help="
|
|
61
|
-
resume: bool = typer.Option(False, "--resume", "-r", help="
|
|
75
|
+
continue_: bool = typer.Option(False, "--continue", "-c", help="Resume latest session"),
|
|
76
|
+
resume: bool = typer.Option(False, "--resume", "-r", help="Pick a session to resume"),
|
|
62
77
|
resume_by_id: str | None = typer.Option(
|
|
63
78
|
None,
|
|
64
79
|
"--resume-by-id",
|
|
65
|
-
help="Resume
|
|
80
|
+
help="Resume session by ID",
|
|
66
81
|
),
|
|
67
82
|
select_model: bool = typer.Option(
|
|
68
83
|
False,
|
|
69
84
|
"--select-model",
|
|
70
85
|
"-s",
|
|
71
|
-
help="
|
|
86
|
+
help="Choose model interactively",
|
|
72
87
|
rich_help_panel="LLM",
|
|
73
88
|
),
|
|
74
89
|
debug: bool = typer.Option(
|
|
75
90
|
False,
|
|
76
91
|
"--debug",
|
|
77
92
|
"-d",
|
|
78
|
-
help="Enable debug
|
|
93
|
+
help="Enable debug logging",
|
|
79
94
|
rich_help_panel="Debug",
|
|
80
95
|
),
|
|
81
96
|
debug_filter: str | None = typer.Option(
|
|
@@ -87,14 +102,23 @@ def main_callback(
|
|
|
87
102
|
vanilla: bool = typer.Option(
|
|
88
103
|
False,
|
|
89
104
|
"--vanilla",
|
|
90
|
-
help="
|
|
105
|
+
help="Minimal mode: basic tools only, no system prompts",
|
|
91
106
|
),
|
|
92
107
|
banana: bool = typer.Option(
|
|
93
108
|
False,
|
|
94
109
|
"--banana",
|
|
95
|
-
help="Image generation mode
|
|
110
|
+
help="Image generation mode",
|
|
96
111
|
rich_help_panel="LLM",
|
|
97
112
|
),
|
|
113
|
+
version: bool = typer.Option(
|
|
114
|
+
False,
|
|
115
|
+
"--version",
|
|
116
|
+
"-V",
|
|
117
|
+
"-v",
|
|
118
|
+
help="Show version and exit",
|
|
119
|
+
callback=version_option_callback,
|
|
120
|
+
is_eager=True,
|
|
121
|
+
),
|
|
98
122
|
) -> None:
|
|
99
123
|
# Only run interactive mode when no subcommand is invoked
|
|
100
124
|
if ctx.invoked_subcommand is None:
|
klaude_code/cli/self_update.py
CHANGED
|
@@ -35,14 +35,14 @@ def version_command() -> None:
|
|
|
35
35
|
_print_version()
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def upgrade_command(
|
|
39
39
|
check: bool = typer.Option(
|
|
40
40
|
False,
|
|
41
41
|
"--check",
|
|
42
|
-
help="Check
|
|
42
|
+
help="Check only, don't upgrade",
|
|
43
43
|
),
|
|
44
44
|
) -> None:
|
|
45
|
-
"""Upgrade
|
|
45
|
+
"""Upgrade to latest version"""
|
|
46
46
|
|
|
47
47
|
info = check_for_updates_blocking()
|
|
48
48
|
|
|
@@ -79,9 +79,9 @@ def update_command(
|
|
|
79
79
|
log("Update complete. Please re-run `klaude` to use the new version.")
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def
|
|
82
|
+
def register_self_upgrade_commands(app: typer.Typer) -> None:
|
|
83
83
|
"""Register self-update and version subcommands to the given Typer app."""
|
|
84
84
|
|
|
85
|
-
app.command("
|
|
86
|
-
app.command("
|
|
87
|
-
app.command("version",
|
|
85
|
+
app.command("upgrade")(upgrade_command)
|
|
86
|
+
app.command("update", hidden=True)(upgrade_command)
|
|
87
|
+
app.command("version", hidden=True)(version_command)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
---
|
|
2
1
|
# Built-in provider and model configurations
|
|
3
2
|
# Users can start using klaude by simply setting environment variables
|
|
4
3
|
# (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) without manual configuration.
|
|
@@ -25,32 +24,58 @@ provider_list:
|
|
|
25
24
|
protocol: responses
|
|
26
25
|
api_key: ${OPENAI_API_KEY}
|
|
27
26
|
model_list:
|
|
28
|
-
- model_name: gpt-5.2
|
|
27
|
+
- model_name: gpt-5.2-high
|
|
29
28
|
model_id: gpt-5.2
|
|
30
29
|
max_tokens: 128000
|
|
31
30
|
context_limit: 400000
|
|
32
31
|
verbosity: high
|
|
33
32
|
thinking:
|
|
34
33
|
reasoning_effort: high
|
|
34
|
+
reasoning_summary: detailed
|
|
35
|
+
cost: {input: 1.75, output: 14, cache_read: 0.17}
|
|
36
|
+
- model_name: gpt-5.2-medium
|
|
37
|
+
model_id: gpt-5.2
|
|
38
|
+
context_limit: 400000
|
|
39
|
+
verbosity: high
|
|
40
|
+
thinking:
|
|
41
|
+
reasoning_effort: medium
|
|
42
|
+
reasoning_summary: concise
|
|
43
|
+
cost: {input: 1.75, output: 14, cache_read: 0.17}
|
|
44
|
+
- model_name: gpt-5.2-low
|
|
45
|
+
model_id: gpt-5.2
|
|
46
|
+
context_limit: 400000
|
|
47
|
+
verbosity: low
|
|
48
|
+
thinking:
|
|
49
|
+
reasoning_effort: low
|
|
50
|
+
reasoning_summary: concise
|
|
51
|
+
cost: {input: 1.75, output: 14, cache_read: 0.17}
|
|
52
|
+
- model_name: gpt-5.2-fast
|
|
53
|
+
model_id: gpt-5.2
|
|
54
|
+
context_limit: 400000
|
|
55
|
+
verbosity: low
|
|
56
|
+
thinking:
|
|
57
|
+
reasoning_effort: none
|
|
35
58
|
cost: {input: 1.75, output: 14, cache_read: 0.17}
|
|
36
|
-
- provider_name: openrouter
|
|
37
|
-
protocol: openrouter
|
|
38
|
-
api_key: ${OPENROUTER_API_KEY}
|
|
39
|
-
model_list:
|
|
40
59
|
- model_name: gpt-5.1-codex-max
|
|
41
|
-
model_id:
|
|
60
|
+
model_id: gpt-5.1-codex-max
|
|
42
61
|
max_tokens: 128000
|
|
43
62
|
context_limit: 400000
|
|
44
63
|
thinking:
|
|
45
64
|
reasoning_effort: medium
|
|
65
|
+
reasoning_summary: detailed
|
|
46
66
|
cost: {input: 1.25, output: 10, cache_read: 0.13}
|
|
47
|
-
|
|
67
|
+
- provider_name: openrouter
|
|
68
|
+
protocol: openrouter
|
|
69
|
+
api_key: ${OPENROUTER_API_KEY}
|
|
70
|
+
model_list:
|
|
71
|
+
- model_name: gpt-5.2-high
|
|
48
72
|
model_id: openai/gpt-5.2
|
|
49
73
|
max_tokens: 128000
|
|
50
74
|
context_limit: 400000
|
|
51
75
|
verbosity: high
|
|
52
76
|
thinking:
|
|
53
77
|
reasoning_effort: high
|
|
78
|
+
reasoning_summary: detailed
|
|
54
79
|
cost: {input: 1.75, output: 14, cache_read: 0.17}
|
|
55
80
|
- model_name: gpt-5.2-medium
|
|
56
81
|
model_id: openai/gpt-5.2
|
|
@@ -59,22 +84,7 @@ provider_list:
|
|
|
59
84
|
verbosity: high
|
|
60
85
|
thinking:
|
|
61
86
|
reasoning_effort: medium
|
|
62
|
-
|
|
63
|
-
- model_name: gpt-5.2-low
|
|
64
|
-
model_id: openai/gpt-5.2
|
|
65
|
-
max_tokens: 128000
|
|
66
|
-
context_limit: 400000
|
|
67
|
-
verbosity: low
|
|
68
|
-
thinking:
|
|
69
|
-
reasoning_effort: low
|
|
70
|
-
cost: {input: 1.75, output: 14, cache_read: 0.17}
|
|
71
|
-
- model_name: gpt-5.2-fast
|
|
72
|
-
model_id: openai/gpt-5.2
|
|
73
|
-
max_tokens: 128000
|
|
74
|
-
context_limit: 400000
|
|
75
|
-
verbosity: low
|
|
76
|
-
thinking:
|
|
77
|
-
reasoning_effort: none
|
|
87
|
+
reasoning_summary: concise
|
|
78
88
|
cost: {input: 1.75, output: 14, cache_read: 0.17}
|
|
79
89
|
- model_name: kimi
|
|
80
90
|
model_id: moonshotai/kimi-k2-thinking
|
|
@@ -165,10 +175,14 @@ provider_list:
|
|
|
165
175
|
- model_name: gemini-pro
|
|
166
176
|
model_id: gemini-3-pro-preview
|
|
167
177
|
context_limit: 1048576
|
|
178
|
+
thinking:
|
|
179
|
+
reasoning_effort: high
|
|
168
180
|
cost: {input: 2, output: 12, cache_read: 0.2}
|
|
169
181
|
- model_name: gemini-flash
|
|
170
182
|
model_id: gemini-3-flash-preview
|
|
171
183
|
context_limit: 1048576
|
|
184
|
+
thinking:
|
|
185
|
+
reasoning_effort: medium
|
|
172
186
|
cost: {input: 0.5, output: 3, cache_read: 0.05}
|
|
173
187
|
- model_name: nano-banana-pro
|
|
174
188
|
model_id: gemini-3-pro-image-preview
|
|
@@ -177,6 +191,13 @@ provider_list:
|
|
|
177
191
|
- image
|
|
178
192
|
- text
|
|
179
193
|
cost: {input: 2, output: 12, cache_read: 0.2, image: 120}
|
|
194
|
+
- model_name: nano-banana
|
|
195
|
+
model_id: gemini-2.5-flash-image
|
|
196
|
+
context_limit: 33000
|
|
197
|
+
modalities:
|
|
198
|
+
- image
|
|
199
|
+
- text
|
|
200
|
+
cost: {input: 0.3, output: 2.5, cache_read: 0.03, image: 30}
|
|
180
201
|
- provider_name: bedrock
|
|
181
202
|
protocol: bedrock
|
|
182
203
|
aws_access_key: ${AWS_ACCESS_KEY_ID}
|
|
@@ -5,6 +5,7 @@ environment variables (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.) without
|
|
|
5
5
|
manually configuring providers.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from dataclasses import dataclass
|
|
8
9
|
from functools import lru_cache
|
|
9
10
|
from importlib import resources
|
|
10
11
|
from typing import TYPE_CHECKING, Any
|
|
@@ -14,15 +15,28 @@ import yaml
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
15
16
|
from klaude_code.config.config import ProviderConfig
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class ApiKeyInfo:
|
|
21
|
+
"""Information about a supported API key."""
|
|
22
|
+
|
|
23
|
+
env_var: str
|
|
24
|
+
name: str
|
|
25
|
+
description: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# All supported API keys with their metadata
|
|
29
|
+
SUPPORTED_API_KEYS: tuple[ApiKeyInfo, ...] = (
|
|
30
|
+
ApiKeyInfo("ANTHROPIC_API_KEY", "Anthropic", "Anthropic API key"),
|
|
31
|
+
ApiKeyInfo("OPENAI_API_KEY", "OpenAI", "OpenAI API key"),
|
|
32
|
+
ApiKeyInfo("OPENROUTER_API_KEY", "OpenRouter", "OpenRouter API key"),
|
|
33
|
+
ApiKeyInfo("GOOGLE_API_KEY", "Google Gemini", "Google API key (Gemini)"),
|
|
34
|
+
ApiKeyInfo("DEEPSEEK_API_KEY", "DeepSeek", "DeepSeek API key"),
|
|
35
|
+
ApiKeyInfo("MOONSHOT_API_KEY", "Moonshot Kimi", "Moonshot API key (Kimi)"),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# For backwards compatibility
|
|
39
|
+
SUPPORTED_API_KEY_ENVS = [k.env_var for k in SUPPORTED_API_KEYS]
|
|
26
40
|
|
|
27
41
|
|
|
28
42
|
@lru_cache(maxsize=1)
|
klaude_code/config/config.py
CHANGED
|
@@ -8,8 +8,9 @@ from typing import Any, cast
|
|
|
8
8
|
import yaml
|
|
9
9
|
from pydantic import BaseModel, Field, ValidationError, model_validator
|
|
10
10
|
|
|
11
|
+
from klaude_code.auth.env import get_auth_env
|
|
11
12
|
from klaude_code.config.builtin_config import (
|
|
12
|
-
|
|
13
|
+
SUPPORTED_API_KEYS,
|
|
13
14
|
get_builtin_provider_configs,
|
|
14
15
|
get_builtin_sub_agent_models,
|
|
15
16
|
)
|
|
@@ -26,7 +27,8 @@ def parse_env_var_syntax(value: str | None) -> tuple[str | None, str | None]:
|
|
|
26
27
|
|
|
27
28
|
Returns:
|
|
28
29
|
A tuple of (env_var_name, resolved_value).
|
|
29
|
-
- If value uses ${ENV_VAR} syntax: (env_var_name,
|
|
30
|
+
- If value uses ${ENV_VAR} syntax: (env_var_name, resolved_value)
|
|
31
|
+
Priority: os.environ > klaude-auth.json env section
|
|
30
32
|
- If value is a plain string: (None, value)
|
|
31
33
|
- If value is None: (None, None)
|
|
32
34
|
"""
|
|
@@ -36,7 +38,9 @@ def parse_env_var_syntax(value: str | None) -> tuple[str | None, str | None]:
|
|
|
36
38
|
match = _ENV_VAR_PATTERN.match(value)
|
|
37
39
|
if match:
|
|
38
40
|
env_var_name = match.group(1)
|
|
39
|
-
|
|
41
|
+
# Priority: real env var > auth.json env section
|
|
42
|
+
resolved = os.environ.get(env_var_name) or get_auth_env(env_var_name)
|
|
43
|
+
return env_var_name, resolved
|
|
40
44
|
|
|
41
45
|
return None, value
|
|
42
46
|
|
|
@@ -613,17 +617,23 @@ def load_config() -> Config:
|
|
|
613
617
|
|
|
614
618
|
def print_no_available_models_hint() -> None:
|
|
615
619
|
"""Print helpful message when no models are available due to missing API keys."""
|
|
616
|
-
log("No available models.
|
|
620
|
+
log("No available models. Configure an API key using one of these methods:", style="yellow")
|
|
617
621
|
log("")
|
|
618
|
-
|
|
619
|
-
|
|
622
|
+
log("Option 1: Use klaude auth login", style="bold")
|
|
623
|
+
# Use first word of name for brevity
|
|
624
|
+
names = [k.name.split()[0].lower() for k in SUPPORTED_API_KEYS]
|
|
625
|
+
log(f" klaude auth login <provider> (providers: {', '.join(names)})", style="dim")
|
|
626
|
+
log("")
|
|
627
|
+
log("Option 2: Set environment variables", style="bold")
|
|
628
|
+
max_len = max(len(k.env_var) for k in SUPPORTED_API_KEYS)
|
|
629
|
+
for key_info in SUPPORTED_API_KEYS:
|
|
630
|
+
current_value = os.environ.get(key_info.env_var) or get_auth_env(key_info.env_var)
|
|
620
631
|
if current_value:
|
|
621
|
-
log(f" {env_var}
|
|
632
|
+
log(f" {key_info.env_var:<{max_len}} (set)", style="green")
|
|
622
633
|
else:
|
|
623
|
-
log(f"
|
|
634
|
+
log(f" {key_info.env_var:<{max_len}} {key_info.description}", style="dim")
|
|
624
635
|
log("")
|
|
625
636
|
log(f"Or add custom providers in: {config_path}", style="dim")
|
|
626
|
-
log(f"See example config: {example_config_path}", style="dim")
|
|
627
637
|
|
|
628
638
|
|
|
629
639
|
# Expose cache control for tests and callers that need to invalidate the cache.
|
|
@@ -102,6 +102,7 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
102
102
|
)
|
|
103
103
|
|
|
104
104
|
# Normalized matching (e.g. gpt52 == gpt-5.2, gpt52 in gpt-5.2-2025-...)
|
|
105
|
+
# Only match selector/model_name exactly; model_id is checked via substring match below
|
|
105
106
|
preferred_norm = _normalize_model_key(preferred)
|
|
106
107
|
normalized_matches: list[ModelEntry] = []
|
|
107
108
|
if preferred_norm:
|
|
@@ -110,7 +111,6 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
110
111
|
for m in models
|
|
111
112
|
if preferred_norm == _normalize_model_key(m.selector)
|
|
112
113
|
or preferred_norm == _normalize_model_key(m.model_name)
|
|
113
|
-
or preferred_norm == _normalize_model_key(m.model_id or "")
|
|
114
114
|
]
|
|
115
115
|
if len(normalized_matches) == 1:
|
|
116
116
|
return ModelMatchResult(
|
klaude_code/const.py
CHANGED
|
@@ -27,6 +27,7 @@ def _get_int_env(name: str, default: int) -> int:
|
|
|
27
27
|
# =============================================================================
|
|
28
28
|
|
|
29
29
|
MAX_FAILED_TURN_RETRIES = 10 # Maximum retry attempts for failed turns
|
|
30
|
+
RETRY_PRESERVE_PARTIAL_MESSAGE = True # Preserve partial message on stream error for retry prefill
|
|
30
31
|
LLM_HTTP_TIMEOUT_TOTAL = 300.0 # HTTP timeout for LLM API requests (seconds)
|
|
31
32
|
LLM_HTTP_TIMEOUT_CONNECT = 15.0 # HTTP connect timeout (seconds)
|
|
32
33
|
LLM_HTTP_TIMEOUT_READ = 285.0 # HTTP read timeout (seconds)
|
|
@@ -157,7 +158,7 @@ MARKDOWN_RIGHT_MARGIN = 2 # Right margin (columns) for markdown rendering
|
|
|
157
158
|
STATUS_HINT_TEXT = " (esc to interrupt)" # Status hint text shown after spinner
|
|
158
159
|
|
|
159
160
|
# Spinner status texts
|
|
160
|
-
STATUS_WAITING_TEXT = "
|
|
161
|
+
STATUS_WAITING_TEXT = "Loading …"
|
|
161
162
|
STATUS_THINKING_TEXT = "Thinking …"
|
|
162
163
|
STATUS_COMPOSING_TEXT = "Composing"
|
|
163
164
|
|
|
@@ -98,7 +98,7 @@ class EditTool(ToolABC):
|
|
|
98
98
|
if is_directory(file_path):
|
|
99
99
|
return message.ToolResultMessage(
|
|
100
100
|
status="error",
|
|
101
|
-
output_text="<tool_use_error>Illegal operation on a directory
|
|
101
|
+
output_text="<tool_use_error>Illegal operation on a directory: edit</tool_use_error>",
|
|
102
102
|
)
|
|
103
103
|
|
|
104
104
|
if args.old_string == "":
|
|
@@ -210,7 +210,7 @@ class ReadTool(ToolABC):
|
|
|
210
210
|
if is_directory(file_path):
|
|
211
211
|
return message.ToolResultMessage(
|
|
212
212
|
status="error",
|
|
213
|
-
output_text="<tool_use_error>Illegal operation on a directory
|
|
213
|
+
output_text="<tool_use_error>Illegal operation on a directory: read</tool_use_error>",
|
|
214
214
|
)
|
|
215
215
|
if not file_exists(file_path):
|
|
216
216
|
return message.ToolResultMessage(
|
|
@@ -308,7 +308,7 @@ class ReadTool(ToolABC):
|
|
|
308
308
|
except IsADirectoryError:
|
|
309
309
|
return message.ToolResultMessage(
|
|
310
310
|
status="error",
|
|
311
|
-
output_text="<tool_use_error>Illegal operation on a directory
|
|
311
|
+
output_text="<tool_use_error>Illegal operation on a directory: read</tool_use_error>",
|
|
312
312
|
)
|
|
313
313
|
|
|
314
314
|
if offset > max(read_result.total_lines, 0):
|
|
@@ -57,7 +57,7 @@ class WriteTool(ToolABC):
|
|
|
57
57
|
if is_directory(file_path):
|
|
58
58
|
return message.ToolResultMessage(
|
|
59
59
|
status="error",
|
|
60
|
-
output_text="<tool_use_error>Illegal operation on a directory
|
|
60
|
+
output_text="<tool_use_error>Illegal operation on a directory: write</tool_use_error>",
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
file_tracker = context.file_tracker
|
klaude_code/core/turn.py
CHANGED
|
@@ -4,7 +4,7 @@ from collections.abc import AsyncGenerator
|
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from klaude_code.const import SUPPORTED_IMAGE_SIZES
|
|
7
|
+
from klaude_code.const import RETRY_PRESERVE_PARTIAL_MESSAGE, SUPPORTED_IMAGE_SIZES
|
|
8
8
|
from klaude_code.core.tool import ToolABC
|
|
9
9
|
from klaude_code.core.tool.context import SubAgentResumeClaims, ToolContext
|
|
10
10
|
|
|
@@ -24,6 +24,14 @@ from klaude_code.llm.client import LLMStreamABC
|
|
|
24
24
|
from klaude_code.log import DebugType, log_debug
|
|
25
25
|
from klaude_code.protocol import events, llm_param, message, model, tools
|
|
26
26
|
|
|
27
|
+
# Protocols that support prefill (continuing from partial assistant message)
|
|
28
|
+
_PREFILL_SUPPORTED_PROTOCOLS = frozenset(
|
|
29
|
+
{
|
|
30
|
+
"anthropic",
|
|
31
|
+
"claude_oauth",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
27
35
|
|
|
28
36
|
class TurnError(Exception):
|
|
29
37
|
"""Raised when a turn fails and should be retried."""
|
|
@@ -176,7 +184,19 @@ class TurnExecutor:
|
|
|
176
184
|
yield event
|
|
177
185
|
|
|
178
186
|
if self._turn_result.stream_error is not None:
|
|
187
|
+
# Save accumulated content for potential prefill on retry (only for supported protocols)
|
|
179
188
|
session_ctx.append_history([self._turn_result.stream_error])
|
|
189
|
+
protocol = ctx.llm_client.get_llm_config().protocol
|
|
190
|
+
supports_prefill = protocol.value in _PREFILL_SUPPORTED_PROTOCOLS
|
|
191
|
+
if (
|
|
192
|
+
RETRY_PRESERVE_PARTIAL_MESSAGE
|
|
193
|
+
and supports_prefill
|
|
194
|
+
and self._turn_result.assistant_message is not None
|
|
195
|
+
and self._turn_result.assistant_message.parts
|
|
196
|
+
):
|
|
197
|
+
session_ctx.append_history([self._turn_result.assistant_message])
|
|
198
|
+
# Add continuation prompt to avoid Anthropic thinking block requirement
|
|
199
|
+
session_ctx.append_history([message.UserMessage(parts=[message.TextPart(text="continue")])])
|
|
180
200
|
yield events.TurnEndEvent(session_id=session_ctx.session_id)
|
|
181
201
|
raise TurnError(self._turn_result.stream_error.error)
|
|
182
202
|
|
|
@@ -231,9 +251,6 @@ class TurnExecutor:
|
|
|
231
251
|
image_size = generation.get("image_size")
|
|
232
252
|
if image_size in SUPPORTED_IMAGE_SIZES:
|
|
233
253
|
image_config.image_size = image_size
|
|
234
|
-
extra = generation.get("extra")
|
|
235
|
-
if isinstance(extra, dict) and extra:
|
|
236
|
-
image_config.extra = extra
|
|
237
254
|
if image_config.model_dump(exclude_none=True):
|
|
238
255
|
call_param.image_config = image_config
|
|
239
256
|
|