klaude-code 2.5.3__py3-none-any.whl → 2.7.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/app/runtime.py +1 -1
- klaude_code/auth/__init__.py +10 -0
- klaude_code/auth/env.py +81 -0
- klaude_code/cli/auth_cmd.py +87 -8
- klaude_code/cli/config_cmd.py +5 -5
- klaude_code/cli/cost_cmd.py +159 -60
- klaude_code/cli/main.py +146 -65
- klaude_code/cli/self_update.py +7 -7
- klaude_code/config/builtin_config.py +23 -9
- klaude_code/config/config.py +19 -9
- klaude_code/const.py +10 -1
- klaude_code/core/reminders.py +4 -5
- klaude_code/core/turn.py +8 -9
- klaude_code/llm/google/client.py +12 -0
- klaude_code/llm/openai_compatible/stream.py +5 -1
- klaude_code/llm/openrouter/client.py +1 -0
- klaude_code/protocol/commands.py +0 -1
- 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/skill/loader.py +12 -13
- klaude_code/skill/manager.py +3 -3
- klaude_code/tui/command/__init__.py +1 -4
- klaude_code/tui/command/copy_cmd.py +1 -1
- klaude_code/tui/command/fork_session_cmd.py +4 -4
- klaude_code/tui/commands.py +0 -5
- klaude_code/tui/components/command_output.py +1 -1
- klaude_code/tui/components/metadata.py +4 -5
- klaude_code/tui/components/rich/markdown.py +60 -0
- klaude_code/tui/components/rich/theme.py +8 -0
- klaude_code/tui/components/sub_agent.py +6 -0
- klaude_code/tui/components/user_input.py +38 -27
- klaude_code/tui/display.py +11 -1
- klaude_code/tui/input/AGENTS.md +44 -0
- klaude_code/tui/input/completers.py +21 -21
- klaude_code/tui/input/drag_drop.py +197 -0
- klaude_code/tui/input/images.py +227 -0
- klaude_code/tui/input/key_bindings.py +173 -19
- klaude_code/tui/input/paste.py +71 -0
- klaude_code/tui/input/prompt_toolkit.py +13 -3
- klaude_code/tui/machine.py +90 -56
- klaude_code/tui/renderer.py +1 -62
- klaude_code/tui/runner.py +1 -1
- klaude_code/tui/terminal/image.py +40 -9
- klaude_code/tui/terminal/selector.py +52 -2
- {klaude_code-2.5.3.dist-info → klaude_code-2.7.0.dist-info}/METADATA +32 -40
- {klaude_code-2.5.3.dist-info → klaude_code-2.7.0.dist-info}/RECORD +49 -54
- klaude_code/cli/session_cmd.py +0 -87
- 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/tui/command/terminal_setup_cmd.py +0 -248
- klaude_code/tui/input/clipboard.py +0 -152
- {klaude_code-2.5.3.dist-info → klaude_code-2.7.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.5.3.dist-info → klaude_code-2.7.0.dist-info}/entry_points.txt +0 -0
klaude_code/cli/main.py
CHANGED
|
@@ -1,114 +1,186 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import sys
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any
|
|
3
5
|
|
|
4
6
|
import typer
|
|
7
|
+
from typer.core import TyperGroup
|
|
5
8
|
|
|
6
9
|
from klaude_code.cli.auth_cmd import register_auth_commands
|
|
7
10
|
from klaude_code.cli.config_cmd import register_config_commands
|
|
8
11
|
from klaude_code.cli.cost_cmd import register_cost_commands
|
|
9
12
|
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
|
|
13
|
+
from klaude_code.cli.self_update import register_self_upgrade_commands, version_option_callback
|
|
12
14
|
from klaude_code.session import Session
|
|
13
15
|
from klaude_code.tui.command.resume_cmd import select_session_sync
|
|
14
16
|
from klaude_code.ui.terminal.title import update_terminal_title
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
18
|
+
|
|
19
|
+
def _build_env_help() -> str:
|
|
20
|
+
from klaude_code.config.builtin_config import SUPPORTED_API_KEYS
|
|
21
|
+
|
|
22
|
+
lines = [
|
|
23
|
+
"Environment Variables:",
|
|
24
|
+
"",
|
|
25
|
+
"Provider API keys (built-in config):",
|
|
26
|
+
]
|
|
27
|
+
# Calculate max env_var length for alignment
|
|
28
|
+
max_len = max(len(k.env_var) for k in SUPPORTED_API_KEYS)
|
|
29
|
+
for k in SUPPORTED_API_KEYS:
|
|
30
|
+
lines.append(f" {k.env_var:<{max_len}} {k.description}")
|
|
31
|
+
lines.extend(
|
|
32
|
+
[
|
|
33
|
+
"",
|
|
34
|
+
"Tool limits (Read):",
|
|
35
|
+
" KLAUDE_READ_GLOBAL_LINE_CAP Max lines to read (default: 2000)",
|
|
36
|
+
" KLAUDE_READ_MAX_CHARS Max total chars to read (default: 50000)",
|
|
37
|
+
" KLAUDE_READ_MAX_IMAGE_BYTES Max image bytes to read (default: 4MB)",
|
|
38
|
+
" KLAUDE_IMAGE_OUTPUT_MAX_BYTES Max decoded image bytes (default: 64MB)",
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
return "\n\n".join(lines)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ENV_HELP = _build_env_help()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _looks_like_flag(token: str) -> bool:
|
|
48
|
+
return token.startswith("-") and token != "-"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _preprocess_cli_args(args: list[str]) -> list[str]:
|
|
52
|
+
"""Rewrite CLI args to support optional values for selected options.
|
|
53
|
+
|
|
54
|
+
Supported rewrites:
|
|
55
|
+
- --model / -m with no value -> --model-select
|
|
56
|
+
- --resume / -r with value -> --resume-by-id <value>
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
rewritten: list[str] = []
|
|
60
|
+
i = 0
|
|
61
|
+
while i < len(args):
|
|
62
|
+
token = args[i]
|
|
63
|
+
|
|
64
|
+
if token in {"--model", "-m"}:
|
|
65
|
+
next_token = args[i + 1] if i + 1 < len(args) else None
|
|
66
|
+
if next_token is None or next_token == "--" or _looks_like_flag(next_token):
|
|
67
|
+
rewritten.append("--model-select")
|
|
68
|
+
i += 1
|
|
69
|
+
continue
|
|
70
|
+
rewritten.append(token)
|
|
71
|
+
i += 1
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
if token.startswith("--model="):
|
|
75
|
+
value = token.split("=", 1)[1]
|
|
76
|
+
if value == "":
|
|
77
|
+
rewritten.append("--model-select")
|
|
78
|
+
else:
|
|
79
|
+
rewritten.append(token)
|
|
80
|
+
i += 1
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
if token in {"--resume", "-r"}:
|
|
84
|
+
next_token = args[i + 1] if i + 1 < len(args) else None
|
|
85
|
+
if next_token is not None and next_token != "--" and not _looks_like_flag(next_token):
|
|
86
|
+
rewritten.extend(["--resume-by-id", next_token])
|
|
87
|
+
i += 2
|
|
88
|
+
continue
|
|
89
|
+
rewritten.append(token)
|
|
90
|
+
i += 1
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if token.startswith("--resume="):
|
|
94
|
+
value = token.split("=", 1)[1]
|
|
95
|
+
rewritten.extend(["--resume-by-id", value])
|
|
96
|
+
i += 1
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
rewritten.append(token)
|
|
100
|
+
i += 1
|
|
101
|
+
|
|
102
|
+
return rewritten
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class _PreprocessingTyperGroup(TyperGroup):
|
|
106
|
+
def main(
|
|
107
|
+
self,
|
|
108
|
+
args: Sequence[str] | None = None,
|
|
109
|
+
prog_name: str | None = None,
|
|
110
|
+
complete_var: str | None = None,
|
|
111
|
+
standalone_mode: bool = True,
|
|
112
|
+
windows_expand_args: bool = True,
|
|
113
|
+
**extra: Any,
|
|
114
|
+
) -> Any:
|
|
115
|
+
click_args = _preprocess_cli_args(list(args) if args is not None else sys.argv[1:])
|
|
116
|
+
return super().main(
|
|
117
|
+
args=click_args,
|
|
118
|
+
prog_name=prog_name,
|
|
119
|
+
complete_var=complete_var,
|
|
120
|
+
standalone_mode=standalone_mode,
|
|
121
|
+
windows_expand_args=windows_expand_args,
|
|
122
|
+
**extra,
|
|
123
|
+
)
|
|
124
|
+
|
|
56
125
|
|
|
57
126
|
app = typer.Typer(
|
|
127
|
+
cls=_PreprocessingTyperGroup,
|
|
58
128
|
add_completion=False,
|
|
59
129
|
pretty_exceptions_enable=False,
|
|
60
130
|
no_args_is_help=False,
|
|
61
131
|
rich_markup_mode="rich",
|
|
62
132
|
epilog=ENV_HELP,
|
|
133
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
63
134
|
)
|
|
64
135
|
|
|
65
136
|
# Register subcommands from modules
|
|
66
|
-
register_session_commands(app)
|
|
67
137
|
register_auth_commands(app)
|
|
68
138
|
register_config_commands(app)
|
|
69
139
|
register_cost_commands(app)
|
|
140
|
+
register_self_upgrade_commands(app)
|
|
70
141
|
|
|
71
|
-
|
|
142
|
+
|
|
143
|
+
@app.command("help", hidden=True)
|
|
144
|
+
def help_command(ctx: typer.Context) -> None:
|
|
145
|
+
"""Show help message."""
|
|
146
|
+
print(ctx.parent.get_help() if ctx.parent else ctx.get_help())
|
|
72
147
|
|
|
73
148
|
|
|
74
149
|
@app.callback(invoke_without_command=True)
|
|
75
150
|
def main_callback(
|
|
76
151
|
ctx: typer.Context,
|
|
77
|
-
version: bool = typer.Option(
|
|
78
|
-
False,
|
|
79
|
-
"--version",
|
|
80
|
-
"-V",
|
|
81
|
-
"-v",
|
|
82
|
-
help="Show version and exit",
|
|
83
|
-
callback=version_option_callback,
|
|
84
|
-
is_eager=True,
|
|
85
|
-
),
|
|
86
152
|
model: str | None = typer.Option(
|
|
87
153
|
None,
|
|
88
154
|
"--model",
|
|
89
155
|
"-m",
|
|
90
|
-
help="
|
|
156
|
+
help="Select model by name; use --model with no value to choose interactively",
|
|
91
157
|
rich_help_panel="LLM",
|
|
92
158
|
),
|
|
93
|
-
continue_: bool = typer.Option(False, "--continue", "-c", help="
|
|
94
|
-
resume: bool = typer.Option(
|
|
159
|
+
continue_: bool = typer.Option(False, "--continue", "-c", help="Resume latest session"),
|
|
160
|
+
resume: bool = typer.Option(
|
|
161
|
+
False,
|
|
162
|
+
"--resume",
|
|
163
|
+
"-r",
|
|
164
|
+
help="Resume a session; use --resume <id> to resume directly, or --resume to pick interactively",
|
|
165
|
+
),
|
|
95
166
|
resume_by_id: str | None = typer.Option(
|
|
96
167
|
None,
|
|
97
168
|
"--resume-by-id",
|
|
98
|
-
help="Resume
|
|
169
|
+
help="Resume session by ID",
|
|
170
|
+
hidden=True,
|
|
99
171
|
),
|
|
100
172
|
select_model: bool = typer.Option(
|
|
101
173
|
False,
|
|
102
|
-
"--select
|
|
103
|
-
"
|
|
104
|
-
|
|
174
|
+
"--model-select",
|
|
175
|
+
help="Choose model interactively (same as --model with no value)",
|
|
176
|
+
hidden=True,
|
|
105
177
|
rich_help_panel="LLM",
|
|
106
178
|
),
|
|
107
179
|
debug: bool = typer.Option(
|
|
108
180
|
False,
|
|
109
181
|
"--debug",
|
|
110
182
|
"-d",
|
|
111
|
-
help="Enable debug
|
|
183
|
+
help="Enable debug logging",
|
|
112
184
|
rich_help_panel="Debug",
|
|
113
185
|
),
|
|
114
186
|
debug_filter: str | None = typer.Option(
|
|
@@ -120,14 +192,23 @@ def main_callback(
|
|
|
120
192
|
vanilla: bool = typer.Option(
|
|
121
193
|
False,
|
|
122
194
|
"--vanilla",
|
|
123
|
-
help="
|
|
195
|
+
help="Minimal mode: basic tools only, no system prompts",
|
|
124
196
|
),
|
|
125
197
|
banana: bool = typer.Option(
|
|
126
198
|
False,
|
|
127
199
|
"--banana",
|
|
128
|
-
help="Image generation mode
|
|
200
|
+
help="Image generation mode (alias for --model banana)",
|
|
129
201
|
rich_help_panel="LLM",
|
|
130
202
|
),
|
|
203
|
+
version: bool = typer.Option(
|
|
204
|
+
False,
|
|
205
|
+
"--version",
|
|
206
|
+
"-V",
|
|
207
|
+
"-v",
|
|
208
|
+
help="Show version and exit",
|
|
209
|
+
callback=version_option_callback,
|
|
210
|
+
is_eager=True,
|
|
211
|
+
),
|
|
131
212
|
) -> None:
|
|
132
213
|
# Only run interactive mode when no subcommand is invoked
|
|
133
214
|
if ctx.invoked_subcommand is None:
|
|
@@ -139,11 +220,11 @@ def main_callback(
|
|
|
139
220
|
|
|
140
221
|
resume_by_id_value = resume_by_id.strip() if resume_by_id is not None else None
|
|
141
222
|
if resume_by_id_value == "":
|
|
142
|
-
log(("Error: --resume
|
|
223
|
+
log(("Error: --resume <id> cannot be empty", "red"))
|
|
143
224
|
raise typer.Exit(2)
|
|
144
225
|
|
|
145
226
|
if resume_by_id_value is not None and (resume or continue_):
|
|
146
|
-
log(("Error: --resume
|
|
227
|
+
log(("Error: --resume <id> cannot be combined with --continue or interactive --resume", "red"))
|
|
147
228
|
raise typer.Exit(2)
|
|
148
229
|
|
|
149
230
|
if resume_by_id_value is not None and not Session.exists(resume_by_id_value):
|
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)
|
|
@@ -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.
|
klaude_code/const.py
CHANGED
|
@@ -7,6 +7,8 @@ that were previously scattered across the codebase.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import os
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
10
12
|
from dataclasses import dataclass
|
|
11
13
|
from pathlib import Path
|
|
12
14
|
|
|
@@ -22,6 +24,13 @@ def _get_int_env(name: str, default: int) -> int:
|
|
|
22
24
|
return default
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
def get_system_temp() -> str:
|
|
28
|
+
"""Return system-level temp directory: /tmp on Unix, system temp on Windows."""
|
|
29
|
+
if sys.platform == "win32":
|
|
30
|
+
return tempfile.gettempdir()
|
|
31
|
+
return "/tmp"
|
|
32
|
+
|
|
33
|
+
|
|
25
34
|
# =============================================================================
|
|
26
35
|
# Agent / LLM Configuration
|
|
27
36
|
# =============================================================================
|
|
@@ -115,7 +124,7 @@ TOOL_OUTPUT_DISPLAY_TAIL = 10000 # Characters to show from the end of truncated
|
|
|
115
124
|
TOOL_OUTPUT_MAX_LINES = 2000 # Maximum lines for tool output before truncation
|
|
116
125
|
TOOL_OUTPUT_DISPLAY_HEAD_LINES = 1000 # Lines to show from the beginning of truncated output
|
|
117
126
|
TOOL_OUTPUT_DISPLAY_TAIL_LINES = 1000 # Lines to show from the end of truncated output
|
|
118
|
-
TOOL_OUTPUT_TRUNCATION_DIR =
|
|
127
|
+
TOOL_OUTPUT_TRUNCATION_DIR = get_system_temp() # Directory for saving full truncated output
|
|
119
128
|
|
|
120
129
|
|
|
121
130
|
# =============================================================================
|
klaude_code/core/reminders.py
CHANGED
|
@@ -17,8 +17,8 @@ from klaude_code.skill import get_skill
|
|
|
17
17
|
# Match @ preceded by whitespace, start of line, or → (ReadTool line number arrow)
|
|
18
18
|
AT_FILE_PATTERN = re.compile(r'(?:(?<!\S)|(?<=\u2192))@("(?P<quoted>[^\"]+)"|(?P<plain>\S+))')
|
|
19
19
|
|
|
20
|
-
# Match $skill or ¥skill at
|
|
21
|
-
SKILL_PATTERN = re.compile(r"
|
|
20
|
+
# Match $skill or ¥skill inline (at start of line or after whitespace)
|
|
21
|
+
SKILL_PATTERN = re.compile(r"(?:^|\s)[$¥](?P<skill>\S+)")
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def get_last_new_user_input(session: Session) -> str | None:
|
|
@@ -79,14 +79,13 @@ def get_at_patterns_with_source(session: Session) -> list[AtPatternSource]:
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
def get_skill_from_user_input(session: Session) -> str | None:
|
|
82
|
-
"""Get $skill reference from
|
|
82
|
+
"""Get $skill reference from last user input (first match wins)."""
|
|
83
83
|
for item in reversed(session.conversation_history):
|
|
84
84
|
if isinstance(item, message.ToolResultMessage):
|
|
85
85
|
return None
|
|
86
86
|
if isinstance(item, message.UserMessage):
|
|
87
87
|
content = message.join_text_parts(item.parts)
|
|
88
|
-
|
|
89
|
-
m = SKILL_PATTERN.match(first_line)
|
|
88
|
+
m = SKILL_PATTERN.search(content)
|
|
90
89
|
if m:
|
|
91
90
|
return m.group("skill")
|
|
92
91
|
return None
|
klaude_code/core/turn.py
CHANGED
|
@@ -25,10 +25,12 @@ from klaude_code.log import DebugType, log_debug
|
|
|
25
25
|
from klaude_code.protocol import events, llm_param, message, model, tools
|
|
26
26
|
|
|
27
27
|
# Protocols that support prefill (continuing from partial assistant message)
|
|
28
|
-
_PREFILL_SUPPORTED_PROTOCOLS = frozenset(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
_PREFILL_SUPPORTED_PROTOCOLS = frozenset(
|
|
29
|
+
{
|
|
30
|
+
"anthropic",
|
|
31
|
+
"claude_oauth",
|
|
32
|
+
}
|
|
33
|
+
)
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
class TurnError(Exception):
|
|
@@ -183,6 +185,7 @@ class TurnExecutor:
|
|
|
183
185
|
|
|
184
186
|
if self._turn_result.stream_error is not None:
|
|
185
187
|
# Save accumulated content for potential prefill on retry (only for supported protocols)
|
|
188
|
+
session_ctx.append_history([self._turn_result.stream_error])
|
|
186
189
|
protocol = ctx.llm_client.get_llm_config().protocol
|
|
187
190
|
supports_prefill = protocol.value in _PREFILL_SUPPORTED_PROTOCOLS
|
|
188
191
|
if (
|
|
@@ -193,8 +196,7 @@ class TurnExecutor:
|
|
|
193
196
|
):
|
|
194
197
|
session_ctx.append_history([self._turn_result.assistant_message])
|
|
195
198
|
# Add continuation prompt to avoid Anthropic thinking block requirement
|
|
196
|
-
session_ctx.append_history([message.UserMessage(parts=[message.TextPart(text="continue")])])
|
|
197
|
-
session_ctx.append_history([self._turn_result.stream_error])
|
|
199
|
+
session_ctx.append_history([message.UserMessage(parts=[message.TextPart(text="<system>continue</system>")])])
|
|
198
200
|
yield events.TurnEndEvent(session_id=session_ctx.session_id)
|
|
199
201
|
raise TurnError(self._turn_result.stream_error.error)
|
|
200
202
|
|
|
@@ -249,9 +251,6 @@ class TurnExecutor:
|
|
|
249
251
|
image_size = generation.get("image_size")
|
|
250
252
|
if image_size in SUPPORTED_IMAGE_SIZES:
|
|
251
253
|
image_config.image_size = image_size
|
|
252
|
-
extra = generation.get("extra")
|
|
253
|
-
if isinstance(extra, dict) and extra:
|
|
254
|
-
image_config.extra = extra
|
|
255
254
|
if image_config.model_dump(exclude_none=True):
|
|
256
255
|
call_param.image_config = image_config
|
|
257
256
|
|
klaude_code/llm/google/client.py
CHANGED
|
@@ -25,6 +25,9 @@ from google.genai.types import (
|
|
|
25
25
|
ThinkingLevel,
|
|
26
26
|
ToolConfig,
|
|
27
27
|
)
|
|
28
|
+
from google.genai.types import (
|
|
29
|
+
ImageConfig as GoogleImageConfig,
|
|
30
|
+
)
|
|
28
31
|
|
|
29
32
|
from klaude_code.llm.client import LLMClientABC, LLMStreamABC
|
|
30
33
|
from klaude_code.llm.google.input import convert_history_to_contents, convert_tool_schema
|
|
@@ -91,6 +94,14 @@ def _build_config(param: llm_param.LLMCallParameter) -> GenerateContentConfig:
|
|
|
91
94
|
if param.thinking.reasoning_effort:
|
|
92
95
|
thinking_config.thinking_level = convert_gemini_thinking_level(param.thinking.reasoning_effort)
|
|
93
96
|
|
|
97
|
+
# ImageGen per-call overrides
|
|
98
|
+
image_config: GoogleImageConfig | None = None
|
|
99
|
+
if param.image_config is not None:
|
|
100
|
+
image_config = GoogleImageConfig(
|
|
101
|
+
aspect_ratio=param.image_config.aspect_ratio,
|
|
102
|
+
image_size=param.image_config.image_size,
|
|
103
|
+
)
|
|
104
|
+
|
|
94
105
|
return GenerateContentConfig(
|
|
95
106
|
system_instruction=param.system,
|
|
96
107
|
temperature=param.temperature,
|
|
@@ -98,6 +109,7 @@ def _build_config(param: llm_param.LLMCallParameter) -> GenerateContentConfig:
|
|
|
98
109
|
tools=cast(Any, tool_list) if tool_list else None,
|
|
99
110
|
tool_config=tool_config,
|
|
100
111
|
thinking_config=thinking_config,
|
|
112
|
+
image_config=image_config,
|
|
101
113
|
)
|
|
102
114
|
|
|
103
115
|
|
|
@@ -199,6 +199,7 @@ async def parse_chat_completions_stream(
|
|
|
199
199
|
metadata_tracker: MetadataTracker,
|
|
200
200
|
reasoning_handler: ReasoningHandlerABC,
|
|
201
201
|
on_event: Callable[[object], None] | None = None,
|
|
202
|
+
provider_prefix: str = "",
|
|
202
203
|
) -> AsyncGenerator[message.LLMStreamItem]:
|
|
203
204
|
"""Parse OpenAI Chat Completions stream into stream items.
|
|
204
205
|
|
|
@@ -235,7 +236,7 @@ async def parse_chat_completions_stream(
|
|
|
235
236
|
if event_model := getattr(event, "model", None):
|
|
236
237
|
metadata_tracker.set_model_name(str(event_model))
|
|
237
238
|
if provider := getattr(event, "provider", None):
|
|
238
|
-
metadata_tracker.set_provider(
|
|
239
|
+
metadata_tracker.set_provider(f"{provider_prefix}{provider}")
|
|
239
240
|
|
|
240
241
|
choices = cast(Any, getattr(event, "choices", None))
|
|
241
242
|
if not choices:
|
|
@@ -364,12 +365,14 @@ class OpenAILLMStream(LLMStreamABC):
|
|
|
364
365
|
metadata_tracker: MetadataTracker,
|
|
365
366
|
reasoning_handler: ReasoningHandlerABC,
|
|
366
367
|
on_event: Callable[[object], None] | None = None,
|
|
368
|
+
provider_prefix: str = "",
|
|
367
369
|
) -> None:
|
|
368
370
|
self._stream = stream
|
|
369
371
|
self._param = param
|
|
370
372
|
self._metadata_tracker = metadata_tracker
|
|
371
373
|
self._reasoning_handler = reasoning_handler
|
|
372
374
|
self._on_event = on_event
|
|
375
|
+
self._provider_prefix = provider_prefix
|
|
373
376
|
self._state = StreamStateManager(
|
|
374
377
|
param_model=str(param.model_id),
|
|
375
378
|
)
|
|
@@ -386,6 +389,7 @@ class OpenAILLMStream(LLMStreamABC):
|
|
|
386
389
|
metadata_tracker=self._metadata_tracker,
|
|
387
390
|
reasoning_handler=self._reasoning_handler,
|
|
388
391
|
on_event=self._on_event,
|
|
392
|
+
provider_prefix=self._provider_prefix,
|
|
389
393
|
):
|
|
390
394
|
if isinstance(item, message.AssistantMessage):
|
|
391
395
|
self._completed = True
|
klaude_code/protocol/commands.py
CHANGED