klaude-code 2.8.0__py3-none-any.whl → 2.9.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 +2 -1
- klaude_code/auth/antigravity/oauth.py +0 -9
- klaude_code/auth/antigravity/token_manager.py +0 -18
- klaude_code/auth/base.py +53 -0
- klaude_code/auth/codex/exceptions.py +0 -4
- klaude_code/auth/codex/oauth.py +32 -28
- klaude_code/auth/codex/token_manager.py +0 -18
- klaude_code/cli/cost_cmd.py +128 -39
- klaude_code/cli/list_model.py +27 -10
- klaude_code/cli/main.py +15 -4
- klaude_code/config/assets/builtin_config.yaml +8 -24
- klaude_code/config/config.py +47 -25
- klaude_code/config/sub_agent_model_helper.py +18 -13
- klaude_code/config/thinking.py +0 -8
- klaude_code/const.py +2 -2
- klaude_code/core/agent_profile.py +11 -53
- klaude_code/core/compaction/compaction.py +4 -6
- klaude_code/core/compaction/overflow.py +0 -4
- klaude_code/core/executor.py +51 -5
- klaude_code/core/manager/llm_clients.py +9 -1
- klaude_code/core/prompts/prompt-claude-code.md +4 -4
- klaude_code/core/reminders.py +21 -23
- klaude_code/core/task.py +0 -4
- klaude_code/core/tool/__init__.py +3 -2
- klaude_code/core/tool/file/apply_patch.py +0 -27
- klaude_code/core/tool/file/edit_tool.py +1 -2
- klaude_code/core/tool/file/read_tool.md +3 -2
- klaude_code/core/tool/file/read_tool.py +15 -2
- klaude_code/core/tool/offload.py +0 -35
- klaude_code/core/tool/sub_agent/__init__.py +6 -0
- klaude_code/core/tool/sub_agent/image_gen.md +16 -0
- klaude_code/core/tool/sub_agent/image_gen.py +146 -0
- klaude_code/core/tool/sub_agent/task.md +20 -0
- klaude_code/core/tool/sub_agent/task.py +205 -0
- klaude_code/core/tool/tool_registry.py +0 -16
- klaude_code/core/turn.py +1 -1
- klaude_code/llm/anthropic/input.py +6 -5
- klaude_code/llm/antigravity/input.py +14 -7
- klaude_code/llm/codex/client.py +22 -0
- klaude_code/llm/codex/prompt_sync.py +237 -0
- klaude_code/llm/google/client.py +8 -6
- klaude_code/llm/google/input.py +20 -12
- klaude_code/llm/image.py +18 -11
- klaude_code/llm/input_common.py +14 -6
- klaude_code/llm/json_stable.py +37 -0
- klaude_code/llm/openai_compatible/input.py +0 -10
- klaude_code/llm/openai_compatible/stream.py +16 -1
- klaude_code/llm/registry.py +0 -5
- klaude_code/llm/responses/input.py +15 -5
- klaude_code/llm/usage.py +0 -8
- klaude_code/protocol/commands.py +1 -0
- klaude_code/protocol/events.py +2 -1
- klaude_code/protocol/message.py +2 -2
- klaude_code/protocol/model.py +20 -1
- klaude_code/protocol/op.py +27 -0
- klaude_code/protocol/op_handler.py +10 -0
- klaude_code/protocol/sub_agent/AGENTS.md +5 -5
- klaude_code/protocol/sub_agent/__init__.py +13 -34
- klaude_code/protocol/sub_agent/explore.py +7 -34
- klaude_code/protocol/sub_agent/image_gen.py +3 -74
- klaude_code/protocol/sub_agent/task.py +3 -47
- klaude_code/protocol/sub_agent/web.py +8 -52
- klaude_code/protocol/tools.py +2 -0
- klaude_code/session/export.py +308 -299
- klaude_code/session/session.py +58 -21
- klaude_code/session/store.py +0 -4
- klaude_code/session/templates/export_session.html +430 -134
- klaude_code/skill/assets/deslop/SKILL.md +9 -0
- klaude_code/skill/system_skills.py +0 -20
- klaude_code/tui/command/__init__.py +3 -0
- klaude_code/tui/command/continue_cmd.py +34 -0
- klaude_code/tui/command/fork_session_cmd.py +5 -2
- klaude_code/tui/command/resume_cmd.py +9 -2
- klaude_code/tui/command/sub_agent_model_cmd.py +85 -18
- klaude_code/tui/components/assistant.py +0 -26
- klaude_code/tui/components/command_output.py +3 -1
- klaude_code/tui/components/developer.py +3 -0
- klaude_code/tui/components/diffs.py +2 -208
- klaude_code/tui/components/errors.py +4 -0
- klaude_code/tui/components/mermaid_viewer.py +2 -2
- klaude_code/tui/components/rich/markdown.py +60 -63
- klaude_code/tui/components/rich/theme.py +2 -0
- klaude_code/tui/components/sub_agent.py +2 -46
- klaude_code/tui/components/thinking.py +0 -33
- klaude_code/tui/components/tools.py +43 -21
- klaude_code/tui/input/images.py +21 -18
- klaude_code/tui/input/key_bindings.py +2 -2
- klaude_code/tui/input/prompt_toolkit.py +49 -49
- klaude_code/tui/machine.py +15 -11
- klaude_code/tui/renderer.py +12 -20
- klaude_code/tui/runner.py +2 -1
- klaude_code/tui/terminal/image.py +6 -34
- klaude_code/ui/common.py +0 -70
- {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/METADATA +3 -6
- {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/RECORD +97 -92
- klaude_code/core/tool/sub_agent_tool.py +0 -126
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -108
- klaude_code/tui/components/rich/searchable_text.py +0 -68
- {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.8.0.dist-info → klaude_code-2.9.0.dist-info}/entry_points.txt +0 -0
klaude_code/config/config.py
CHANGED
|
@@ -44,13 +44,6 @@ def parse_env_var_syntax(value: str | None) -> tuple[str | None, str | None]:
|
|
|
44
44
|
return None, value
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def is_env_var_syntax(value: str | None) -> bool:
|
|
48
|
-
"""Check if a value uses ${ENV_VAR} syntax."""
|
|
49
|
-
if value is None:
|
|
50
|
-
return False
|
|
51
|
-
return _ENV_VAR_PATTERN.match(value) is not None
|
|
52
|
-
|
|
53
|
-
|
|
54
47
|
def resolve_api_key(value: str | None) -> str | None:
|
|
55
48
|
"""Resolve an API key value, expanding ${ENV_VAR} syntax if present."""
|
|
56
49
|
_, resolved = parse_env_var_syntax(value)
|
|
@@ -70,6 +63,7 @@ class ModelConfig(llm_param.LLMConfigModelParameter):
|
|
|
70
63
|
class ProviderConfig(llm_param.LLMConfigProviderParameter):
|
|
71
64
|
"""Full provider configuration (used in merged config)."""
|
|
72
65
|
|
|
66
|
+
disabled: bool = False
|
|
73
67
|
model_list: list[ModelConfig] = Field(default_factory=lambda: [])
|
|
74
68
|
|
|
75
69
|
def get_resolved_api_key(self) -> str | None:
|
|
@@ -141,6 +135,7 @@ class UserProviderConfig(BaseModel):
|
|
|
141
135
|
|
|
142
136
|
provider_name: str
|
|
143
137
|
protocol: llm_param.LLMClientProtocol | None = None
|
|
138
|
+
disabled: bool = False
|
|
144
139
|
base_url: str | None = None
|
|
145
140
|
api_key: str | None = None
|
|
146
141
|
is_azure: bool = False
|
|
@@ -290,6 +285,9 @@ class Config(BaseModel):
|
|
|
290
285
|
if requested_provider is not None and provider.provider_name.casefold() != requested_provider.casefold():
|
|
291
286
|
continue
|
|
292
287
|
|
|
288
|
+
if provider.disabled:
|
|
289
|
+
continue
|
|
290
|
+
|
|
293
291
|
api_key = provider.get_resolved_api_key()
|
|
294
292
|
if (
|
|
295
293
|
provider.protocol
|
|
@@ -303,7 +301,11 @@ class Config(BaseModel):
|
|
|
303
301
|
):
|
|
304
302
|
continue
|
|
305
303
|
|
|
306
|
-
|
|
304
|
+
for model in provider.model_list:
|
|
305
|
+
if model.model_name != requested_model:
|
|
306
|
+
continue
|
|
307
|
+
if model.disabled:
|
|
308
|
+
continue
|
|
307
309
|
return requested_model, provider.provider_name
|
|
308
310
|
|
|
309
311
|
return None
|
|
@@ -315,6 +317,11 @@ class Config(BaseModel):
|
|
|
315
317
|
if requested_provider is not None and provider.provider_name.casefold() != requested_provider.casefold():
|
|
316
318
|
continue
|
|
317
319
|
|
|
320
|
+
if provider.disabled:
|
|
321
|
+
if requested_provider is not None:
|
|
322
|
+
raise ValueError(f"Provider '{provider.provider_name}' is disabled for: {model_name}")
|
|
323
|
+
continue
|
|
324
|
+
|
|
318
325
|
# Resolve ${ENV_VAR} syntax for api_key
|
|
319
326
|
api_key = provider.get_resolved_api_key()
|
|
320
327
|
|
|
@@ -339,7 +346,15 @@ class Config(BaseModel):
|
|
|
339
346
|
for model in provider.model_list:
|
|
340
347
|
if model.model_name != requested_model:
|
|
341
348
|
continue
|
|
342
|
-
|
|
349
|
+
|
|
350
|
+
if model.disabled:
|
|
351
|
+
if requested_provider is not None:
|
|
352
|
+
raise ValueError(
|
|
353
|
+
f"Model '{requested_model}' is disabled in provider '{provider.provider_name}' for: {model_name}"
|
|
354
|
+
)
|
|
355
|
+
break
|
|
356
|
+
|
|
357
|
+
provider_dump = provider.model_dump(exclude={"model_list", "disabled"})
|
|
343
358
|
provider_dump["api_key"] = api_key
|
|
344
359
|
return llm_param.LLMConfigParameter(
|
|
345
360
|
**provider_dump,
|
|
@@ -353,7 +368,7 @@ class Config(BaseModel):
|
|
|
353
368
|
|
|
354
369
|
Args:
|
|
355
370
|
only_available: If True, only return models from providers with valid API keys.
|
|
356
|
-
include_disabled: If False, exclude models with disabled=True.
|
|
371
|
+
include_disabled: If False, exclude models/providers with disabled=True.
|
|
357
372
|
"""
|
|
358
373
|
return [
|
|
359
374
|
ModelEntry(
|
|
@@ -362,7 +377,8 @@ class Config(BaseModel):
|
|
|
362
377
|
**model.model_dump(exclude={"model_name"}),
|
|
363
378
|
)
|
|
364
379
|
for provider in self.provider_list
|
|
365
|
-
if
|
|
380
|
+
if include_disabled or not provider.disabled
|
|
381
|
+
if not only_available or (not provider.disabled and not provider.is_api_key_missing())
|
|
366
382
|
for model in provider.model_list
|
|
367
383
|
if include_disabled or not model.disabled
|
|
368
384
|
]
|
|
@@ -374,13 +390,6 @@ class Config(BaseModel):
|
|
|
374
390
|
return True
|
|
375
391
|
return False
|
|
376
392
|
|
|
377
|
-
def get_first_available_nano_banana_model(self) -> str | None:
|
|
378
|
-
"""Get the first available nano-banana model, or None."""
|
|
379
|
-
for entry in self.iter_model_entries(only_available=True, include_disabled=False):
|
|
380
|
-
if "nano-banana" in entry.model_name:
|
|
381
|
-
return entry.model_name
|
|
382
|
-
return None
|
|
383
|
-
|
|
384
393
|
def get_first_available_image_model(self) -> str | None:
|
|
385
394
|
"""Get the first available image generation model, or None."""
|
|
386
395
|
for entry in self.iter_model_entries(only_available=True, include_disabled=False):
|
|
@@ -406,7 +415,21 @@ class Config(BaseModel):
|
|
|
406
415
|
user_config.theme = self.theme
|
|
407
416
|
# Note: provider_list is NOT synced - user providers are already in user_config
|
|
408
417
|
|
|
409
|
-
|
|
418
|
+
# Keep the saved file compact (exclude defaults), but preserve explicit
|
|
419
|
+
# overrides inside provider_list (e.g. `disabled: false` to re-enable a
|
|
420
|
+
# builtin provider that is disabled by default).
|
|
421
|
+
config_dict = user_config.model_dump(
|
|
422
|
+
mode="json",
|
|
423
|
+
exclude_none=True,
|
|
424
|
+
exclude_defaults=True,
|
|
425
|
+
exclude={"provider_list"},
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
provider_list = [
|
|
429
|
+
p.model_dump(mode="json", exclude_none=True, exclude_unset=True) for p in (user_config.provider_list or [])
|
|
430
|
+
]
|
|
431
|
+
if provider_list:
|
|
432
|
+
config_dict["provider_list"] = provider_list
|
|
410
433
|
|
|
411
434
|
def _save_config() -> None:
|
|
412
435
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -454,12 +477,12 @@ def _get_builtin_config() -> Config:
|
|
|
454
477
|
def _merge_model(builtin: ModelConfig, user: ModelConfig) -> ModelConfig:
|
|
455
478
|
"""Merge user model config with builtin model config.
|
|
456
479
|
|
|
457
|
-
Strategy: user values take precedence if explicitly set (not
|
|
458
|
-
This allows users to override specific fields (e.g., disabled=true)
|
|
480
|
+
Strategy: user values take precedence if explicitly set (not unset).
|
|
481
|
+
This allows users to override specific fields (e.g., disabled=true/false)
|
|
459
482
|
without losing other builtin settings (e.g., model_id, max_tokens).
|
|
460
483
|
"""
|
|
461
484
|
merged_data = builtin.model_dump()
|
|
462
|
-
user_data = user.model_dump(
|
|
485
|
+
user_data = user.model_dump(exclude_unset=True, exclude={"model_name"})
|
|
463
486
|
for key, value in user_data.items():
|
|
464
487
|
if value is not None:
|
|
465
488
|
merged_data[key] = value
|
|
@@ -485,10 +508,9 @@ def _merge_provider(builtin: ProviderConfig, user: UserProviderConfig) -> Provid
|
|
|
485
508
|
# New model from user
|
|
486
509
|
merged_models[m.model_name] = m
|
|
487
510
|
|
|
488
|
-
# For other fields, use user values if explicitly set, otherwise use builtin
|
|
489
|
-
# We check if user explicitly provided a value by comparing to defaults
|
|
511
|
+
# For other fields, use user values if explicitly set, otherwise use builtin.
|
|
490
512
|
merged_data = builtin.model_dump()
|
|
491
|
-
user_data = user.model_dump(
|
|
513
|
+
user_data = user.model_dump(exclude_unset=True, exclude={"model_list"})
|
|
492
514
|
|
|
493
515
|
# Update with user's explicit settings
|
|
494
516
|
for key, value in user_data.items():
|
|
@@ -5,13 +5,12 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from klaude_code.protocol import tools
|
|
8
9
|
from klaude_code.protocol.sub_agent import (
|
|
9
10
|
AVAILABILITY_IMAGE_MODEL,
|
|
10
11
|
SubAgentProfile,
|
|
11
12
|
get_sub_agent_profile,
|
|
12
|
-
get_sub_agent_profile_by_tool,
|
|
13
13
|
iter_sub_agent_profiles,
|
|
14
|
-
sub_agent_tool_names,
|
|
15
14
|
)
|
|
16
15
|
from klaude_code.protocol.tools import SubAgentType
|
|
17
16
|
|
|
@@ -30,7 +29,7 @@ class SubAgentModelInfo:
|
|
|
30
29
|
# Effective model name used by this sub-agent.
|
|
31
30
|
# - When configured_model is set: equals configured_model.
|
|
32
31
|
# - When requirement-based default applies (e.g. ImageGen): resolved model.
|
|
33
|
-
# - When inheriting from
|
|
32
|
+
# - When inheriting from defaults: resolved model name.
|
|
34
33
|
effective_model: str | None
|
|
35
34
|
|
|
36
35
|
|
|
@@ -106,10 +105,11 @@ class SubAgentModelHelper:
|
|
|
106
105
|
) -> EmptySubAgentModelBehavior:
|
|
107
106
|
"""Describe what happens when a sub-agent model is not configured.
|
|
108
107
|
|
|
109
|
-
Most sub-agents default to
|
|
108
|
+
Most sub-agents default to the Task model if configured, otherwise
|
|
109
|
+
they inherit the main model.
|
|
110
110
|
|
|
111
111
|
Sub-agents with an availability requirement (e.g. ImageGen) do NOT
|
|
112
|
-
inherit from
|
|
112
|
+
inherit from Task/main; instead they auto-resolve a suitable model
|
|
113
113
|
(currently: the first available image model).
|
|
114
114
|
"""
|
|
115
115
|
|
|
@@ -117,9 +117,11 @@ class SubAgentModelHelper:
|
|
|
117
117
|
|
|
118
118
|
requirement = profile.availability_requirement
|
|
119
119
|
if requirement is None:
|
|
120
|
+
task_model = self._config.sub_agent_models.get(tools.TASK)
|
|
121
|
+
resolved = task_model or main_model_name
|
|
120
122
|
return EmptySubAgentModelBehavior(
|
|
121
|
-
description=f"
|
|
122
|
-
resolved_model_name=
|
|
123
|
+
description=f"use default behavior: {resolved}",
|
|
124
|
+
resolved_model_name=resolved,
|
|
123
125
|
)
|
|
124
126
|
|
|
125
127
|
resolved = self.resolve_model_for_requirement(requirement)
|
|
@@ -154,13 +156,15 @@ class SubAgentModelHelper:
|
|
|
154
156
|
For sub-agents without explicit config, resolves model based on availability_requirement.
|
|
155
157
|
"""
|
|
156
158
|
result: list[SubAgentModelInfo] = []
|
|
157
|
-
for profile in iter_sub_agent_profiles(
|
|
159
|
+
for profile in iter_sub_agent_profiles():
|
|
158
160
|
if not self.check_availability_requirement(profile.availability_requirement):
|
|
159
161
|
continue
|
|
160
162
|
configured_model = self._config.sub_agent_models.get(profile.name)
|
|
161
163
|
effective_model = configured_model
|
|
162
164
|
if not effective_model and profile.availability_requirement:
|
|
163
165
|
effective_model = self.resolve_model_for_requirement(profile.availability_requirement)
|
|
166
|
+
if not effective_model and profile.availability_requirement is None:
|
|
167
|
+
effective_model = self._config.sub_agent_models.get(tools.TASK) or self._config.main_model
|
|
164
168
|
result.append(
|
|
165
169
|
SubAgentModelInfo(
|
|
166
170
|
profile=profile,
|
|
@@ -189,11 +193,9 @@ class SubAgentModelHelper:
|
|
|
189
193
|
|
|
190
194
|
def get_enabled_sub_agent_tool_names(self) -> list[str]:
|
|
191
195
|
"""Return sub-agent tool names that should be added to main agent's tool list."""
|
|
192
|
-
result: list[str] = []
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if profile is not None and self.check_availability_requirement(profile.availability_requirement):
|
|
196
|
-
result.append(name)
|
|
196
|
+
result: list[str] = [tools.TASK]
|
|
197
|
+
if self.check_availability_requirement(AVAILABILITY_IMAGE_MODEL):
|
|
198
|
+
result.append(tools.IMAGE_GEN)
|
|
197
199
|
return result
|
|
198
200
|
|
|
199
201
|
def build_sub_agent_client_configs(self) -> dict[SubAgentType, str]:
|
|
@@ -205,4 +207,7 @@ class SubAgentModelHelper:
|
|
|
205
207
|
model_name = self.resolve_model_for_requirement(profile.availability_requirement)
|
|
206
208
|
if model_name:
|
|
207
209
|
result[profile.name] = model_name
|
|
210
|
+
task_model = self._config.sub_agent_models.get(tools.TASK)
|
|
211
|
+
if task_model:
|
|
212
|
+
result.setdefault(tools.TASK, task_model)
|
|
208
213
|
return result
|
klaude_code/config/thinking.py
CHANGED
|
@@ -62,14 +62,6 @@ def _is_gemini_flash_model(model_name: str | None) -> bool:
|
|
|
62
62
|
return "gemini-3-flash" in model_name.lower()
|
|
63
63
|
|
|
64
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
65
|
def get_levels_for_responses(model_name: str | None) -> list[str]:
|
|
74
66
|
"""Get thinking levels for responses protocol."""
|
|
75
67
|
if _is_codex_max_model(model_name):
|
klaude_code/const.py
CHANGED
|
@@ -81,7 +81,7 @@ MEMORY_FILE_NAMES = ["CLAUDE.md", "AGENTS.md", "AGENT.md"] # Memory file names
|
|
|
81
81
|
READ_CHAR_LIMIT_PER_LINE = 2000 # Maximum characters per line before truncation
|
|
82
82
|
READ_GLOBAL_LINE_CAP = _get_int_env("KLAUDE_READ_GLOBAL_LINE_CAP", 2000) # Maximum lines to read from a file
|
|
83
83
|
READ_MAX_CHARS = _get_int_env("KLAUDE_READ_MAX_CHARS", 50000) # Maximum total characters to read
|
|
84
|
-
READ_MAX_IMAGE_BYTES = _get_int_env("KLAUDE_READ_MAX_IMAGE_BYTES",
|
|
84
|
+
READ_MAX_IMAGE_BYTES = _get_int_env("KLAUDE_READ_MAX_IMAGE_BYTES", 64 * 1024 * 1024) # Max image size (64MB)
|
|
85
85
|
IMAGE_OUTPUT_MAX_BYTES = _get_int_env("KLAUDE_IMAGE_OUTPUT_MAX_BYTES", 64 * 1024 * 1024) # Max decoded image (64MB)
|
|
86
86
|
BINARY_CHECK_SIZE = 8192 # Bytes to check for binary file detection
|
|
87
87
|
|
|
@@ -141,7 +141,7 @@ MIN_HIDDEN_LINES_FOR_INDICATOR = 5 # Minimum hidden lines before showing trunca
|
|
|
141
141
|
SUB_AGENT_RESULT_MAX_LINES = 10 # Maximum lines for sub-agent result display
|
|
142
142
|
TRUNCATE_HEAD_MAX_LINES = 2 # Maximum lines for sub-agent error display
|
|
143
143
|
BASH_OUTPUT_PANEL_THRESHOLD = 10 # Bash output line threshold for CodePanel display
|
|
144
|
-
BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES =
|
|
144
|
+
BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES = 4 # Max lines shown for heredoc / multiline string tokens in bash tool calls
|
|
145
145
|
URL_TRUNCATE_MAX_LENGTH = 400 # Maximum length for URL truncation in display
|
|
146
146
|
QUERY_DISPLAY_TRUNCATE_LENGTH = 80 # Maximum length for search query display
|
|
147
147
|
NOTIFY_COMPACT_LIMIT = 160 # Maximum length for notification body text
|
|
@@ -12,12 +12,10 @@ from typing import TYPE_CHECKING, Any, Protocol
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from klaude_code.config.config import Config
|
|
14
14
|
|
|
15
|
-
from klaude_code.auth.codex.exceptions import CodexUnsupportedModelError
|
|
16
15
|
from klaude_code.config.sub_agent_model_helper import SubAgentModelHelper
|
|
17
16
|
from klaude_code.core.reminders import (
|
|
18
17
|
at_file_reader_reminder,
|
|
19
18
|
empty_todo_reminder,
|
|
20
|
-
file_changed_externally_reminder,
|
|
21
19
|
image_reminder,
|
|
22
20
|
last_path_memory_reminder,
|
|
23
21
|
memory_reminder,
|
|
@@ -28,7 +26,7 @@ from klaude_code.core.tool.report_back_tool import ReportBackTool
|
|
|
28
26
|
from klaude_code.core.tool.tool_registry import get_tool_schemas
|
|
29
27
|
from klaude_code.llm import LLMClientABC
|
|
30
28
|
from klaude_code.protocol import llm_param, message, tools
|
|
31
|
-
from klaude_code.protocol.sub_agent import get_sub_agent_profile
|
|
29
|
+
from klaude_code.protocol.sub_agent import AVAILABILITY_IMAGE_MODEL, get_sub_agent_profile
|
|
32
30
|
from klaude_code.session import Session
|
|
33
31
|
|
|
34
32
|
type Reminder = Callable[[Session], Awaitable[message.DeveloperMessage | None]]
|
|
@@ -54,12 +52,6 @@ COMMAND_DESCRIPTIONS: dict[str, str] = {
|
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
|
|
57
|
-
# Prompts for codex_oauth protocol - must be used exactly as-is without any additions.
|
|
58
|
-
CODEX_OAUTH_PROMPTS: dict[str, str] = {
|
|
59
|
-
"gpt-5.2-codex": "prompts/prompt-codex-gpt-5-2-codex.md",
|
|
60
|
-
"gpt-5.2": "prompts/prompt-codex-gpt-5-2.md",
|
|
61
|
-
}
|
|
62
|
-
|
|
63
55
|
# Prompt for antigravity protocol - used exactly as-is without any additions.
|
|
64
56
|
ANTIGRAVITY_PROMPT_PATH = "prompts/prompt-antigravity.md"
|
|
65
57
|
|
|
@@ -73,20 +65,6 @@ Only the content passed to `report_back` will be returned to user.\
|
|
|
73
65
|
"""
|
|
74
66
|
|
|
75
67
|
|
|
76
|
-
SUB_AGENT_COMMON_PROMPT_FOR_MAIN_AGENT = """\
|
|
77
|
-
|
|
78
|
-
# Sub-agent capabilities
|
|
79
|
-
You have sub-agents (e.g. Task, Explore, WebAgent, ImageGen) with structured output and resume capabilites:
|
|
80
|
-
- Agents can be provided with an `output_format` (JSON Schema) parameter for structured output
|
|
81
|
-
- Example: `output_format={"type": "object", "properties": {"files": {"type": "array", "items": {"type": "string"}, "description": "List of file paths that match the search criteria, e.g. ['src/main.py', 'src/utils/helper.py']"}}, "required": ["files"]}`
|
|
82
|
-
- Agents can be resumed using the `resume` parameter by passing the agent ID from a previous invocation. \
|
|
83
|
-
When resumed, the agent continues with its full previous context preserved. \
|
|
84
|
-
When NOT resuming, each invocation starts fresh and you should provide a detailed task description with all necessary context.
|
|
85
|
-
- When the agent is done, it will return a single message back to you along with its agent ID. \
|
|
86
|
-
You can use this ID to resume the agent later if needed for follow-up work.
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
|
|
90
68
|
@cache
|
|
91
69
|
def _load_prompt_by_path(prompt_path: str) -> str:
|
|
92
70
|
"""Load and cache prompt content from a file path relative to core package."""
|
|
@@ -144,19 +122,6 @@ def _build_env_info(model_name: str) -> str:
|
|
|
144
122
|
return "\n".join(env_lines)
|
|
145
123
|
|
|
146
124
|
|
|
147
|
-
def _has_sub_agents(config: Config | None) -> bool:
|
|
148
|
-
"""Check if there are any sub-agent tools available for the main agent."""
|
|
149
|
-
if config is not None:
|
|
150
|
-
from klaude_code.config.sub_agent_model_helper import SubAgentModelHelper
|
|
151
|
-
|
|
152
|
-
helper = SubAgentModelHelper(config)
|
|
153
|
-
return bool(helper.get_enabled_sub_agent_tool_names())
|
|
154
|
-
|
|
155
|
-
from klaude_code.protocol.sub_agent import sub_agent_tool_names
|
|
156
|
-
|
|
157
|
-
return bool(sub_agent_tool_names(enabled_only=True))
|
|
158
|
-
|
|
159
|
-
|
|
160
125
|
def load_system_prompt(
|
|
161
126
|
model_name: str,
|
|
162
127
|
protocol: llm_param.LLMClientProtocol,
|
|
@@ -165,12 +130,11 @@ def load_system_prompt(
|
|
|
165
130
|
) -> str:
|
|
166
131
|
"""Get system prompt content for the given model and sub-agent type."""
|
|
167
132
|
|
|
168
|
-
# For codex_oauth protocol, use
|
|
133
|
+
# For codex_oauth protocol, use dynamic prompts from GitHub (no additions).
|
|
169
134
|
if protocol == llm_param.LLMClientProtocol.CODEX_OAUTH:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
raise CodexUnsupportedModelError(f"codex_oauth protocol does not support model: {model_name}")
|
|
135
|
+
from klaude_code.llm.codex.prompt_sync import get_codex_instructions
|
|
136
|
+
|
|
137
|
+
return get_codex_instructions(model_name)
|
|
174
138
|
|
|
175
139
|
# For antigravity protocol, use exact prompt without any additions.
|
|
176
140
|
if protocol == llm_param.LLMClientProtocol.ANTIGRAVITY:
|
|
@@ -183,18 +147,13 @@ def load_system_prompt(
|
|
|
183
147
|
base_prompt = _load_prompt_by_model(model_name)
|
|
184
148
|
|
|
185
149
|
skills_prompt = ""
|
|
186
|
-
sub_agent_prompt = ""
|
|
187
150
|
if sub_agent_type is None:
|
|
188
151
|
# Skills are progressive-disclosure: keep only metadata in the system prompt.
|
|
189
152
|
from klaude_code.skill.manager import format_available_skills_for_system_prompt
|
|
190
153
|
|
|
191
154
|
skills_prompt = format_available_skills_for_system_prompt()
|
|
192
155
|
|
|
193
|
-
|
|
194
|
-
if _has_sub_agents(config):
|
|
195
|
-
sub_agent_prompt = "\n" + SUB_AGENT_COMMON_PROMPT_FOR_MAIN_AGENT
|
|
196
|
-
|
|
197
|
-
return base_prompt + _build_env_info(model_name) + skills_prompt + sub_agent_prompt
|
|
156
|
+
return base_prompt + _build_env_info(model_name) + skills_prompt
|
|
198
157
|
|
|
199
158
|
|
|
200
159
|
def load_agent_tools(
|
|
@@ -222,13 +181,13 @@ def load_agent_tools(
|
|
|
222
181
|
else:
|
|
223
182
|
tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.TODO_WRITE]
|
|
224
183
|
|
|
184
|
+
tool_names.append(tools.TASK)
|
|
225
185
|
if config is not None:
|
|
226
186
|
helper = SubAgentModelHelper(config)
|
|
227
|
-
|
|
187
|
+
if helper.check_availability_requirement(AVAILABILITY_IMAGE_MODEL):
|
|
188
|
+
tool_names.append(tools.IMAGE_GEN)
|
|
228
189
|
else:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
tool_names.extend(sub_agent_tool_names(enabled_only=True))
|
|
190
|
+
tool_names.append(tools.IMAGE_GEN)
|
|
232
191
|
|
|
233
192
|
tool_names.extend([tools.MERMAID])
|
|
234
193
|
# tool_names.extend([tools.MEMORY])
|
|
@@ -258,7 +217,6 @@ def load_agent_reminders(
|
|
|
258
217
|
memory_reminder,
|
|
259
218
|
at_file_reader_reminder,
|
|
260
219
|
last_path_memory_reminder,
|
|
261
|
-
file_changed_externally_reminder,
|
|
262
220
|
image_reminder,
|
|
263
221
|
skill_reminder,
|
|
264
222
|
]
|
|
@@ -311,7 +269,7 @@ class DefaultModelProfileProvider(ModelProfileProvider):
|
|
|
311
269
|
if is_image_model:
|
|
312
270
|
agent_system_prompt: str | None = None
|
|
313
271
|
agent_tools: list[llm_param.ToolSchema] = []
|
|
314
|
-
agent_reminders: list[Reminder] = []
|
|
272
|
+
agent_reminders: list[Reminder] = [at_file_reader_reminder, image_reminder]
|
|
315
273
|
else:
|
|
316
274
|
agent_system_prompt = load_system_prompt(
|
|
317
275
|
model_name, llm_client.protocol, sub_agent_type, config=self._config
|
|
@@ -268,9 +268,7 @@ def _call_args_probably_modify_file(args: dict[str, object]) -> bool:
|
|
|
268
268
|
return True
|
|
269
269
|
# Batch edits.
|
|
270
270
|
edits = args.get("edits")
|
|
271
|
-
|
|
272
|
-
return True
|
|
273
|
-
return False
|
|
271
|
+
return bool(isinstance(edits, list))
|
|
274
272
|
|
|
275
273
|
|
|
276
274
|
def _collect_file_operations(
|
|
@@ -478,12 +476,12 @@ async def _build_summary(
|
|
|
478
476
|
previous_summary,
|
|
479
477
|
cancel,
|
|
480
478
|
)
|
|
481
|
-
if messages_to_summarize
|
|
482
|
-
else asyncio.sleep(0, result=
|
|
479
|
+
if messages_to_summarize
|
|
480
|
+
else asyncio.sleep(0, result=previous_summary or "")
|
|
483
481
|
)
|
|
484
482
|
prefix_task = _generate_task_prefix_summary(task_prefix_messages, llm_client, config, cancel)
|
|
485
483
|
history_summary, task_prefix_summary = await asyncio.gather(history_task, prefix_task)
|
|
486
|
-
return f"{COMPACTION_SUMMARY_PREFIX}\n\n<summary>{history_summary}\n\n---\n\n**Task Context (
|
|
484
|
+
return f"{COMPACTION_SUMMARY_PREFIX}\n\n<summary>{history_summary}\n\n---\n\n**Task Context (current task):**\n\n{task_prefix_summary}\n\n</summary>"
|
|
487
485
|
|
|
488
486
|
return await _generate_summary(
|
|
489
487
|
messages_to_summarize,
|
|
@@ -24,7 +24,3 @@ def is_context_overflow(error_message: str | None) -> bool:
|
|
|
24
24
|
if _STATUS_CODE_PATTERN.search(error_message):
|
|
25
25
|
return True
|
|
26
26
|
return any(pattern.search(error_message) for pattern in _OVERFLOW_PATTERNS)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_overflow_patterns() -> list[re.Pattern[str]]:
|
|
30
|
-
return list(_OVERFLOW_PATTERNS)
|
klaude_code/core/executor.py
CHANGED
|
@@ -179,6 +179,21 @@ class AgentRuntime:
|
|
|
179
179
|
)
|
|
180
180
|
self._task_manager.register(operation.id, task, operation.session_id)
|
|
181
181
|
|
|
182
|
+
async def continue_agent(self, operation: op.ContinueAgentOperation) -> None:
|
|
183
|
+
"""Continue agent execution without adding a new user message."""
|
|
184
|
+
agent = await self.ensure_agent(operation.session_id)
|
|
185
|
+
|
|
186
|
+
existing_active = self._task_manager.get(operation.id)
|
|
187
|
+
if existing_active is not None and not existing_active.task.done():
|
|
188
|
+
raise RuntimeError(f"Active task already registered for operation {operation.id}")
|
|
189
|
+
|
|
190
|
+
# Use empty input since we're continuing from existing history
|
|
191
|
+
empty_input = message.UserInputPayload(text="")
|
|
192
|
+
task: asyncio.Task[None] = asyncio.create_task(
|
|
193
|
+
self._run_agent_task(agent, empty_input, operation.id, operation.session_id)
|
|
194
|
+
)
|
|
195
|
+
self._task_manager.register(operation.id, task, operation.session_id)
|
|
196
|
+
|
|
182
197
|
async def compact_session(self, operation: op.CompactSessionOperation) -> None:
|
|
183
198
|
agent = await self.ensure_agent(operation.session_id)
|
|
184
199
|
|
|
@@ -525,6 +540,9 @@ class ExecutorContext:
|
|
|
525
540
|
async def handle_run_agent(self, operation: op.RunAgentOperation) -> None:
|
|
526
541
|
await self._agent_runtime.run_agent(operation)
|
|
527
542
|
|
|
543
|
+
async def handle_continue_agent(self, operation: op.ContinueAgentOperation) -> None:
|
|
544
|
+
await self._agent_runtime.continue_agent(operation)
|
|
545
|
+
|
|
528
546
|
async def handle_compact_session(self, operation: op.CompactSessionOperation) -> None:
|
|
529
547
|
await self._agent_runtime.compact_session(operation)
|
|
530
548
|
|
|
@@ -658,6 +676,39 @@ class ExecutorContext:
|
|
|
658
676
|
)
|
|
659
677
|
)
|
|
660
678
|
|
|
679
|
+
async def handle_change_compact_model(self, operation: op.ChangeCompactModelOperation) -> None:
|
|
680
|
+
"""Handle a change compact model operation."""
|
|
681
|
+
agent = await self._agent_runtime.ensure_agent(operation.session_id)
|
|
682
|
+
config = load_config()
|
|
683
|
+
|
|
684
|
+
model_name = operation.model_name
|
|
685
|
+
|
|
686
|
+
if model_name is None:
|
|
687
|
+
# Clear explicit override and use main client for compaction
|
|
688
|
+
self.llm_clients.compact = None
|
|
689
|
+
agent.compact_llm_client = None
|
|
690
|
+
display_model = "(inherit from main agent)"
|
|
691
|
+
else:
|
|
692
|
+
# Create new client for compaction
|
|
693
|
+
llm_config = config.get_model_config(model_name)
|
|
694
|
+
new_client = create_llm_client(llm_config)
|
|
695
|
+
self.llm_clients.compact = new_client
|
|
696
|
+
agent.compact_llm_client = new_client
|
|
697
|
+
display_model = new_client.model_name
|
|
698
|
+
|
|
699
|
+
if operation.save_as_default:
|
|
700
|
+
config.compact_model = model_name
|
|
701
|
+
await config.save()
|
|
702
|
+
|
|
703
|
+
saved_note = " (saved in ~/.klaude/klaude-config.yaml)" if operation.save_as_default else ""
|
|
704
|
+
await self.emit_event(
|
|
705
|
+
events.CommandOutputEvent(
|
|
706
|
+
session_id=agent.session.id,
|
|
707
|
+
command_name=commands.CommandName.SUB_AGENT_MODEL,
|
|
708
|
+
content=f"Compact model: {display_model}{saved_note}",
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
|
|
661
712
|
async def handle_clear_session(self, operation: op.ClearSessionOperation) -> None:
|
|
662
713
|
await self._agent_runtime.clear_session(operation.session_id)
|
|
663
714
|
|
|
@@ -745,11 +796,6 @@ class ExecutorContext:
|
|
|
745
796
|
return None
|
|
746
797
|
return active.task
|
|
747
798
|
|
|
748
|
-
def has_active_task(self, submission_id: str) -> bool:
|
|
749
|
-
"""Return True if a task is registered for the submission id."""
|
|
750
|
-
|
|
751
|
-
return self.task_manager.get(submission_id) is not None
|
|
752
|
-
|
|
753
799
|
|
|
754
800
|
class Executor:
|
|
755
801
|
"""
|
|
@@ -6,6 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
from dataclasses import field as dataclass_field
|
|
7
7
|
|
|
8
8
|
from klaude_code.llm.client import LLMClientABC
|
|
9
|
+
from klaude_code.protocol import tools
|
|
9
10
|
from klaude_code.protocol.tools import SubAgentType
|
|
10
11
|
|
|
11
12
|
|
|
@@ -26,7 +27,14 @@ class LLMClients:
|
|
|
26
27
|
|
|
27
28
|
if sub_agent_type is None:
|
|
28
29
|
return self.main
|
|
29
|
-
|
|
30
|
+
client = self.sub_clients.get(sub_agent_type)
|
|
31
|
+
if client is not None:
|
|
32
|
+
return client
|
|
33
|
+
if sub_agent_type != tools.TASK:
|
|
34
|
+
fallback = self.sub_clients.get(tools.TASK)
|
|
35
|
+
if fallback is not None:
|
|
36
|
+
return fallback
|
|
37
|
+
return self.main
|
|
30
38
|
|
|
31
39
|
def get_compact_client(self) -> LLMClientABC:
|
|
32
40
|
"""Return compact client if configured, otherwise main client."""
|
|
@@ -72,16 +72,16 @@ The user will primarily request you perform software engineering tasks. This inc
|
|
|
72
72
|
- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.
|
|
73
73
|
|
|
74
74
|
## Tool usage policy
|
|
75
|
-
- When doing file search, prefer to use the
|
|
75
|
+
- When doing file search, prefer to use the Task tool in order to reduce context usage.
|
|
76
76
|
- You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
|
|
77
77
|
- If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to launch multiple agents in parallel, send a single message with multiple Task tool calls.
|
|
78
78
|
- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
|
|
79
|
-
- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the
|
|
79
|
+
- VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the Task tool with subagent_type=Explore instead of running search commands directly.
|
|
80
80
|
<example>
|
|
81
81
|
user: Where are errors from the client handled?
|
|
82
|
-
assistant: [Uses the
|
|
82
|
+
assistant: [Uses the Task tool with subagent_type=Explore to find the files that handle client errors instead of using Glob or Grep directly]
|
|
83
83
|
</example>
|
|
84
84
|
<example>
|
|
85
85
|
user: What is the codebase structure?
|
|
86
|
-
assistant: [Uses the
|
|
86
|
+
assistant: [Uses the Task tool with subagent_type=Explore]
|
|
87
87
|
</example>
|