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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import html
|
|
4
|
+
import importlib.resources
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from klaude_code import const
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def artifacts_dir() -> Path:
|
|
12
|
+
return Path(const.TOOL_OUTPUT_TRUNCATION_DIR) / "mermaid"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@lru_cache(maxsize=1)
|
|
16
|
+
def load_template() -> str:
|
|
17
|
+
template_file = importlib.resources.files("klaude_code.session.templates").joinpath("mermaid_viewer.html")
|
|
18
|
+
return template_file.read_text(encoding="utf-8")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ensure_viewer_file(*, code: str, link: str, tool_call_id: str) -> Path | None:
|
|
22
|
+
"""Create a local HTML viewer with large preview + editor."""
|
|
23
|
+
|
|
24
|
+
if not tool_call_id:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
safe_id = tool_call_id.replace("/", "_")
|
|
28
|
+
path = artifacts_dir() / f"mermaid-viewer-{safe_id}.html"
|
|
29
|
+
if path.exists():
|
|
30
|
+
return path
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
|
|
35
|
+
escaped_code = html.escape(code)
|
|
36
|
+
escaped_view_link = html.escape(link, quote=True)
|
|
37
|
+
escaped_edit_link = html.escape(link.replace("/view#pako:", "/edit#pako:"), quote=True)
|
|
38
|
+
|
|
39
|
+
template = load_template()
|
|
40
|
+
content = (
|
|
41
|
+
template.replace("__KLAUDE_VIEW_LINK__", escaped_view_link)
|
|
42
|
+
.replace("__KLAUDE_EDIT_LINK__", escaped_edit_link)
|
|
43
|
+
.replace("__KLAUDE_CODE__", escaped_code)
|
|
44
|
+
)
|
|
45
|
+
path.write_text(content, encoding="utf-8")
|
|
46
|
+
except OSError:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
return path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_viewer(*, code: str, link: str, tool_call_id: str) -> Path | None:
|
|
53
|
+
"""Create a local Mermaid viewer HTML file."""
|
|
54
|
+
|
|
55
|
+
if not code:
|
|
56
|
+
return None
|
|
57
|
+
return ensure_viewer_file(code=code, link=link, tool_call_id=tool_call_id)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
from importlib.metadata import version
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
2
|
|
|
3
3
|
from rich import box
|
|
4
|
-
from rich.box import Box
|
|
5
4
|
from rich.console import Group, RenderableType
|
|
6
5
|
from rich.padding import Padding
|
|
7
6
|
from rich.panel import Panel
|
|
8
7
|
from rich.text import Text
|
|
9
8
|
|
|
10
|
-
from klaude_code
|
|
9
|
+
from klaude_code import const
|
|
10
|
+
from klaude_code.protocol import events, model
|
|
11
11
|
from klaude_code.trace import is_debug_enabled
|
|
12
|
+
from klaude_code.ui.renderers.common import create_grid
|
|
12
13
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
13
14
|
from klaude_code.ui.utils.common import format_number
|
|
14
15
|
|
|
@@ -17,80 +18,96 @@ def _get_version() -> str:
|
|
|
17
18
|
"""Get the current version of klaude-code."""
|
|
18
19
|
try:
|
|
19
20
|
return version("klaude-code")
|
|
20
|
-
except
|
|
21
|
+
except PackageNotFoundError:
|
|
21
22
|
return "unknown"
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
def
|
|
25
|
-
metadata
|
|
25
|
+
def _render_task_metadata_block(
|
|
26
|
+
metadata: model.TaskMetadata,
|
|
27
|
+
*,
|
|
28
|
+
is_sub_agent: bool = False,
|
|
29
|
+
show_context_and_time: bool = True,
|
|
30
|
+
) -> RenderableType:
|
|
31
|
+
"""Render a single TaskMetadata block.
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
Args:
|
|
34
|
+
metadata: The TaskMetadata to render.
|
|
35
|
+
is_sub_agent: Whether this is a sub-agent block.
|
|
36
|
+
show_context_and_time: Whether to show context usage percent and time.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
A renderable for this metadata block.
|
|
40
|
+
"""
|
|
41
|
+
grid = create_grid()
|
|
42
|
+
|
|
43
|
+
# Get currency symbol
|
|
44
|
+
currency = metadata.usage.currency if metadata.usage else "USD"
|
|
45
|
+
currency_symbol = "¥" if currency == "CNY" else "$"
|
|
46
|
+
|
|
47
|
+
# First column: mark only
|
|
48
|
+
mark = Text("└", style=ThemeKey.METADATA_DIM) if is_sub_agent else Text("⇅", style=ThemeKey.METADATA)
|
|
49
|
+
|
|
50
|
+
# Second column: model@provider / tokens / cost / ...
|
|
51
|
+
content = Text()
|
|
52
|
+
content.append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
|
|
32
53
|
if metadata.provider is not None:
|
|
33
|
-
|
|
54
|
+
content.append_text(Text("@", style=ThemeKey.METADATA)).append_text(
|
|
34
55
|
Text(metadata.provider.lower().replace(" ", "-"), style=ThemeKey.METADATA)
|
|
35
56
|
)
|
|
36
57
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Line 2: Token consumption, Context, TPS, Cost
|
|
58
|
+
# All info parts (tokens, cost, context, etc.)
|
|
40
59
|
parts: list[Text] = []
|
|
41
60
|
|
|
42
61
|
if metadata.usage is not None:
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
("
|
|
46
|
-
(format_number(metadata.usage.input_tokens), ThemeKey.METADATA_DIM),
|
|
62
|
+
# Tokens: ↑ 37k cache 5k ↓ 907 think 45k
|
|
63
|
+
token_parts: list[Text] = [
|
|
64
|
+
Text.assemble(("↑", ThemeKey.METADATA_DIM), (format_number(metadata.usage.input_tokens), ThemeKey.METADATA))
|
|
47
65
|
]
|
|
48
|
-
if metadata.usage.input_cost is not None:
|
|
49
|
-
input_parts.append((f"(${metadata.usage.input_cost:.4f})", ThemeKey.METADATA_DIM))
|
|
50
|
-
parts.append(Text.assemble(*input_parts))
|
|
51
|
-
|
|
52
|
-
# Cached
|
|
53
66
|
if metadata.usage.cached_tokens > 0:
|
|
54
|
-
|
|
55
|
-
(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
(format_number(metadata.usage.output_tokens), ThemeKey.METADATA_DIM),
|
|
66
|
-
]
|
|
67
|
-
if metadata.usage.output_cost is not None:
|
|
68
|
-
output_parts.append((f"(${metadata.usage.output_cost:.4f})", ThemeKey.METADATA_DIM))
|
|
69
|
-
parts.append(Text.assemble(*output_parts))
|
|
70
|
-
|
|
71
|
-
# Reasoning
|
|
67
|
+
token_parts.append(
|
|
68
|
+
Text.assemble(
|
|
69
|
+
Text("cache ", style=ThemeKey.METADATA_DIM),
|
|
70
|
+
Text(format_number(metadata.usage.cached_tokens), style=ThemeKey.METADATA),
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
token_parts.append(
|
|
74
|
+
Text.assemble(
|
|
75
|
+
("↓", ThemeKey.METADATA_DIM), (format_number(metadata.usage.output_tokens), ThemeKey.METADATA)
|
|
76
|
+
)
|
|
77
|
+
)
|
|
72
78
|
if metadata.usage.reasoning_tokens > 0:
|
|
73
|
-
|
|
79
|
+
token_parts.append(
|
|
74
80
|
Text.assemble(
|
|
75
|
-
("
|
|
76
|
-
(
|
|
77
|
-
(
|
|
78
|
-
format_number(metadata.usage.reasoning_tokens),
|
|
79
|
-
ThemeKey.METADATA_DIM,
|
|
80
|
-
),
|
|
81
|
+
("think ", ThemeKey.METADATA_DIM),
|
|
82
|
+
(format_number(metadata.usage.reasoning_tokens), ThemeKey.METADATA),
|
|
81
83
|
)
|
|
82
84
|
)
|
|
85
|
+
parts.append(Text(" · ").join(token_parts))
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
# Cost
|
|
88
|
+
if metadata.usage is not None and metadata.usage.total_cost is not None:
|
|
89
|
+
parts.append(
|
|
90
|
+
Text.assemble(
|
|
91
|
+
(currency_symbol, ThemeKey.METADATA_DIM),
|
|
92
|
+
(f"{metadata.usage.total_cost:.4f}", ThemeKey.METADATA),
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
if metadata.usage is not None:
|
|
96
|
+
# Context (only for main agent)
|
|
97
|
+
if show_context_and_time and metadata.usage.context_usage_percent is not None:
|
|
98
|
+
context_size = format_number(metadata.usage.context_size or 0)
|
|
99
|
+
# Calculate effective limit (same as Usage.context_usage_percent)
|
|
100
|
+
effective_limit = (metadata.usage.context_limit or 0) - (
|
|
101
|
+
metadata.usage.max_tokens or const.DEFAULT_MAX_TOKENS
|
|
102
|
+
)
|
|
103
|
+
effective_limit_str = format_number(effective_limit) if effective_limit > 0 else "?"
|
|
86
104
|
parts.append(
|
|
87
105
|
Text.assemble(
|
|
88
|
-
("context", ThemeKey.METADATA_DIM),
|
|
89
|
-
(
|
|
90
|
-
(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
),
|
|
106
|
+
("context ", ThemeKey.METADATA_DIM),
|
|
107
|
+
(context_size, ThemeKey.METADATA),
|
|
108
|
+
("/", ThemeKey.METADATA_DIM),
|
|
109
|
+
(effective_limit_str, ThemeKey.METADATA),
|
|
110
|
+
(f" ({metadata.usage.context_usage_percent:.1f}%)", ThemeKey.METADATA_DIM),
|
|
94
111
|
)
|
|
95
112
|
)
|
|
96
113
|
|
|
@@ -98,44 +115,63 @@ def render_response_metadata(e: events.ResponseMetadataEvent) -> RenderableType:
|
|
|
98
115
|
if metadata.usage.throughput_tps is not None:
|
|
99
116
|
parts.append(
|
|
100
117
|
Text.assemble(
|
|
101
|
-
("
|
|
102
|
-
("
|
|
103
|
-
|
|
118
|
+
(f"{metadata.usage.throughput_tps:.1f} ", ThemeKey.METADATA),
|
|
119
|
+
("avg-tps", ThemeKey.METADATA_DIM),
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# First token latency
|
|
124
|
+
if metadata.usage.first_token_latency_ms is not None:
|
|
125
|
+
parts.append(
|
|
126
|
+
Text.assemble(
|
|
127
|
+
(f"{metadata.usage.first_token_latency_ms:.0f}", ThemeKey.METADATA),
|
|
128
|
+
("ms avg-ftl", ThemeKey.METADATA_DIM),
|
|
104
129
|
)
|
|
105
130
|
)
|
|
106
131
|
|
|
107
132
|
# Duration
|
|
108
|
-
if metadata.task_duration_s is not None:
|
|
133
|
+
if show_context_and_time and metadata.task_duration_s is not None:
|
|
109
134
|
parts.append(
|
|
110
135
|
Text.assemble(
|
|
111
|
-
("
|
|
112
|
-
("
|
|
113
|
-
(f"{metadata.task_duration_s:.1f}s", ThemeKey.METADATA_DIM),
|
|
136
|
+
(f"{metadata.task_duration_s:.1f}", ThemeKey.METADATA),
|
|
137
|
+
("s", ThemeKey.METADATA_DIM),
|
|
114
138
|
)
|
|
115
139
|
)
|
|
116
140
|
|
|
117
|
-
#
|
|
118
|
-
if
|
|
141
|
+
# Turn count
|
|
142
|
+
if show_context_and_time and metadata.turn_count > 0:
|
|
119
143
|
parts.append(
|
|
120
144
|
Text.assemble(
|
|
121
|
-
(
|
|
122
|
-
("
|
|
123
|
-
(f"${metadata.usage.total_cost:.4f}", ThemeKey.METADATA_DIM),
|
|
145
|
+
(str(metadata.turn_count), ThemeKey.METADATA),
|
|
146
|
+
(" turns", ThemeKey.METADATA_DIM),
|
|
124
147
|
)
|
|
125
148
|
)
|
|
126
149
|
|
|
127
150
|
if parts:
|
|
128
|
-
|
|
129
|
-
|
|
151
|
+
content.append_text(Text(" · ", style=ThemeKey.METADATA_DIM))
|
|
152
|
+
content.append_text(Text(" · ", style=ThemeKey.METADATA_DIM).join(parts))
|
|
153
|
+
|
|
154
|
+
grid.add_row(mark, content)
|
|
155
|
+
return grid if not is_sub_agent else Padding(grid, (0, 0, 0, 2))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
159
|
+
"""Render task metadata including main agent and sub-agents."""
|
|
160
|
+
renderables: list[RenderableType] = []
|
|
161
|
+
|
|
162
|
+
renderables.append(
|
|
163
|
+
_render_task_metadata_block(e.metadata.main_agent, is_sub_agent=False, show_context_and_time=True)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Render each sub-agent metadata block
|
|
167
|
+
for meta in e.metadata.sub_agent_task_metadata:
|
|
168
|
+
renderables.append(_render_task_metadata_block(meta, is_sub_agent=True, show_context_and_time=False))
|
|
130
169
|
|
|
131
170
|
return Group(*renderables)
|
|
132
171
|
|
|
133
172
|
|
|
134
|
-
def render_welcome(e: events.WelcomeEvent
|
|
173
|
+
def render_welcome(e: events.WelcomeEvent) -> RenderableType:
|
|
135
174
|
"""Render the welcome panel with model info and settings."""
|
|
136
|
-
if box_style is None:
|
|
137
|
-
box_style = box.ROUNDED
|
|
138
|
-
|
|
139
175
|
debug_mode = is_debug_enabled()
|
|
140
176
|
|
|
141
177
|
# First line: Klaude Code version
|
|
@@ -185,6 +221,6 @@ def render_welcome(e: events.WelcomeEvent, *, box_style: Box | None = None) -> R
|
|
|
185
221
|
|
|
186
222
|
border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
|
|
187
223
|
return Group(
|
|
188
|
-
Panel.fit(panel_content, border_style=border_style, box=
|
|
224
|
+
Panel.fit(panel_content, border_style=border_style, box=box.ROUNDED),
|
|
189
225
|
"", # empty line
|
|
190
226
|
)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from typing import Any, cast
|
|
2
3
|
|
|
4
|
+
from rich import box
|
|
3
5
|
from rich.console import Group, RenderableType
|
|
6
|
+
from rich.json import JSON
|
|
4
7
|
from rich.panel import Panel
|
|
5
8
|
from rich.style import Style
|
|
6
9
|
from rich.text import Text
|
|
@@ -12,37 +15,112 @@ from klaude_code.ui.rich.markdown import NoInsetMarkdown
|
|
|
12
15
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
def _compact_schema_value(value: dict[str, Any]) -> str | list[Any] | dict[str, Any]:
|
|
19
|
+
"""Convert a JSON Schema value to compact representation."""
|
|
20
|
+
value_type = value.get("type", "any").lower()
|
|
21
|
+
desc = value.get("description", "")
|
|
22
|
+
|
|
23
|
+
if value_type == "object":
|
|
24
|
+
props = value.get("properties", {})
|
|
25
|
+
return {k: _compact_schema_value(v) for k, v in props.items()}
|
|
26
|
+
elif value_type == "array":
|
|
27
|
+
items = value.get("items", {})
|
|
28
|
+
# If items have no description, use the array's description
|
|
29
|
+
if desc and not items.get("description"):
|
|
30
|
+
items = {**items, "description": desc}
|
|
31
|
+
return [_compact_schema_value(items)]
|
|
32
|
+
else:
|
|
33
|
+
if desc:
|
|
34
|
+
return f"{value_type} // {desc}"
|
|
35
|
+
return value_type
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _compact_schema(schema: dict[str, Any]) -> dict[str, Any] | list[Any] | str:
|
|
39
|
+
"""Convert JSON Schema to compact representation for display."""
|
|
40
|
+
return _compact_schema_value(schema)
|
|
41
|
+
|
|
42
|
+
|
|
15
43
|
def render_sub_agent_call(e: model.SubAgentState, style: Style | None = None) -> RenderableType:
|
|
16
44
|
"""Render sub-agent tool call header and prompt body."""
|
|
17
45
|
desc = Text(
|
|
18
46
|
f" {e.sub_agent_desc} ",
|
|
19
47
|
style=Style(color=style.color if style else None, bold=True, reverse=True),
|
|
20
48
|
)
|
|
21
|
-
|
|
49
|
+
elements: list[RenderableType] = [
|
|
22
50
|
Text.assemble((e.sub_agent_type, ThemeKey.TOOL_NAME), " ", desc),
|
|
23
51
|
Text(e.sub_agent_prompt, style=style or ""),
|
|
24
|
-
|
|
52
|
+
]
|
|
53
|
+
if e.output_schema:
|
|
54
|
+
elements.append(Text("\nExpected Output Format JSON:", style=style or ""))
|
|
55
|
+
compact = _compact_schema(e.output_schema)
|
|
56
|
+
schema_text = json.dumps(compact, ensure_ascii=False, indent=2)
|
|
57
|
+
elements.append(JSON(schema_text))
|
|
58
|
+
return Group(*elements)
|
|
25
59
|
|
|
26
60
|
|
|
27
|
-
def render_sub_agent_result(
|
|
61
|
+
def render_sub_agent_result(
|
|
62
|
+
result: str,
|
|
63
|
+
*,
|
|
64
|
+
code_theme: str,
|
|
65
|
+
style: Style | None = None,
|
|
66
|
+
has_structured_output: bool = False,
|
|
67
|
+
description: str | None = None,
|
|
68
|
+
panel_style: Style | None = None,
|
|
69
|
+
) -> RenderableType:
|
|
28
70
|
stripped_result = result.strip()
|
|
71
|
+
result_panel_style = panel_style or ThemeKey.SUB_AGENT_RESULT_PANEL
|
|
72
|
+
|
|
73
|
+
# Use rich JSON for structured output
|
|
74
|
+
if has_structured_output:
|
|
75
|
+
try:
|
|
76
|
+
group_elements: list[RenderableType] = [
|
|
77
|
+
Text(
|
|
78
|
+
"use /export to view full output",
|
|
79
|
+
style=ThemeKey.TOOL_RESULT,
|
|
80
|
+
),
|
|
81
|
+
JSON(stripped_result),
|
|
82
|
+
]
|
|
83
|
+
if description:
|
|
84
|
+
group_elements.insert(0, NoInsetMarkdown(f"# {description}", code_theme=code_theme, style=style or ""))
|
|
85
|
+
return Panel.fit(
|
|
86
|
+
Group(*group_elements),
|
|
87
|
+
box=box.SIMPLE,
|
|
88
|
+
border_style=ThemeKey.LINES,
|
|
89
|
+
style=result_panel_style,
|
|
90
|
+
)
|
|
91
|
+
except json.JSONDecodeError:
|
|
92
|
+
# Fall back to markdown if not valid JSON
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# Add markdown heading if description is provided for non-structured output
|
|
96
|
+
if description:
|
|
97
|
+
stripped_result = f"# {description}\n\n{stripped_result}"
|
|
98
|
+
|
|
29
99
|
lines = stripped_result.splitlines()
|
|
30
100
|
if len(lines) > const.SUB_AGENT_RESULT_MAX_LINES:
|
|
31
101
|
hidden_count = len(lines) - const.SUB_AGENT_RESULT_MAX_LINES
|
|
32
|
-
|
|
102
|
+
head_count = const.SUB_AGENT_RESULT_MAX_LINES // 2
|
|
103
|
+
tail_count = const.SUB_AGENT_RESULT_MAX_LINES - head_count
|
|
104
|
+
head_text = "\n".join(lines[:head_count])
|
|
105
|
+
tail_text = "\n".join(lines[-tail_count:])
|
|
33
106
|
return Panel.fit(
|
|
34
107
|
Group(
|
|
108
|
+
NoInsetMarkdown(head_text, code_theme=code_theme, style=style or ""),
|
|
35
109
|
Text(
|
|
36
|
-
f"… more {hidden_count} lines — use /export to view full output",
|
|
37
|
-
style=ThemeKey.
|
|
110
|
+
f"\n( … more {hidden_count} lines — use /export to view full output )\n",
|
|
111
|
+
style=ThemeKey.TOOL_RESULT_TRUNCATED,
|
|
38
112
|
),
|
|
39
|
-
NoInsetMarkdown(
|
|
113
|
+
NoInsetMarkdown(tail_text, code_theme=code_theme, style=style or ""),
|
|
40
114
|
),
|
|
115
|
+
box=box.SIMPLE,
|
|
41
116
|
border_style=ThemeKey.LINES,
|
|
117
|
+
style=result_panel_style,
|
|
42
118
|
)
|
|
43
119
|
return Panel.fit(
|
|
44
120
|
NoInsetMarkdown(stripped_result, code_theme=code_theme),
|
|
121
|
+
box=box.SIMPLE,
|
|
45
122
|
border_style=ThemeKey.LINES,
|
|
123
|
+
style=result_panel_style,
|
|
46
124
|
)
|
|
47
125
|
|
|
48
126
|
|
|
@@ -53,6 +131,7 @@ def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAg
|
|
|
53
131
|
return None
|
|
54
132
|
description = profile.name
|
|
55
133
|
prompt = ""
|
|
134
|
+
output_schema: dict[str, Any] | None = None
|
|
56
135
|
if e.arguments:
|
|
57
136
|
try:
|
|
58
137
|
payload: dict[str, object] = json.loads(e.arguments)
|
|
@@ -64,8 +143,14 @@ def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAg
|
|
|
64
143
|
prompt_value = payload.get("prompt") or payload.get("task")
|
|
65
144
|
if isinstance(prompt_value, str):
|
|
66
145
|
prompt = prompt_value.strip()
|
|
146
|
+
# Extract output_schema if profile supports it
|
|
147
|
+
if profile.output_schema_arg:
|
|
148
|
+
schema_value = payload.get(profile.output_schema_arg)
|
|
149
|
+
if isinstance(schema_value, dict):
|
|
150
|
+
output_schema = cast(dict[str, Any], schema_value)
|
|
67
151
|
return model.SubAgentState(
|
|
68
152
|
sub_agent_type=profile.name,
|
|
69
153
|
sub_agent_desc=description,
|
|
70
154
|
sub_agent_prompt=prompt,
|
|
155
|
+
output_schema=output_schema,
|
|
71
156
|
)
|
|
@@ -1,27 +1,44 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
1
3
|
from rich.console import RenderableType
|
|
2
4
|
from rich.padding import Padding
|
|
3
5
|
from rich.text import Text
|
|
4
6
|
|
|
5
|
-
from klaude_code
|
|
7
|
+
from klaude_code import const
|
|
8
|
+
from klaude_code.ui.renderers.common import create_grid
|
|
9
|
+
from klaude_code.ui.rich.markdown import ThinkingMarkdown
|
|
6
10
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return Text.from_markup("[not italic]⸫[/not italic] Thinking …", style=ThemeKey.THINKING)
|
|
12
|
+
# UI markers
|
|
13
|
+
THINKING_MESSAGE_MARK = "∴"
|
|
11
14
|
|
|
12
15
|
|
|
13
|
-
def
|
|
16
|
+
def normalize_thinking_content(content: str) -> str:
|
|
14
17
|
"""Normalize thinking content for display."""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
text = content.rstrip()
|
|
19
|
+
|
|
20
|
+
# Weird case of Gemini 3
|
|
21
|
+
text = text.replace("\\n\\n\n\n", "")
|
|
22
|
+
|
|
23
|
+
# Fix OpenRouter OpenAI reasoning formatting where segments like
|
|
24
|
+
# "text**Title**\n\n" lose the blank line between segments.
|
|
25
|
+
# We want: "text\n**Title**\n" so that each bold title starts on
|
|
26
|
+
# its own line and uses a single trailing newline.
|
|
27
|
+
text = re.sub(r"([^\n])(\*\*[^*]+?\*\*)\n\n", r"\1 \n\n\2 \n", text)
|
|
28
|
+
|
|
29
|
+
# Remove extra newlines between back-to-back bold titles, eg
|
|
30
|
+
# "**Title1****Title2**" -> "**Title1**\n\n**Title2**".
|
|
31
|
+
text = text.replace("****", "**\n\n**")
|
|
32
|
+
|
|
33
|
+
# Compact double-newline after bold so the body text follows
|
|
34
|
+
# directly after the title line, using a markdown line break.
|
|
35
|
+
text = text.replace("**\n\n", "** \n")
|
|
36
|
+
|
|
37
|
+
return text
|
|
21
38
|
|
|
22
39
|
|
|
23
40
|
def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableType | None:
|
|
24
|
-
"""Render thinking content as
|
|
41
|
+
"""Render thinking content as markdown with left mark.
|
|
25
42
|
|
|
26
43
|
Returns None if content is empty.
|
|
27
44
|
Note: Caller should push thinking_markdown_theme before printing.
|
|
@@ -29,11 +46,16 @@ def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableT
|
|
|
29
46
|
if len(content.strip()) == 0:
|
|
30
47
|
return None
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
grid = create_grid()
|
|
50
|
+
grid.add_row(
|
|
51
|
+
Text(THINKING_MESSAGE_MARK, style=ThemeKey.THINKING),
|
|
52
|
+
Padding(
|
|
53
|
+
ThinkingMarkdown(
|
|
54
|
+
normalize_thinking_content(content),
|
|
55
|
+
code_theme=code_theme,
|
|
56
|
+
style=style,
|
|
57
|
+
),
|
|
58
|
+
(0, const.MARKDOWN_RIGHT_MARGIN, 0, 0),
|
|
37
59
|
),
|
|
38
|
-
level=2,
|
|
39
60
|
)
|
|
61
|
+
return grid
|