klaude-code 1.2.6__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- klaude_code/auth/__init__.py +24 -0
- klaude_code/auth/codex/__init__.py +20 -0
- klaude_code/auth/codex/exceptions.py +17 -0
- klaude_code/auth/codex/jwt_utils.py +45 -0
- klaude_code/auth/codex/oauth.py +229 -0
- klaude_code/auth/codex/token_manager.py +84 -0
- klaude_code/cli/auth_cmd.py +73 -0
- klaude_code/cli/config_cmd.py +91 -0
- klaude_code/cli/cost_cmd.py +338 -0
- klaude_code/cli/debug.py +78 -0
- klaude_code/cli/list_model.py +307 -0
- klaude_code/cli/main.py +233 -134
- klaude_code/cli/runtime.py +309 -117
- klaude_code/{version.py → cli/self_update.py} +114 -5
- klaude_code/cli/session_cmd.py +37 -21
- klaude_code/command/__init__.py +88 -27
- klaude_code/command/clear_cmd.py +8 -7
- klaude_code/command/command_abc.py +31 -31
- klaude_code/command/debug_cmd.py +79 -0
- klaude_code/command/export_cmd.py +19 -53
- klaude_code/command/export_online_cmd.py +154 -0
- klaude_code/command/fork_session_cmd.py +267 -0
- klaude_code/command/help_cmd.py +7 -8
- klaude_code/command/model_cmd.py +60 -10
- klaude_code/command/model_select.py +84 -0
- klaude_code/command/prompt-jj-describe.md +32 -0
- klaude_code/command/prompt_command.py +19 -11
- klaude_code/command/refresh_cmd.py +8 -10
- klaude_code/command/registry.py +139 -40
- klaude_code/command/release_notes_cmd.py +84 -0
- klaude_code/command/resume_cmd.py +111 -0
- klaude_code/command/status_cmd.py +104 -60
- klaude_code/command/terminal_setup_cmd.py +7 -9
- klaude_code/command/thinking_cmd.py +98 -0
- klaude_code/config/__init__.py +14 -6
- klaude_code/config/assets/__init__.py +1 -0
- klaude_code/config/assets/builtin_config.yaml +303 -0
- klaude_code/config/builtin_config.py +38 -0
- klaude_code/config/config.py +378 -109
- klaude_code/config/select_model.py +117 -53
- klaude_code/config/thinking.py +269 -0
- klaude_code/{const/__init__.py → const.py} +50 -19
- klaude_code/core/agent.py +20 -28
- klaude_code/core/executor.py +327 -112
- klaude_code/core/manager/__init__.py +2 -4
- klaude_code/core/manager/llm_clients.py +1 -15
- klaude_code/core/manager/llm_clients_builder.py +10 -11
- klaude_code/core/manager/sub_agent_manager.py +37 -6
- klaude_code/core/prompt.py +63 -44
- klaude_code/core/prompts/prompt-claude-code.md +2 -13
- klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
- klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
- klaude_code/core/prompts/prompt-codex.md +9 -42
- klaude_code/core/prompts/prompt-minimal.md +12 -0
- klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
- klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
- klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
- klaude_code/core/reminders.py +283 -95
- klaude_code/core/task.py +113 -75
- klaude_code/core/tool/__init__.py +24 -31
- klaude_code/core/tool/file/_utils.py +36 -0
- klaude_code/core/tool/file/apply_patch.py +17 -25
- klaude_code/core/tool/file/apply_patch_tool.py +57 -77
- klaude_code/core/tool/file/diff_builder.py +151 -0
- klaude_code/core/tool/file/edit_tool.py +50 -63
- klaude_code/core/tool/file/move_tool.md +41 -0
- klaude_code/core/tool/file/move_tool.py +435 -0
- klaude_code/core/tool/file/read_tool.md +1 -1
- klaude_code/core/tool/file/read_tool.py +86 -86
- klaude_code/core/tool/file/write_tool.py +59 -69
- klaude_code/core/tool/report_back_tool.py +84 -0
- klaude_code/core/tool/shell/bash_tool.py +265 -22
- klaude_code/core/tool/shell/command_safety.py +3 -6
- klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
- klaude_code/core/tool/sub_agent_tool.py +13 -2
- klaude_code/core/tool/todo/todo_write_tool.md +0 -157
- klaude_code/core/tool/todo/todo_write_tool.py +1 -1
- klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
- klaude_code/core/tool/todo/update_plan_tool.py +1 -1
- klaude_code/core/tool/tool_abc.py +18 -0
- klaude_code/core/tool/tool_context.py +27 -12
- klaude_code/core/tool/tool_registry.py +7 -7
- klaude_code/core/tool/tool_runner.py +44 -36
- klaude_code/core/tool/truncation.py +29 -14
- klaude_code/core/tool/web/mermaid_tool.md +43 -0
- klaude_code/core/tool/web/mermaid_tool.py +2 -5
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +112 -22
- klaude_code/core/tool/web/web_search_tool.md +23 -0
- klaude_code/core/tool/web/web_search_tool.py +130 -0
- klaude_code/core/turn.py +168 -66
- klaude_code/llm/__init__.py +2 -10
- klaude_code/llm/anthropic/client.py +190 -178
- klaude_code/llm/anthropic/input.py +39 -15
- klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code/llm/bedrock/client.py +60 -0
- klaude_code/llm/client.py +7 -21
- klaude_code/llm/codex/__init__.py +5 -0
- klaude_code/llm/codex/client.py +149 -0
- klaude_code/llm/google/__init__.py +3 -0
- klaude_code/llm/google/client.py +309 -0
- klaude_code/llm/google/input.py +215 -0
- klaude_code/llm/input_common.py +3 -9
- klaude_code/llm/openai_compatible/client.py +72 -164
- klaude_code/llm/openai_compatible/input.py +6 -4
- klaude_code/llm/openai_compatible/stream.py +273 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- klaude_code/llm/openrouter/client.py +89 -160
- klaude_code/llm/openrouter/input.py +18 -30
- klaude_code/llm/openrouter/reasoning.py +118 -0
- klaude_code/llm/registry.py +39 -7
- klaude_code/llm/responses/client.py +184 -171
- klaude_code/llm/responses/input.py +20 -1
- klaude_code/llm/usage.py +17 -12
- klaude_code/protocol/commands.py +17 -1
- klaude_code/protocol/events.py +31 -4
- klaude_code/protocol/llm_param.py +13 -10
- klaude_code/protocol/model.py +232 -29
- klaude_code/protocol/op.py +90 -1
- klaude_code/protocol/op_handler.py +35 -1
- klaude_code/protocol/sub_agent/__init__.py +117 -0
- klaude_code/protocol/sub_agent/explore.py +63 -0
- klaude_code/protocol/sub_agent/oracle.py +91 -0
- klaude_code/protocol/sub_agent/task.py +61 -0
- klaude_code/protocol/sub_agent/web.py +79 -0
- klaude_code/protocol/tools.py +4 -2
- klaude_code/session/__init__.py +2 -2
- klaude_code/session/codec.py +71 -0
- klaude_code/session/export.py +293 -86
- klaude_code/session/selector.py +89 -67
- klaude_code/session/session.py +320 -309
- klaude_code/session/store.py +220 -0
- klaude_code/session/templates/export_session.html +595 -83
- klaude_code/session/templates/mermaid_viewer.html +926 -0
- klaude_code/skill/__init__.py +27 -0
- klaude_code/skill/assets/deslop/SKILL.md +17 -0
- klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
- klaude_code/skill/assets/handoff/SKILL.md +39 -0
- klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
- klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
- klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
- klaude_code/skill/manager.py +70 -0
- klaude_code/skill/system_skills.py +192 -0
- klaude_code/trace/__init__.py +20 -2
- klaude_code/trace/log.py +150 -5
- klaude_code/ui/__init__.py +4 -9
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +7 -7
- klaude_code/ui/modes/debug/display.py +2 -1
- klaude_code/ui/modes/repl/__init__.py +3 -48
- klaude_code/ui/modes/repl/clipboard.py +5 -5
- klaude_code/ui/modes/repl/completers.py +487 -123
- klaude_code/ui/modes/repl/display.py +5 -4
- klaude_code/ui/modes/repl/event_handler.py +370 -117
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
- klaude_code/ui/modes/repl/key_bindings.py +146 -23
- klaude_code/ui/modes/repl/renderer.py +189 -99
- klaude_code/ui/renderers/assistant.py +9 -2
- klaude_code/ui/renderers/bash_syntax.py +178 -0
- klaude_code/ui/renderers/common.py +78 -0
- klaude_code/ui/renderers/developer.py +104 -48
- klaude_code/ui/renderers/diffs.py +87 -6
- klaude_code/ui/renderers/errors.py +11 -6
- klaude_code/ui/renderers/mermaid_viewer.py +57 -0
- klaude_code/ui/renderers/metadata.py +112 -76
- klaude_code/ui/renderers/sub_agent.py +92 -7
- klaude_code/ui/renderers/thinking.py +40 -18
- klaude_code/ui/renderers/tools.py +405 -227
- klaude_code/ui/renderers/user_input.py +73 -13
- klaude_code/ui/rich/__init__.py +10 -1
- klaude_code/ui/rich/cjk_wrap.py +228 -0
- klaude_code/ui/rich/code_panel.py +131 -0
- klaude_code/ui/rich/live.py +17 -0
- klaude_code/ui/rich/markdown.py +305 -170
- klaude_code/ui/rich/searchable_text.py +10 -13
- klaude_code/ui/rich/status.py +190 -49
- klaude_code/ui/rich/theme.py +135 -39
- klaude_code/ui/terminal/__init__.py +55 -0
- klaude_code/ui/terminal/color.py +1 -1
- klaude_code/ui/terminal/control.py +13 -22
- klaude_code/ui/terminal/notifier.py +44 -4
- klaude_code/ui/terminal/selector.py +658 -0
- klaude_code/ui/utils/common.py +0 -18
- klaude_code-1.8.0.dist-info/METADATA +377 -0
- klaude_code-1.8.0.dist-info/RECORD +219 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
- klaude_code/command/diff_cmd.py +0 -138
- klaude_code/command/prompt-dev-docs-update.md +0 -56
- klaude_code/command/prompt-dev-docs.md +0 -46
- klaude_code/config/list_model.py +0 -162
- klaude_code/core/manager/agent_manager.py +0 -127
- klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
- klaude_code/core/tool/file/multi_edit_tool.md +0 -42
- klaude_code/core/tool/file/multi_edit_tool.py +0 -199
- klaude_code/core/tool/memory/memory_tool.md +0 -16
- klaude_code/core/tool/memory/memory_tool.py +0 -462
- klaude_code/llm/openrouter/reasoning_handler.py +0 -209
- klaude_code/protocol/sub_agent.py +0 -348
- klaude_code/ui/utils/debouncer.py +0 -42
- klaude_code-1.2.6.dist-info/METADATA +0 -178
- klaude_code-1.2.6.dist-info/RECORD +0 -167
- /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
- /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
- /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
|
@@ -12,6 +12,9 @@ class LLMClientProtocol(Enum):
|
|
|
12
12
|
RESPONSES = "responses"
|
|
13
13
|
OPENROUTER = "openrouter"
|
|
14
14
|
ANTHROPIC = "anthropic"
|
|
15
|
+
BEDROCK = "bedrock"
|
|
16
|
+
CODEX = "codex"
|
|
17
|
+
GOOGLE = "google"
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class ToolSchema(BaseModel):
|
|
@@ -27,7 +30,7 @@ class Thinking(BaseModel):
|
|
|
27
30
|
"""
|
|
28
31
|
|
|
29
32
|
# OpenAI Reasoning Style
|
|
30
|
-
reasoning_effort: Literal["high", "medium", "low", "minimal", "none"] | None = None
|
|
33
|
+
reasoning_effort: Literal["high", "medium", "low", "minimal", "none", "xhigh"] | None = None
|
|
31
34
|
reasoning_summary: Literal["auto", "concise", "detailed"] | None = None
|
|
32
35
|
|
|
33
36
|
# Claude/Gemini Thinking Style
|
|
@@ -36,12 +39,13 @@ class Thinking(BaseModel):
|
|
|
36
39
|
|
|
37
40
|
|
|
38
41
|
class Cost(BaseModel):
|
|
39
|
-
"""Cost configuration per million tokens
|
|
42
|
+
"""Cost configuration per million tokens."""
|
|
40
43
|
|
|
41
44
|
input: float # Input token price per million tokens
|
|
42
45
|
output: float # Output token price per million tokens
|
|
43
46
|
cache_read: float = 0.0 # Cache read price per million tokens
|
|
44
47
|
cache_write: float = 0.0 # Cache write price per million tokens (ignored in calculation for now)
|
|
48
|
+
currency: Literal["USD", "CNY"] = "USD" # Currency for cost display
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
class OpenRouterProviderRouting(BaseModel):
|
|
@@ -89,8 +93,15 @@ class LLMConfigProviderParameter(BaseModel):
|
|
|
89
93
|
protocol: LLMClientProtocol
|
|
90
94
|
base_url: str | None = None
|
|
91
95
|
api_key: str | None = None
|
|
96
|
+
# Azure OpenAI
|
|
92
97
|
is_azure: bool = False
|
|
93
98
|
azure_api_version: str | None = None
|
|
99
|
+
# AWS Bedrock configuration
|
|
100
|
+
aws_access_key: str | None = None
|
|
101
|
+
aws_secret_key: str | None = None
|
|
102
|
+
aws_region: str | None = None
|
|
103
|
+
aws_session_token: str | None = None
|
|
104
|
+
aws_profile: str | None = None
|
|
94
105
|
|
|
95
106
|
|
|
96
107
|
class LLMConfigModelParameter(BaseModel):
|
|
@@ -136,12 +147,4 @@ class LLMCallParameter(LLMConfigModelParameter):
|
|
|
136
147
|
input: list[ConversationItem]
|
|
137
148
|
system: str | None = None
|
|
138
149
|
tools: list[ToolSchema] | None = None
|
|
139
|
-
|
|
140
|
-
stream: Literal[True] = True # Always True
|
|
141
|
-
|
|
142
|
-
# OpenAI Responses
|
|
143
|
-
include: list[str] | None = None
|
|
144
|
-
store: bool = True
|
|
145
|
-
previous_response_id: str | None = None
|
|
146
|
-
|
|
147
150
|
session_id: str | None = None
|
klaude_code/protocol/model.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Literal
|
|
3
|
+
from typing import Annotated, Any, Literal
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel, Field
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, computed_field
|
|
6
6
|
|
|
7
|
+
from klaude_code import const
|
|
7
8
|
from klaude_code.protocol.commands import CommandName
|
|
8
9
|
from klaude_code.protocol.tools import SubAgentType
|
|
9
10
|
|
|
@@ -12,26 +13,73 @@ TodoStatusType = Literal["pending", "in_progress", "completed"]
|
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class Usage(BaseModel):
|
|
16
|
+
# Token Usage (primary state)
|
|
15
17
|
input_tokens: int = 0
|
|
16
18
|
cached_tokens: int = 0
|
|
17
19
|
reasoning_tokens: int = 0
|
|
18
20
|
output_tokens: int = 0
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
|
|
22
|
+
# Context window tracking
|
|
23
|
+
context_size: int | None = None # Peak total_tokens seen (for context usage display)
|
|
24
|
+
context_limit: int | None = None # Model's context limit
|
|
25
|
+
max_tokens: int | None = None # Max output tokens for this request
|
|
26
|
+
|
|
21
27
|
throughput_tps: float | None = None
|
|
22
28
|
first_token_latency_ms: float | None = None
|
|
23
29
|
|
|
24
|
-
# Cost
|
|
30
|
+
# Cost (calculated from token counts and cost config)
|
|
25
31
|
input_cost: float | None = None # Cost for non-cached input tokens
|
|
26
32
|
output_cost: float | None = None # Cost for output tokens (including reasoning)
|
|
27
33
|
cache_read_cost: float | None = None # Cost for cached tokens
|
|
28
|
-
|
|
34
|
+
currency: str = "USD" # Currency for cost display (USD or CNY)
|
|
35
|
+
|
|
36
|
+
@computed_field
|
|
37
|
+
@property
|
|
38
|
+
def total_tokens(self) -> int:
|
|
39
|
+
"""Total tokens computed from input + output tokens."""
|
|
40
|
+
return self.input_tokens + self.output_tokens
|
|
41
|
+
|
|
42
|
+
@computed_field
|
|
43
|
+
@property
|
|
44
|
+
def total_cost(self) -> float | None:
|
|
45
|
+
"""Total cost computed from input + output + cache_read costs."""
|
|
46
|
+
costs = [self.input_cost, self.output_cost, self.cache_read_cost]
|
|
47
|
+
non_none = [c for c in costs if c is not None]
|
|
48
|
+
return sum(non_none) if non_none else None
|
|
49
|
+
|
|
50
|
+
@computed_field
|
|
51
|
+
@property
|
|
52
|
+
def context_usage_percent(self) -> float | None:
|
|
53
|
+
"""Context usage percentage computed from context_token / (context_limit - max_tokens)."""
|
|
54
|
+
if self.context_limit is None or self.context_limit <= 0:
|
|
55
|
+
return None
|
|
56
|
+
if self.context_size is None:
|
|
57
|
+
return None
|
|
58
|
+
effective_limit = self.context_limit - (self.max_tokens or const.DEFAULT_MAX_TOKENS)
|
|
59
|
+
if effective_limit <= 0:
|
|
60
|
+
return None
|
|
61
|
+
return (self.context_size / effective_limit) * 100
|
|
29
62
|
|
|
30
63
|
|
|
31
64
|
class TodoItem(BaseModel):
|
|
65
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
66
|
+
|
|
32
67
|
content: str
|
|
33
68
|
status: TodoStatusType
|
|
34
|
-
|
|
69
|
+
active_form: str = Field(default="", alias="activeForm")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class FileStatus(BaseModel):
|
|
73
|
+
"""Tracks file state including modification time and content hash.
|
|
74
|
+
|
|
75
|
+
Notes:
|
|
76
|
+
- `mtime` is a cheap heuristic and may miss changes on some filesystems.
|
|
77
|
+
- `content_sha256` provides an explicit content-based change detector.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
mtime: float
|
|
81
|
+
content_sha256: str | None = None
|
|
82
|
+
is_memory: bool = False
|
|
35
83
|
|
|
36
84
|
|
|
37
85
|
class TodoUIExtra(BaseModel):
|
|
@@ -39,43 +87,104 @@ class TodoUIExtra(BaseModel):
|
|
|
39
87
|
new_completed: list[str]
|
|
40
88
|
|
|
41
89
|
|
|
42
|
-
class ToolResultUIExtraType(str, Enum):
|
|
43
|
-
DIFF_TEXT = "diff_text"
|
|
44
|
-
TODO_LIST = "todo_list"
|
|
45
|
-
SESSION_ID = "session_id"
|
|
46
|
-
MERMAID_LINK = "mermaid_link"
|
|
47
|
-
TRUNCATION = "truncation"
|
|
48
|
-
SESSION_STATUS = "session_status"
|
|
49
|
-
|
|
50
|
-
|
|
51
90
|
class ToolSideEffect(str, Enum):
|
|
52
91
|
TODO_CHANGE = "todo_change"
|
|
53
92
|
|
|
54
93
|
|
|
94
|
+
# Discriminated union types for ToolResultUIExtra
|
|
95
|
+
class DiffSpan(BaseModel):
|
|
96
|
+
op: Literal["equal", "insert", "delete"]
|
|
97
|
+
text: str
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class DiffLine(BaseModel):
|
|
101
|
+
kind: Literal["ctx", "add", "remove", "gap"]
|
|
102
|
+
new_line_no: int | None = None
|
|
103
|
+
spans: list[DiffSpan]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class DiffFileDiff(BaseModel):
|
|
107
|
+
file_path: str
|
|
108
|
+
lines: list[DiffLine]
|
|
109
|
+
stats_add: int = 0
|
|
110
|
+
stats_remove: int = 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class DiffUIExtra(BaseModel):
|
|
114
|
+
type: Literal["diff"] = "diff"
|
|
115
|
+
files: list[DiffFileDiff]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TodoListUIExtra(BaseModel):
|
|
119
|
+
type: Literal["todo_list"] = "todo_list"
|
|
120
|
+
todo_list: TodoUIExtra
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class SessionIdUIExtra(BaseModel):
|
|
124
|
+
type: Literal["session_id"] = "session_id"
|
|
125
|
+
session_id: str
|
|
126
|
+
|
|
127
|
+
|
|
55
128
|
class MermaidLinkUIExtra(BaseModel):
|
|
129
|
+
type: Literal["mermaid_link"] = "mermaid_link"
|
|
130
|
+
code: str = ""
|
|
56
131
|
link: str
|
|
57
132
|
line_count: int
|
|
58
133
|
|
|
59
134
|
|
|
60
135
|
class TruncationUIExtra(BaseModel):
|
|
136
|
+
type: Literal["truncation"] = "truncation"
|
|
61
137
|
saved_file_path: str
|
|
62
138
|
original_length: int
|
|
63
139
|
truncated_length: int
|
|
64
140
|
|
|
65
141
|
|
|
142
|
+
class MarkdownDocUIExtra(BaseModel):
|
|
143
|
+
type: Literal["markdown_doc"] = "markdown_doc"
|
|
144
|
+
file_path: str
|
|
145
|
+
content: str
|
|
146
|
+
|
|
147
|
+
|
|
66
148
|
class SessionStatusUIExtra(BaseModel):
|
|
149
|
+
type: Literal["session_status"] = "session_status"
|
|
67
150
|
usage: "Usage"
|
|
68
151
|
task_count: int
|
|
152
|
+
by_model: list["TaskMetadata"] = []
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
MultiUIExtraItem = (
|
|
156
|
+
DiffUIExtra
|
|
157
|
+
| TodoListUIExtra
|
|
158
|
+
| SessionIdUIExtra
|
|
159
|
+
| MermaidLinkUIExtra
|
|
160
|
+
| TruncationUIExtra
|
|
161
|
+
| MarkdownDocUIExtra
|
|
162
|
+
| SessionStatusUIExtra
|
|
163
|
+
)
|
|
164
|
+
|
|
69
165
|
|
|
166
|
+
class MultiUIExtra(BaseModel):
|
|
167
|
+
"""A container UIExtra that can render multiple UI blocks for a single tool result.
|
|
168
|
+
|
|
169
|
+
This is primarily used by tools like apply_patch which can perform multiple
|
|
170
|
+
operations in one invocation.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
type: Literal["multi"] = "multi"
|
|
174
|
+
items: list[MultiUIExtraItem]
|
|
70
175
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
176
|
+
|
|
177
|
+
ToolResultUIExtra = Annotated[
|
|
178
|
+
DiffUIExtra
|
|
179
|
+
| TodoListUIExtra
|
|
180
|
+
| SessionIdUIExtra
|
|
181
|
+
| MermaidLinkUIExtra
|
|
182
|
+
| TruncationUIExtra
|
|
183
|
+
| MarkdownDocUIExtra
|
|
184
|
+
| SessionStatusUIExtra
|
|
185
|
+
| MultiUIExtra,
|
|
186
|
+
Field(discriminator="type"),
|
|
187
|
+
]
|
|
79
188
|
|
|
80
189
|
|
|
81
190
|
class AtPatternParseResult(BaseModel):
|
|
@@ -85,6 +194,7 @@ class AtPatternParseResult(BaseModel):
|
|
|
85
194
|
tool_args: str
|
|
86
195
|
operation: Literal["Read", "List"]
|
|
87
196
|
images: list["ImageURLPart"] | None = None
|
|
197
|
+
mentioned_in: str | None = None # Parent file that referenced this file
|
|
88
198
|
|
|
89
199
|
|
|
90
200
|
class CommandOutput(BaseModel):
|
|
@@ -97,6 +207,7 @@ class SubAgentState(BaseModel):
|
|
|
97
207
|
sub_agent_type: SubAgentType
|
|
98
208
|
sub_agent_desc: str
|
|
99
209
|
sub_agent_prompt: str
|
|
210
|
+
output_schema: dict[str, Any] | None = None
|
|
100
211
|
|
|
101
212
|
|
|
102
213
|
"""
|
|
@@ -121,7 +232,7 @@ A conversation history input contains:
|
|
|
121
232
|
- [DeveloperMessageItem]
|
|
122
233
|
|
|
123
234
|
When adding a new item, please also modify the following:
|
|
124
|
-
- session.py
|
|
235
|
+
- session/codec.py (ConversationItem registry derived from ConversationItem union)
|
|
125
236
|
"""
|
|
126
237
|
|
|
127
238
|
|
|
@@ -150,11 +261,13 @@ class DeveloperMessageItem(BaseModel):
|
|
|
150
261
|
|
|
151
262
|
# Special fields for reminders UI
|
|
152
263
|
memory_paths: list[str] | None = None
|
|
264
|
+
memory_mentioned: dict[str, list[str]] | None = None # memory_path -> list of @ patterns mentioned in it
|
|
153
265
|
external_file_changes: list[str] | None = None
|
|
154
266
|
todo_use: bool | None = None
|
|
155
267
|
at_files: list[AtPatternParseResult] | None = None
|
|
156
268
|
command_output: CommandOutput | None = None
|
|
157
269
|
user_image_count: int | None = None
|
|
270
|
+
skill_name: str | None = None # Skill name activated via $skill syntax
|
|
158
271
|
|
|
159
272
|
|
|
160
273
|
class ImageURLPart(BaseModel):
|
|
@@ -239,6 +352,7 @@ class ToolResultItem(BaseModel):
|
|
|
239
352
|
ui_extra: ToolResultUIExtra | None = None # Extra data for UI display, e.g. diff render
|
|
240
353
|
images: list[ImageURLPart] | None = None
|
|
241
354
|
side_effects: list[ToolSideEffect] | None = None
|
|
355
|
+
task_metadata: "TaskMetadata | None" = None # Sub-agent task metadata for propagation to main agent
|
|
242
356
|
created_at: datetime = Field(default_factory=datetime.now)
|
|
243
357
|
|
|
244
358
|
|
|
@@ -248,19 +362,101 @@ class AssistantMessageDelta(BaseModel):
|
|
|
248
362
|
created_at: datetime = Field(default_factory=datetime.now)
|
|
249
363
|
|
|
250
364
|
|
|
365
|
+
class ReasoningTextDelta(BaseModel):
|
|
366
|
+
response_id: str | None = None
|
|
367
|
+
content: str
|
|
368
|
+
created_at: datetime = Field(default_factory=datetime.now)
|
|
369
|
+
|
|
370
|
+
|
|
251
371
|
class StreamErrorItem(BaseModel):
|
|
252
372
|
error: str
|
|
253
373
|
created_at: datetime = Field(default_factory=datetime.now)
|
|
254
374
|
|
|
255
375
|
|
|
256
376
|
class ResponseMetadataItem(BaseModel):
|
|
377
|
+
"""Metadata for a single LLM response (turn-level)."""
|
|
378
|
+
|
|
257
379
|
response_id: str | None = None
|
|
258
380
|
usage: Usage | None = None
|
|
259
381
|
model_name: str = ""
|
|
260
382
|
provider: str | None = None # OpenRouter's provider name
|
|
261
383
|
task_duration_s: float | None = None
|
|
262
|
-
|
|
263
|
-
|
|
384
|
+
created_at: datetime = Field(default_factory=datetime.now)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class TaskMetadata(BaseModel):
|
|
388
|
+
"""Base metadata for a task execution (used by both main and sub-agents)."""
|
|
389
|
+
|
|
390
|
+
usage: Usage | None = None
|
|
391
|
+
model_name: str = ""
|
|
392
|
+
provider: str | None = None
|
|
393
|
+
task_duration_s: float | None = None
|
|
394
|
+
turn_count: int = 0
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def merge_usage(dst: Usage, src: Usage) -> None:
|
|
398
|
+
"""Merge src usage into dst usage (in-place).
|
|
399
|
+
|
|
400
|
+
Accumulates token counts and cost components. Does not handle
|
|
401
|
+
special fields like throughput_tps, first_token_latency_ms,
|
|
402
|
+
context_size, or context_limit - those require custom logic.
|
|
403
|
+
"""
|
|
404
|
+
dst.input_tokens += src.input_tokens
|
|
405
|
+
dst.cached_tokens += src.cached_tokens
|
|
406
|
+
dst.reasoning_tokens += src.reasoning_tokens
|
|
407
|
+
dst.output_tokens += src.output_tokens
|
|
408
|
+
|
|
409
|
+
if src.input_cost is not None:
|
|
410
|
+
dst.input_cost = (dst.input_cost or 0.0) + src.input_cost
|
|
411
|
+
if src.output_cost is not None:
|
|
412
|
+
dst.output_cost = (dst.output_cost or 0.0) + src.output_cost
|
|
413
|
+
if src.cache_read_cost is not None:
|
|
414
|
+
dst.cache_read_cost = (dst.cache_read_cost or 0.0) + src.cache_read_cost
|
|
415
|
+
|
|
416
|
+
@staticmethod
|
|
417
|
+
def aggregate_by_model(metadata_list: list["TaskMetadata"]) -> list["TaskMetadata"]:
|
|
418
|
+
"""Aggregate multiple TaskMetadata by (model_name, provider).
|
|
419
|
+
|
|
420
|
+
Returns a list sorted by total_cost descending.
|
|
421
|
+
|
|
422
|
+
Note: total_tokens and total_cost are now computed fields,
|
|
423
|
+
so we only accumulate the primary state fields here.
|
|
424
|
+
"""
|
|
425
|
+
aggregated: dict[tuple[str, str | None], TaskMetadata] = {}
|
|
426
|
+
|
|
427
|
+
for meta in metadata_list:
|
|
428
|
+
if not meta.usage:
|
|
429
|
+
continue
|
|
430
|
+
|
|
431
|
+
key = (meta.model_name, meta.provider)
|
|
432
|
+
usage = meta.usage
|
|
433
|
+
|
|
434
|
+
if key not in aggregated:
|
|
435
|
+
aggregated[key] = TaskMetadata(
|
|
436
|
+
model_name=meta.model_name,
|
|
437
|
+
provider=meta.provider,
|
|
438
|
+
usage=Usage(currency=usage.currency),
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
agg = aggregated[key]
|
|
442
|
+
if agg.usage is None:
|
|
443
|
+
continue
|
|
444
|
+
|
|
445
|
+
TaskMetadata.merge_usage(agg.usage, usage)
|
|
446
|
+
|
|
447
|
+
# Sort by total_cost descending
|
|
448
|
+
return sorted(
|
|
449
|
+
aggregated.values(),
|
|
450
|
+
key=lambda m: m.usage.total_cost if m.usage and m.usage.total_cost else 0.0,
|
|
451
|
+
reverse=True,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class TaskMetadataItem(BaseModel):
|
|
456
|
+
"""Aggregated metadata for a complete task, stored in conversation history."""
|
|
457
|
+
|
|
458
|
+
main_agent: TaskMetadata = Field(default_factory=TaskMetadata) # Main agent metadata
|
|
459
|
+
sub_agent_task_metadata: list[TaskMetadata] = Field(default_factory=lambda: list[TaskMetadata]())
|
|
264
460
|
created_at: datetime = Field(default_factory=datetime.now)
|
|
265
461
|
|
|
266
462
|
|
|
@@ -276,10 +472,17 @@ MessageItem = (
|
|
|
276
472
|
)
|
|
277
473
|
|
|
278
474
|
|
|
279
|
-
StreamItem = AssistantMessageDelta
|
|
475
|
+
StreamItem = AssistantMessageDelta | ReasoningTextDelta
|
|
280
476
|
|
|
281
477
|
ConversationItem = (
|
|
282
|
-
StartItem
|
|
478
|
+
StartItem
|
|
479
|
+
| InterruptItem
|
|
480
|
+
| StreamErrorItem
|
|
481
|
+
| StreamItem
|
|
482
|
+
| MessageItem
|
|
483
|
+
| ResponseMetadataItem
|
|
484
|
+
| TaskMetadataItem
|
|
485
|
+
| ToolCallStartItem
|
|
283
486
|
)
|
|
284
487
|
|
|
285
488
|
|
klaude_code/protocol/op.py
CHANGED
|
@@ -13,6 +13,7 @@ from uuid import uuid4
|
|
|
13
13
|
|
|
14
14
|
from pydantic import BaseModel, Field
|
|
15
15
|
|
|
16
|
+
from klaude_code.protocol.llm_param import Thinking
|
|
16
17
|
from klaude_code.protocol.model import UserInputPayload
|
|
17
18
|
|
|
18
19
|
if TYPE_CHECKING:
|
|
@@ -23,6 +24,12 @@ class OperationType(Enum):
|
|
|
23
24
|
"""Enumeration of supported operation types."""
|
|
24
25
|
|
|
25
26
|
USER_INPUT = "user_input"
|
|
27
|
+
RUN_AGENT = "run_agent"
|
|
28
|
+
CHANGE_MODEL = "change_model"
|
|
29
|
+
CHANGE_THINKING = "change_thinking"
|
|
30
|
+
CLEAR_SESSION = "clear_session"
|
|
31
|
+
RESUME_SESSION = "resume_session"
|
|
32
|
+
EXPORT_SESSION = "export_session"
|
|
26
33
|
INTERRUPT = "interrupt"
|
|
27
34
|
INIT_AGENT = "init_agent"
|
|
28
35
|
END = "end"
|
|
@@ -51,6 +58,84 @@ class UserInputOperation(Operation):
|
|
|
51
58
|
await handler.handle_user_input(self)
|
|
52
59
|
|
|
53
60
|
|
|
61
|
+
class RunAgentOperation(Operation):
|
|
62
|
+
"""Operation for launching an agent task for a given session."""
|
|
63
|
+
|
|
64
|
+
type: OperationType = OperationType.RUN_AGENT
|
|
65
|
+
session_id: str
|
|
66
|
+
input: UserInputPayload
|
|
67
|
+
|
|
68
|
+
async def execute(self, handler: OperationHandler) -> None:
|
|
69
|
+
await handler.handle_run_agent(self)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ChangeModelOperation(Operation):
|
|
73
|
+
"""Operation for changing the model used by the active agent session."""
|
|
74
|
+
|
|
75
|
+
type: OperationType = OperationType.CHANGE_MODEL
|
|
76
|
+
session_id: str
|
|
77
|
+
model_name: str
|
|
78
|
+
save_as_default: bool = False
|
|
79
|
+
# When True, the executor must not auto-trigger an interactive thinking selector.
|
|
80
|
+
# This is required for in-prompt model switching where the terminal is already
|
|
81
|
+
# controlled by a prompt_toolkit PromptSession.
|
|
82
|
+
defer_thinking_selection: bool = False
|
|
83
|
+
# When False, do not emit WelcomeEvent (which renders a banner/panel).
|
|
84
|
+
# This is useful for in-prompt model switching where extra output is noisy.
|
|
85
|
+
emit_welcome_event: bool = True
|
|
86
|
+
|
|
87
|
+
# When False, do not emit the "Switched to: ..." developer message.
|
|
88
|
+
# This is useful for in-prompt model switching where extra output is noisy.
|
|
89
|
+
emit_switch_message: bool = True
|
|
90
|
+
|
|
91
|
+
async def execute(self, handler: OperationHandler) -> None:
|
|
92
|
+
await handler.handle_change_model(self)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ChangeThinkingOperation(Operation):
|
|
96
|
+
"""Operation for changing the thinking/reasoning configuration."""
|
|
97
|
+
|
|
98
|
+
type: OperationType = OperationType.CHANGE_THINKING
|
|
99
|
+
session_id: str
|
|
100
|
+
thinking: Thinking | None = None
|
|
101
|
+
emit_welcome_event: bool = True
|
|
102
|
+
emit_switch_message: bool = True
|
|
103
|
+
|
|
104
|
+
async def execute(self, handler: OperationHandler) -> None:
|
|
105
|
+
await handler.handle_change_thinking(self)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ClearSessionOperation(Operation):
|
|
109
|
+
"""Operation for clearing the active session and starting a new one."""
|
|
110
|
+
|
|
111
|
+
type: OperationType = OperationType.CLEAR_SESSION
|
|
112
|
+
session_id: str
|
|
113
|
+
|
|
114
|
+
async def execute(self, handler: OperationHandler) -> None:
|
|
115
|
+
await handler.handle_clear_session(self)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class ResumeSessionOperation(Operation):
|
|
119
|
+
"""Operation for resuming a different session."""
|
|
120
|
+
|
|
121
|
+
type: OperationType = OperationType.RESUME_SESSION
|
|
122
|
+
target_session_id: str
|
|
123
|
+
|
|
124
|
+
async def execute(self, handler: OperationHandler) -> None:
|
|
125
|
+
await handler.handle_resume_session(self)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ExportSessionOperation(Operation):
|
|
129
|
+
"""Operation for exporting a session transcript to HTML."""
|
|
130
|
+
|
|
131
|
+
type: OperationType = OperationType.EXPORT_SESSION
|
|
132
|
+
session_id: str
|
|
133
|
+
output_path: str | None = None
|
|
134
|
+
|
|
135
|
+
async def execute(self, handler: OperationHandler) -> None:
|
|
136
|
+
await handler.handle_export_session(self)
|
|
137
|
+
|
|
138
|
+
|
|
54
139
|
class InterruptOperation(Operation):
|
|
55
140
|
"""Operation for interrupting currently running tasks."""
|
|
56
141
|
|
|
@@ -63,7 +148,11 @@ class InterruptOperation(Operation):
|
|
|
63
148
|
|
|
64
149
|
|
|
65
150
|
class InitAgentOperation(Operation):
|
|
66
|
-
"""Operation for initializing an agent and replaying history if any.
|
|
151
|
+
"""Operation for initializing an agent and replaying history if any.
|
|
152
|
+
|
|
153
|
+
If session_id is None, a new session is created with an auto-generated ID.
|
|
154
|
+
If session_id is provided, attempts to load existing session or creates new one.
|
|
155
|
+
"""
|
|
67
156
|
|
|
68
157
|
type: OperationType = OperationType.INIT_AGENT
|
|
69
158
|
session_id: str | None = None
|
|
@@ -9,7 +9,17 @@ from __future__ import annotations
|
|
|
9
9
|
from typing import TYPE_CHECKING, Protocol
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
-
from klaude_code.protocol.op import
|
|
12
|
+
from klaude_code.protocol.op import (
|
|
13
|
+
ChangeModelOperation,
|
|
14
|
+
ChangeThinkingOperation,
|
|
15
|
+
ClearSessionOperation,
|
|
16
|
+
ExportSessionOperation,
|
|
17
|
+
InitAgentOperation,
|
|
18
|
+
InterruptOperation,
|
|
19
|
+
ResumeSessionOperation,
|
|
20
|
+
RunAgentOperation,
|
|
21
|
+
UserInputOperation,
|
|
22
|
+
)
|
|
13
23
|
|
|
14
24
|
|
|
15
25
|
class OperationHandler(Protocol):
|
|
@@ -19,6 +29,30 @@ class OperationHandler(Protocol):
|
|
|
19
29
|
"""Handle a user input operation."""
|
|
20
30
|
...
|
|
21
31
|
|
|
32
|
+
async def handle_run_agent(self, operation: RunAgentOperation) -> None:
|
|
33
|
+
"""Handle a run agent operation."""
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
async def handle_change_model(self, operation: ChangeModelOperation) -> None:
|
|
37
|
+
"""Handle a change model operation."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
async def handle_change_thinking(self, operation: ChangeThinkingOperation) -> None:
|
|
41
|
+
"""Handle a change thinking operation."""
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
async def handle_clear_session(self, operation: ClearSessionOperation) -> None:
|
|
45
|
+
"""Handle a clear session operation."""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
async def handle_resume_session(self, operation: ResumeSessionOperation) -> None:
|
|
49
|
+
"""Handle a resume session operation."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
async def handle_export_session(self, operation: ExportSessionOperation) -> None:
|
|
53
|
+
"""Handle an export session operation."""
|
|
54
|
+
...
|
|
55
|
+
|
|
22
56
|
async def handle_interrupt(self, operation: InterruptOperation) -> None:
|
|
23
57
|
"""Handle an interrupt operation."""
|
|
24
58
|
...
|