klaude-code 1.2.2__py3-none-any.whl → 1.2.4__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/cli/main.py +7 -0
- klaude_code/cli/runtime.py +6 -6
- klaude_code/command/__init__.py +9 -5
- klaude_code/command/clear_cmd.py +3 -24
- klaude_code/command/command_abc.py +36 -1
- klaude_code/command/export_cmd.py +16 -20
- klaude_code/command/help_cmd.py +1 -0
- klaude_code/command/model_cmd.py +3 -30
- klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
- klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
- klaude_code/command/prompt-init.md +2 -5
- klaude_code/command/prompt_command.py +3 -3
- klaude_code/command/registry.py +6 -7
- klaude_code/command/status_cmd.py +111 -0
- klaude_code/config/config.py +1 -1
- klaude_code/config/list_model.py +1 -1
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/agent.py +2 -11
- klaude_code/core/executor.py +155 -14
- klaude_code/core/prompts/prompt-gemini.md +1 -1
- klaude_code/core/reminders.py +24 -0
- klaude_code/core/task.py +10 -0
- klaude_code/core/tool/shell/bash_tool.py +6 -2
- klaude_code/core/tool/sub_agent_tool.py +1 -1
- klaude_code/core/tool/tool_context.py +1 -1
- klaude_code/core/tool/tool_registry.py +1 -1
- klaude_code/core/tool/tool_runner.py +1 -1
- klaude_code/core/tool/web/mermaid_tool.py +1 -1
- klaude_code/llm/__init__.py +3 -4
- klaude_code/llm/anthropic/client.py +12 -9
- klaude_code/llm/openai_compatible/client.py +2 -18
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
- klaude_code/llm/openrouter/client.py +2 -18
- klaude_code/llm/openrouter/input.py +6 -2
- klaude_code/llm/registry.py +2 -71
- klaude_code/llm/responses/client.py +2 -0
- klaude_code/llm/{metadata_tracker.py → usage.py} +49 -2
- klaude_code/protocol/commands.py +1 -0
- klaude_code/protocol/llm_param.py +12 -0
- klaude_code/protocol/model.py +30 -3
- klaude_code/protocol/op.py +14 -14
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/protocol/tools.py +0 -2
- klaude_code/session/export.py +124 -35
- klaude_code/session/session.py +1 -1
- klaude_code/session/templates/export_session.html +383 -39
- klaude_code/ui/__init__.py +6 -2
- klaude_code/ui/modes/exec/display.py +26 -0
- klaude_code/ui/modes/repl/event_handler.py +5 -1
- klaude_code/ui/renderers/developer.py +62 -11
- klaude_code/ui/renderers/metadata.py +33 -24
- klaude_code/ui/renderers/sub_agent.py +1 -1
- klaude_code/ui/renderers/tools.py +2 -2
- klaude_code/ui/renderers/user_input.py +18 -22
- klaude_code/ui/rich/status.py +13 -2
- {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.dist-info}/METADATA +1 -1
- {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.dist-info}/RECORD +60 -58
- /klaude_code/{core → protocol}/sub_agent.py +0 -0
- {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.2.dist-info → klaude_code-1.2.4.dist-info}/entry_points.txt +0 -0
klaude_code/cli/main.py
CHANGED
|
@@ -62,6 +62,7 @@ def list_models() -> None:
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
@app.command("config")
|
|
65
|
+
@app.command("conf", hidden=True)
|
|
65
66
|
def edit_config() -> None:
|
|
66
67
|
"""Open the configuration file in $EDITOR or default system editor"""
|
|
67
68
|
editor = os.environ.get("EDITOR")
|
|
@@ -148,6 +149,11 @@ def exec_command(
|
|
|
148
149
|
"--vanilla",
|
|
149
150
|
help="Vanilla mode exposes the model's raw API behavior: it provides only minimal tools (Bash, Read & Edit) and omits system prompts and reminders.",
|
|
150
151
|
),
|
|
152
|
+
stream_json: bool = typer.Option(
|
|
153
|
+
False,
|
|
154
|
+
"--stream-json",
|
|
155
|
+
help="Stream all events as JSON lines to stdout.",
|
|
156
|
+
),
|
|
151
157
|
) -> None:
|
|
152
158
|
"""Execute non-interactively with provided input."""
|
|
153
159
|
|
|
@@ -193,6 +199,7 @@ def exec_command(
|
|
|
193
199
|
vanilla=vanilla,
|
|
194
200
|
is_exec_mode=True,
|
|
195
201
|
debug_filters=debug_filters,
|
|
202
|
+
stream_json=stream_json,
|
|
196
203
|
)
|
|
197
204
|
|
|
198
205
|
asyncio.run(
|
klaude_code/cli/runtime.py
CHANGED
|
@@ -8,15 +8,14 @@ import typer
|
|
|
8
8
|
from rich.text import Text
|
|
9
9
|
|
|
10
10
|
from klaude_code import ui
|
|
11
|
-
from klaude_code.command import
|
|
11
|
+
from klaude_code.command import has_interactive_command
|
|
12
12
|
from klaude_code.config import Config, load_config
|
|
13
13
|
from klaude_code.core.agent import DefaultModelProfileProvider, VanillaModelProfileProvider
|
|
14
|
-
from klaude_code.core.executor import Executor
|
|
15
|
-
from klaude_code.core.sub_agent import iter_sub_agent_profiles
|
|
14
|
+
from klaude_code.core.executor import Executor, LLMClients
|
|
16
15
|
from klaude_code.core.tool import SkillLoader, SkillTool
|
|
17
|
-
from klaude_code.llm import LLMClients
|
|
18
16
|
from klaude_code.protocol import events, op
|
|
19
17
|
from klaude_code.protocol.model import UserInputPayload
|
|
18
|
+
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
20
19
|
from klaude_code.trace import DebugType, log, set_debug_logging
|
|
21
20
|
from klaude_code.ui.modes.repl import build_repl_status_snapshot
|
|
22
21
|
from klaude_code.ui.modes.repl.input_prompt_toolkit import REPLStatusSnapshot
|
|
@@ -72,6 +71,7 @@ class AppInitConfig:
|
|
|
72
71
|
vanilla: bool
|
|
73
72
|
is_exec_mode: bool = False
|
|
74
73
|
debug_filters: set[DebugType] | None = None
|
|
74
|
+
stream_json: bool = False
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
@dataclass
|
|
@@ -148,7 +148,7 @@ async def initialize_app_components(init_config: AppInitConfig) -> AppComponents
|
|
|
148
148
|
# Set up UI components using factory functions
|
|
149
149
|
display: ui.DisplayABC
|
|
150
150
|
if init_config.is_exec_mode:
|
|
151
|
-
display = ui.create_exec_display(debug=init_config.debug)
|
|
151
|
+
display = ui.create_exec_display(debug=init_config.debug, stream_json=init_config.stream_json)
|
|
152
152
|
else:
|
|
153
153
|
display = ui.create_default_display(debug=init_config.debug, theme=theme)
|
|
154
154
|
|
|
@@ -300,7 +300,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
300
300
|
)
|
|
301
301
|
# If it's an interactive command (e.g., /model), avoid starting the ESC monitor
|
|
302
302
|
# to prevent TTY conflicts with interactive prompts (questionary/prompt_toolkit).
|
|
303
|
-
if
|
|
303
|
+
if has_interactive_command(user_input.text):
|
|
304
304
|
await components.executor.wait_for(submission_id)
|
|
305
305
|
else:
|
|
306
306
|
# Esc monitor for long-running, interruptible operations
|
klaude_code/command/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .clear_cmd import ClearCommand
|
|
2
|
-
from .command_abc import CommandABC, CommandResult
|
|
2
|
+
from .command_abc import CommandABC, CommandResult, InputAction, InputActionType
|
|
3
3
|
from .diff_cmd import DiffCommand
|
|
4
4
|
from .export_cmd import ExportCommand
|
|
5
5
|
from .help_cmd import HelpCommand
|
|
@@ -10,12 +10,13 @@ from .model_cmd import ModelCommand
|
|
|
10
10
|
from .refresh_cmd import RefreshTerminalCommand
|
|
11
11
|
from .registry import (
|
|
12
12
|
dispatch_command,
|
|
13
|
-
get_command_names,
|
|
14
13
|
get_commands,
|
|
15
|
-
|
|
14
|
+
has_interactive_command,
|
|
15
|
+
is_slash_command_name,
|
|
16
16
|
load_prompt_commands,
|
|
17
17
|
register_command,
|
|
18
18
|
)
|
|
19
|
+
from .status_cmd import StatusCommand
|
|
19
20
|
from .terminal_setup_cmd import TerminalSetupCommand
|
|
20
21
|
|
|
21
22
|
# Dynamically load prompt commands
|
|
@@ -28,12 +29,15 @@ __all__ = [
|
|
|
28
29
|
"ModelCommand",
|
|
29
30
|
"ExportCommand",
|
|
30
31
|
"RefreshTerminalCommand",
|
|
32
|
+
"StatusCommand",
|
|
31
33
|
"TerminalSetupCommand",
|
|
32
34
|
"register_command",
|
|
33
35
|
"CommandABC",
|
|
34
36
|
"CommandResult",
|
|
37
|
+
"InputAction",
|
|
38
|
+
"InputActionType",
|
|
35
39
|
"dispatch_command",
|
|
36
40
|
"get_commands",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
41
|
+
"is_slash_command_name",
|
|
42
|
+
"has_interactive_command",
|
|
39
43
|
]
|
klaude_code/command/clear_cmd.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from klaude_code.command.command_abc import CommandABC, CommandResult
|
|
1
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
|
|
2
2
|
from klaude_code.command.registry import register_command
|
|
3
3
|
from klaude_code.core.agent import Agent
|
|
4
|
-
from klaude_code.protocol import commands
|
|
5
|
-
from klaude_code.session.session import Session
|
|
4
|
+
from klaude_code.protocol import commands
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
@register_command
|
|
@@ -18,24 +17,4 @@ class ClearCommand(CommandABC):
|
|
|
18
17
|
return "Clear conversation history and free up context"
|
|
19
18
|
|
|
20
19
|
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
21
|
-
|
|
22
|
-
new_session = Session(work_dir=agent.session.work_dir)
|
|
23
|
-
new_session.model_name = agent.session.model_name
|
|
24
|
-
|
|
25
|
-
# Replace the agent's session with the new one
|
|
26
|
-
agent.session = new_session
|
|
27
|
-
|
|
28
|
-
# Save the new session
|
|
29
|
-
agent.session.save()
|
|
30
|
-
|
|
31
|
-
return CommandResult(
|
|
32
|
-
events=[
|
|
33
|
-
events.DeveloperMessageEvent(
|
|
34
|
-
session_id=agent.session.id,
|
|
35
|
-
item=model.DeveloperMessageItem(
|
|
36
|
-
content="started new conversation",
|
|
37
|
-
command_output=model.CommandOutput(command_name=self.name),
|
|
38
|
-
),
|
|
39
|
-
),
|
|
40
|
-
]
|
|
41
|
-
)
|
|
20
|
+
return CommandResult(actions=[InputAction.clear()])
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
+
from enum import Enum
|
|
2
3
|
|
|
3
4
|
from pydantic import BaseModel
|
|
4
5
|
|
|
@@ -7,14 +8,48 @@ from klaude_code.protocol import commands
|
|
|
7
8
|
from klaude_code.protocol import events as protocol_events
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
class InputActionType(str, Enum):
|
|
12
|
+
"""Supported input action kinds."""
|
|
13
|
+
|
|
14
|
+
RUN_AGENT = "run_agent"
|
|
15
|
+
CHANGE_MODEL = "change_model"
|
|
16
|
+
CLEAR = "clear"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InputAction(BaseModel):
|
|
20
|
+
"""Structured executor action derived from a user input."""
|
|
21
|
+
|
|
22
|
+
type: InputActionType
|
|
23
|
+
text: str = ""
|
|
24
|
+
model_name: str | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def run_agent(cls, text: str) -> "InputAction":
|
|
28
|
+
"""Create a RunAgent action preserving the provided text."""
|
|
29
|
+
|
|
30
|
+
return cls(type=InputActionType.RUN_AGENT, text=text)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def change_model(cls, model_name: str) -> "InputAction":
|
|
34
|
+
"""Create a ChangeModel action for the provided model name."""
|
|
35
|
+
|
|
36
|
+
return cls(type=InputActionType.CHANGE_MODEL, model_name=model_name)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def clear(cls) -> "InputAction":
|
|
40
|
+
"""Create a Clear action to reset the session."""
|
|
41
|
+
|
|
42
|
+
return cls(type=InputActionType.CLEAR)
|
|
43
|
+
|
|
44
|
+
|
|
10
45
|
class CommandResult(BaseModel):
|
|
11
46
|
"""Result of a command execution."""
|
|
12
47
|
|
|
13
|
-
agent_input: str | None = None # Input to be submitted to agent, or None if no input needed
|
|
14
48
|
events: (
|
|
15
49
|
list[protocol_events.DeveloperMessageEvent | protocol_events.WelcomeEvent | protocol_events.ReplayHistoryEvent]
|
|
16
50
|
| None
|
|
17
51
|
) = None # List of UI events to display immediately
|
|
52
|
+
actions: list[InputAction] | None = None
|
|
18
53
|
|
|
19
54
|
|
|
20
55
|
class CommandABC(ABC):
|
|
@@ -37,29 +37,25 @@ class ExportCommand(CommandABC):
|
|
|
37
37
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
38
|
output_path.write_text(html_doc, encoding="utf-8")
|
|
39
39
|
self._open_file(output_path)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
command_output=model.CommandOutput(command_name=self.name),
|
|
47
|
-
),
|
|
48
|
-
)
|
|
49
|
-
]
|
|
40
|
+
event = events.DeveloperMessageEvent(
|
|
41
|
+
session_id=agent.session.id,
|
|
42
|
+
item=model.DeveloperMessageItem(
|
|
43
|
+
content=f"Session exported and opened: {output_path}",
|
|
44
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
45
|
+
),
|
|
50
46
|
)
|
|
47
|
+
return CommandResult(events=[event])
|
|
51
48
|
except Exception as exc: # pragma: no cover - safeguard for unexpected errors
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
61
|
-
]
|
|
49
|
+
import traceback
|
|
50
|
+
|
|
51
|
+
event = events.DeveloperMessageEvent(
|
|
52
|
+
session_id=agent.session.id,
|
|
53
|
+
item=model.DeveloperMessageItem(
|
|
54
|
+
content=f"Failed to export session: {exc}\n{traceback.format_exc()}",
|
|
55
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
56
|
+
),
|
|
62
57
|
)
|
|
58
|
+
return CommandResult(events=[event])
|
|
63
59
|
|
|
64
60
|
def _resolve_output_path(self, raw: str, agent: Agent) -> Path:
|
|
65
61
|
trimmed = raw.strip()
|
klaude_code/command/help_cmd.py
CHANGED
|
@@ -23,6 +23,7 @@ Usage:
|
|
|
23
23
|
[b]@[/b] to mention file
|
|
24
24
|
[b]esc[/b] to interrupt agent task
|
|
25
25
|
[b]shift-enter[/b] or [b]ctrl-j[/b] for new line
|
|
26
|
+
[b]ctrl-v[/b] for pasting image
|
|
26
27
|
[b]--continue[/b] or [b]--resume[/b] to continue an old session
|
|
27
28
|
[b]--select-model[/b] to switch model
|
|
28
29
|
|
klaude_code/command/model_cmd.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
-
from klaude_code.command.command_abc import CommandABC, CommandResult
|
|
3
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
|
|
4
4
|
from klaude_code.command.registry import register_command
|
|
5
|
-
from klaude_code.config import
|
|
5
|
+
from klaude_code.config import select_model_from_config
|
|
6
6
|
from klaude_code.core.agent import Agent
|
|
7
|
-
from klaude_code.llm import create_llm_client
|
|
8
7
|
from klaude_code.protocol import commands, events, model
|
|
9
|
-
from klaude_code.trace import DebugType, log_debug
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
@register_command
|
|
@@ -42,29 +40,4 @@ class ModelCommand(CommandABC):
|
|
|
42
40
|
]
|
|
43
41
|
)
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
assert config is not None
|
|
47
|
-
llm_config = config.get_model_config(selected_model)
|
|
48
|
-
|
|
49
|
-
log_debug(
|
|
50
|
-
"Updated LLM config",
|
|
51
|
-
llm_config.model_dump_json(exclude_none=True),
|
|
52
|
-
style="yellow",
|
|
53
|
-
debug_type=DebugType.LLM_CONFIG,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
llm_client = create_llm_client(llm_config)
|
|
57
|
-
agent.set_model_profile(agent.build_model_profile(llm_client))
|
|
58
|
-
|
|
59
|
-
return CommandResult(
|
|
60
|
-
events=[
|
|
61
|
-
events.DeveloperMessageEvent(
|
|
62
|
-
session_id=agent.session.id,
|
|
63
|
-
item=model.DeveloperMessageItem(
|
|
64
|
-
content=f"switched to model: {selected_model}",
|
|
65
|
-
command_output=model.CommandOutput(command_name=self.name),
|
|
66
|
-
),
|
|
67
|
-
),
|
|
68
|
-
events.WelcomeEvent(llm_config=llm_config, work_dir=str(agent.session.work_dir)),
|
|
69
|
-
]
|
|
70
|
-
)
|
|
43
|
+
return CommandResult(actions=[InputAction.change_model(selected_model)])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Update dev documentation before context compaction
|
|
3
|
-
|
|
3
|
+
from: https://github.com/diet103/claude-code-infrastructure-showcase/blob/main/.claude/commands/dev-docs-update.md
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
We're approaching context limits. Please update the development documentation to ensure seamless continuation after context reset.
|
|
@@ -50,6 +50,7 @@ If switching to a new conversation:
|
|
|
50
50
|
- Any uncommitted changes that need attention
|
|
51
51
|
- Test commands to verify work
|
|
52
52
|
|
|
53
|
-
## Additional Context:
|
|
53
|
+
## Additional Context:
|
|
54
|
+
$ARGUMENTS
|
|
54
55
|
|
|
55
56
|
**Priority**: Focus on capturing information that would be hard to rediscover or reconstruct from code alone.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Create a comprehensive strategic plan with structured task breakdown
|
|
3
|
-
|
|
3
|
+
from: https://github.com/diet103/claude-code-infrastructure-showcase/blob/main/.claude/commands/dev-docs.md
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Create a comprehensive, actionable plan for:
|
|
7
|
+
$ARGUMENTS
|
|
7
8
|
|
|
8
9
|
## Instructions
|
|
9
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Create an AGENTS.md file with instructions for agent
|
|
3
|
-
|
|
3
|
+
from: https://github.com/openai/codex/blob/main/codex-rs/tui/prompt_for_init_command.md
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Generate/Update a file named AGENTS.md that serves as a contributor guide for this repository.
|
|
@@ -42,7 +42,4 @@ Commit & Pull Request Guidelines
|
|
|
42
42
|
- Summarize commit message conventions found in the project’s Git history.
|
|
43
43
|
- Outline pull request requirements (descriptions, linked issues, screenshots, etc.).
|
|
44
44
|
|
|
45
|
-
(Optional) Add other sections if relevant, such as Security & Configuration Tips, Architecture Overview, or Agent-Specific Instructions.
|
|
46
|
-
|
|
47
|
-
Additional Instructions:
|
|
48
|
-
$ARGUMENTS
|
|
45
|
+
(Optional) Add other sections if relevant, such as Security & Configuration Tips, Architecture Overview, or Agent-Specific Instructions.
|
|
@@ -2,7 +2,7 @@ from importlib.resources import files
|
|
|
2
2
|
|
|
3
3
|
import yaml
|
|
4
4
|
|
|
5
|
-
from klaude_code.command.command_abc import CommandABC, CommandResult
|
|
5
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult, InputAction
|
|
6
6
|
from klaude_code.core.agent import Agent
|
|
7
7
|
from klaude_code.protocol import commands
|
|
8
8
|
|
|
@@ -57,7 +57,7 @@ class PromptCommand(CommandABC):
|
|
|
57
57
|
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
58
58
|
self._ensure_loaded()
|
|
59
59
|
template_content = self._content or ""
|
|
60
|
-
user_input = raw.strip()
|
|
60
|
+
user_input = raw.strip() or "<none>"
|
|
61
61
|
|
|
62
62
|
if "$ARGUMENTS" in template_content:
|
|
63
63
|
final_prompt = template_content.replace("$ARGUMENTS", user_input)
|
|
@@ -66,4 +66,4 @@ class PromptCommand(CommandABC):
|
|
|
66
66
|
if user_input:
|
|
67
67
|
final_prompt += f"\n\nAdditional Instructions:\n{user_input}"
|
|
68
68
|
|
|
69
|
-
return CommandResult(
|
|
69
|
+
return CommandResult(actions=[InputAction.run_agent(final_prompt)])
|
klaude_code/command/registry.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from importlib.resources import files
|
|
2
2
|
from typing import TYPE_CHECKING, TypeVar
|
|
3
3
|
|
|
4
|
-
from klaude_code.command.command_abc import CommandResult
|
|
4
|
+
from klaude_code.command.command_abc import CommandResult, InputAction
|
|
5
5
|
from klaude_code.command.prompt_command import PromptCommand
|
|
6
6
|
from klaude_code.core.agent import Agent
|
|
7
7
|
from klaude_code.protocol import commands, events, model
|
|
@@ -40,15 +40,14 @@ def get_commands() -> dict[commands.CommandName | str, "CommandABC"]:
|
|
|
40
40
|
return _COMMANDS.copy()
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
return [str(k) for k in _COMMANDS.keys()]
|
|
43
|
+
def is_slash_command_name(name: str) -> bool:
|
|
44
|
+
return name in _COMMANDS
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
|
|
49
48
|
# Detect command name
|
|
50
49
|
if not raw.startswith("/"):
|
|
51
|
-
return CommandResult(
|
|
50
|
+
return CommandResult(actions=[InputAction.run_agent(raw)])
|
|
52
51
|
|
|
53
52
|
splits = raw.split(" ", maxsplit=1)
|
|
54
53
|
command_name_raw = splits[0][1:]
|
|
@@ -70,7 +69,7 @@ async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
|
|
|
70
69
|
pass
|
|
71
70
|
|
|
72
71
|
if command_key is None:
|
|
73
|
-
return CommandResult(
|
|
72
|
+
return CommandResult(actions=[InputAction.run_agent(raw)])
|
|
74
73
|
|
|
75
74
|
command = _COMMANDS[command_key]
|
|
76
75
|
command_identifier: commands.CommandName | str = command.name
|
|
@@ -96,7 +95,7 @@ async def dispatch_command(raw: str, agent: Agent) -> CommandResult:
|
|
|
96
95
|
)
|
|
97
96
|
|
|
98
97
|
|
|
99
|
-
def
|
|
98
|
+
def has_interactive_command(raw: str) -> bool:
|
|
100
99
|
if not raw.startswith("/"):
|
|
101
100
|
return False
|
|
102
101
|
splits = raw.split(" ", maxsplit=1)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from klaude_code.command.command_abc import CommandABC, CommandResult
|
|
2
|
+
from klaude_code.command.registry import register_command
|
|
3
|
+
from klaude_code.core.agent import Agent
|
|
4
|
+
from klaude_code.protocol import commands, events, model
|
|
5
|
+
from klaude_code.session.session import Session
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def accumulate_session_usage(session: Session) -> tuple[model.Usage, int]:
|
|
9
|
+
"""Accumulate usage statistics from all ResponseMetadataItems in session history.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
A tuple of (accumulated_usage, task_count)
|
|
13
|
+
"""
|
|
14
|
+
total = model.Usage()
|
|
15
|
+
task_count = 0
|
|
16
|
+
|
|
17
|
+
for item in session.conversation_history:
|
|
18
|
+
if isinstance(item, model.ResponseMetadataItem) and item.usage:
|
|
19
|
+
task_count += 1
|
|
20
|
+
usage = item.usage
|
|
21
|
+
total.input_tokens += usage.input_tokens
|
|
22
|
+
total.cached_tokens += usage.cached_tokens
|
|
23
|
+
total.reasoning_tokens += usage.reasoning_tokens
|
|
24
|
+
total.output_tokens += usage.output_tokens
|
|
25
|
+
total.total_tokens += usage.total_tokens
|
|
26
|
+
|
|
27
|
+
# Accumulate costs
|
|
28
|
+
if usage.input_cost is not None:
|
|
29
|
+
total.input_cost = (total.input_cost or 0.0) + usage.input_cost
|
|
30
|
+
if usage.output_cost is not None:
|
|
31
|
+
total.output_cost = (total.output_cost or 0.0) + usage.output_cost
|
|
32
|
+
if usage.cache_read_cost is not None:
|
|
33
|
+
total.cache_read_cost = (total.cache_read_cost or 0.0) + usage.cache_read_cost
|
|
34
|
+
if usage.total_cost is not None:
|
|
35
|
+
total.total_cost = (total.total_cost or 0.0) + usage.total_cost
|
|
36
|
+
|
|
37
|
+
# Keep the latest context_usage_percent
|
|
38
|
+
if usage.context_usage_percent is not None:
|
|
39
|
+
total.context_usage_percent = usage.context_usage_percent
|
|
40
|
+
|
|
41
|
+
return total, task_count
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _format_tokens(tokens: int) -> str:
|
|
45
|
+
"""Format token count with K/M suffix for readability."""
|
|
46
|
+
if tokens >= 1_000_000:
|
|
47
|
+
return f"{tokens / 1_000_000:.2f}M"
|
|
48
|
+
if tokens >= 1_000:
|
|
49
|
+
return f"{tokens / 1_000:.1f}K"
|
|
50
|
+
return str(tokens)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _format_cost(cost: float | None) -> str:
|
|
54
|
+
"""Format cost in USD."""
|
|
55
|
+
if cost is None:
|
|
56
|
+
return "-"
|
|
57
|
+
if cost < 0.01:
|
|
58
|
+
return f"${cost:.4f}"
|
|
59
|
+
return f"${cost:.2f}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def format_status_content(usage: model.Usage) -> str:
|
|
63
|
+
"""Format session status as comma-separated text."""
|
|
64
|
+
parts: list[str] = []
|
|
65
|
+
|
|
66
|
+
parts.append(f"Input: {_format_tokens(usage.input_tokens)}")
|
|
67
|
+
if usage.cached_tokens > 0:
|
|
68
|
+
parts.append(f"Cached: {_format_tokens(usage.cached_tokens)}")
|
|
69
|
+
parts.append(f"Output: {_format_tokens(usage.output_tokens)}")
|
|
70
|
+
parts.append(f"Total: {_format_tokens(usage.total_tokens)}")
|
|
71
|
+
|
|
72
|
+
if usage.total_cost is not None:
|
|
73
|
+
parts.append(f"Cost: {_format_cost(usage.total_cost)}")
|
|
74
|
+
|
|
75
|
+
return ", ".join(parts)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@register_command
|
|
79
|
+
class StatusCommand(CommandABC):
|
|
80
|
+
"""Display session usage statistics."""
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def name(self) -> commands.CommandName:
|
|
84
|
+
return commands.CommandName.STATUS
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def summary(self) -> str:
|
|
88
|
+
return "Show session usage statistics"
|
|
89
|
+
|
|
90
|
+
async def run(self, raw: str, agent: Agent) -> CommandResult:
|
|
91
|
+
session = agent.session
|
|
92
|
+
usage, task_count = accumulate_session_usage(session)
|
|
93
|
+
|
|
94
|
+
event = events.DeveloperMessageEvent(
|
|
95
|
+
session_id=session.id,
|
|
96
|
+
item=model.DeveloperMessageItem(
|
|
97
|
+
content=format_status_content(usage),
|
|
98
|
+
command_output=model.CommandOutput(
|
|
99
|
+
command_name=self.name,
|
|
100
|
+
ui_extra=model.ToolResultUIExtra(
|
|
101
|
+
type=model.ToolResultUIExtraType.SESSION_STATUS,
|
|
102
|
+
session_status=model.SessionStatusUIExtra(
|
|
103
|
+
usage=usage,
|
|
104
|
+
task_count=task_count,
|
|
105
|
+
),
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return CommandResult(events=[event])
|
klaude_code/config/config.py
CHANGED
|
@@ -6,8 +6,8 @@ from typing import Any, cast
|
|
|
6
6
|
import yaml
|
|
7
7
|
from pydantic import BaseModel, Field, ValidationError, model_validator
|
|
8
8
|
|
|
9
|
-
from klaude_code.core.sub_agent import iter_sub_agent_profiles
|
|
10
9
|
from klaude_code.protocol import llm_param
|
|
10
|
+
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
11
11
|
from klaude_code.trace import log
|
|
12
12
|
|
|
13
13
|
config_path = Path.home() / ".klaude" / "klaude-config.yaml"
|
klaude_code/config/list_model.py
CHANGED
|
@@ -4,7 +4,7 @@ from rich.table import Table
|
|
|
4
4
|
from rich.text import Text
|
|
5
5
|
|
|
6
6
|
from klaude_code.config import Config
|
|
7
|
-
from klaude_code.
|
|
7
|
+
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
8
8
|
from klaude_code.ui.rich.theme import ThemeKey, get_theme
|
|
9
9
|
|
|
10
10
|
|
klaude_code/const/__init__.py
CHANGED
|
@@ -109,7 +109,7 @@ STATUS_SHIMMER_PADDING = 10
|
|
|
109
109
|
# Duration in seconds for one full shimmer sweep across the text
|
|
110
110
|
STATUS_SHIMMER_SWEEP_SECONDS = 2
|
|
111
111
|
# Half-width of the shimmer band in characters
|
|
112
|
-
STATUS_SHIMMER_BAND_HALF_WIDTH =
|
|
112
|
+
STATUS_SHIMMER_BAND_HALF_WIDTH = 5.0
|
|
113
113
|
# Scale factor applied to shimmer intensity when blending colors
|
|
114
114
|
STATUS_SHIMMER_ALPHA_SCALE = 0.7
|
|
115
115
|
|
klaude_code/core/agent.py
CHANGED
|
@@ -74,11 +74,8 @@ class Agent:
|
|
|
74
74
|
self,
|
|
75
75
|
session: Session,
|
|
76
76
|
profile: AgentProfile,
|
|
77
|
-
*,
|
|
78
|
-
model_profile_provider: ModelProfileProvider | None = None,
|
|
79
77
|
):
|
|
80
78
|
self.session: Session = session
|
|
81
|
-
self.model_profile_provider: ModelProfileProvider = model_profile_provider or DefaultModelProfileProvider()
|
|
82
79
|
self.profile: AgentProfile | None = None
|
|
83
80
|
# Active task executor, if any
|
|
84
81
|
self._current_task: TaskExecutor | None = None
|
|
@@ -156,14 +153,8 @@ class Agent:
|
|
|
156
153
|
"""Apply a fully constructed profile to the agent."""
|
|
157
154
|
|
|
158
155
|
self.profile = profile
|
|
159
|
-
self.session.model_name
|
|
160
|
-
|
|
161
|
-
def build_model_profile(
|
|
162
|
-
self,
|
|
163
|
-
llm_client: LLMClientABC,
|
|
164
|
-
sub_agent_type: tools.SubAgentType | None = None,
|
|
165
|
-
) -> AgentProfile:
|
|
166
|
-
return self.model_profile_provider.build_profile(llm_client, sub_agent_type)
|
|
156
|
+
if not self.session.model_name:
|
|
157
|
+
self.session.model_name = profile.llm_client.model_name
|
|
167
158
|
|
|
168
159
|
def get_llm_client(self) -> LLMClientABC:
|
|
169
160
|
return self._require_profile().llm_client
|