zjcode 0.0.1__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.
- deepagents_code/__init__.py +42 -0
- deepagents_code/__main__.py +6 -0
- deepagents_code/_ask_user_types.py +90 -0
- deepagents_code/_cli_context.py +96 -0
- deepagents_code/_constants.py +41 -0
- deepagents_code/_debug.py +148 -0
- deepagents_code/_debug_buffer.py +204 -0
- deepagents_code/_env_vars.py +411 -0
- deepagents_code/_fake_models.py +66 -0
- deepagents_code/_git.py +521 -0
- deepagents_code/_paths.py +69 -0
- deepagents_code/_server_config.py +576 -0
- deepagents_code/_session_stats.py +235 -0
- deepagents_code/_startup_error.py +45 -0
- deepagents_code/_testing_models.py +305 -0
- deepagents_code/_textual_patches.py +420 -0
- deepagents_code/_tool_stream.py +694 -0
- deepagents_code/_version.py +46 -0
- deepagents_code/agent.py +1976 -0
- deepagents_code/app.py +19239 -0
- deepagents_code/app.tcss +438 -0
- deepagents_code/approval_mode.py +131 -0
- deepagents_code/ask_user.py +306 -0
- deepagents_code/auth_display.py +185 -0
- deepagents_code/auth_store.py +545 -0
- deepagents_code/built_in_skills/__init__.py +5 -0
- deepagents_code/built_in_skills/remember/SKILL.md +118 -0
- deepagents_code/built_in_skills/skill-creator/SKILL.md +383 -0
- deepagents_code/built_in_skills/skill-creator/scripts/init_skill.py +366 -0
- deepagents_code/built_in_skills/skill-creator/scripts/quick_validate.py +158 -0
- deepagents_code/client/__init__.py +1 -0
- deepagents_code/client/commands/__init__.py +1 -0
- deepagents_code/client/commands/auth.py +541 -0
- deepagents_code/client/commands/config.py +714 -0
- deepagents_code/client/commands/mcp.py +250 -0
- deepagents_code/client/commands/tools.py +416 -0
- deepagents_code/client/launch/__init__.py +1 -0
- deepagents_code/client/launch/server.py +978 -0
- deepagents_code/client/launch/server_manager.py +540 -0
- deepagents_code/client/non_interactive.py +1758 -0
- deepagents_code/client/remote_client.py +794 -0
- deepagents_code/clipboard.py +217 -0
- deepagents_code/command_registry.py +450 -0
- deepagents_code/config.py +4829 -0
- deepagents_code/config_manifest.py +1451 -0
- deepagents_code/configurable_model.py +577 -0
- deepagents_code/default_agent_prompt.md +12 -0
- deepagents_code/doctor.py +559 -0
- deepagents_code/editor.py +142 -0
- deepagents_code/event_bus.py +411 -0
- deepagents_code/extras_info.py +661 -0
- deepagents_code/file_ops.py +576 -0
- deepagents_code/formatting.py +135 -0
- deepagents_code/goal_rubric.py +497 -0
- deepagents_code/goal_tools.py +459 -0
- deepagents_code/hooks.py +348 -0
- deepagents_code/input.py +1041 -0
- deepagents_code/integrations/__init__.py +1 -0
- deepagents_code/integrations/openai_codex.py +551 -0
- deepagents_code/integrations/sandbox_config.py +198 -0
- deepagents_code/integrations/sandbox_factory.py +1124 -0
- deepagents_code/integrations/sandbox_provider.py +137 -0
- deepagents_code/integrations/sandbox_registry.py +350 -0
- deepagents_code/iterm_cursor_guide.py +176 -0
- deepagents_code/local_context.py +926 -0
- deepagents_code/main.py +3985 -0
- deepagents_code/managed_tools.py +642 -0
- deepagents_code/mcp_auth.py +1950 -0
- deepagents_code/mcp_config.py +176 -0
- deepagents_code/mcp_disabled.py +212 -0
- deepagents_code/mcp_login_service.py +281 -0
- deepagents_code/mcp_oauth_ui.py +199 -0
- deepagents_code/mcp_providers/__init__.py +23 -0
- deepagents_code/mcp_providers/_registry.py +39 -0
- deepagents_code/mcp_providers/base.py +133 -0
- deepagents_code/mcp_providers/github.py +102 -0
- deepagents_code/mcp_providers/slack.py +175 -0
- deepagents_code/mcp_tools.py +2427 -0
- deepagents_code/mcp_trust.py +207 -0
- deepagents_code/media_utils.py +826 -0
- deepagents_code/memory_guard.py +474 -0
- deepagents_code/model_config.py +4156 -0
- deepagents_code/notifications.py +247 -0
- deepagents_code/offload.py +402 -0
- deepagents_code/onboarding.py +223 -0
- deepagents_code/output.py +69 -0
- deepagents_code/paste_collapse.py +103 -0
- deepagents_code/project_utils.py +231 -0
- deepagents_code/py.typed +0 -0
- deepagents_code/reasoning_effort.py +641 -0
- deepagents_code/reliable_rubric.py +97 -0
- deepagents_code/resume_state.py +237 -0
- deepagents_code/server_graph.py +310 -0
- deepagents_code/session_end_summary.py +329 -0
- deepagents_code/sessions.py +1716 -0
- deepagents_code/skills/__init__.py +18 -0
- deepagents_code/skills/commands.py +1252 -0
- deepagents_code/skills/invocation.py +112 -0
- deepagents_code/skills/load.py +196 -0
- deepagents_code/skills/trust.py +547 -0
- deepagents_code/state_migration.py +136 -0
- deepagents_code/subagents.py +278 -0
- deepagents_code/system_prompt.md +204 -0
- deepagents_code/terminal_capabilities.py +115 -0
- deepagents_code/terminal_escape.py +287 -0
- deepagents_code/theme.py +891 -0
- deepagents_code/todo_list_prompt.md +12 -0
- deepagents_code/tool_catalog.py +509 -0
- deepagents_code/tool_display.py +367 -0
- deepagents_code/tools.py +516 -0
- deepagents_code/tui/__init__.py +1 -0
- deepagents_code/tui/textual_adapter.py +2553 -0
- deepagents_code/tui/widgets/__init__.py +9 -0
- deepagents_code/tui/widgets/_js_eval_display.py +139 -0
- deepagents_code/tui/widgets/_links.py +261 -0
- deepagents_code/tui/widgets/agent_selector.py +420 -0
- deepagents_code/tui/widgets/approval.py +602 -0
- deepagents_code/tui/widgets/ask_user.py +515 -0
- deepagents_code/tui/widgets/auth.py +1997 -0
- deepagents_code/tui/widgets/autocomplete.py +890 -0
- deepagents_code/tui/widgets/chat_input.py +3181 -0
- deepagents_code/tui/widgets/codex_auth.py +452 -0
- deepagents_code/tui/widgets/cwd_switch.py +242 -0
- deepagents_code/tui/widgets/debug_console.py +868 -0
- deepagents_code/tui/widgets/diff.py +252 -0
- deepagents_code/tui/widgets/effort_selector.py +189 -0
- deepagents_code/tui/widgets/goal_review.py +390 -0
- deepagents_code/tui/widgets/goal_status.py +50 -0
- deepagents_code/tui/widgets/history.py +194 -0
- deepagents_code/tui/widgets/install_confirm.py +248 -0
- deepagents_code/tui/widgets/launch_init.py +482 -0
- deepagents_code/tui/widgets/loading.py +227 -0
- deepagents_code/tui/widgets/mcp_login.py +539 -0
- deepagents_code/tui/widgets/mcp_reconnect.py +212 -0
- deepagents_code/tui/widgets/mcp_viewer.py +1634 -0
- deepagents_code/tui/widgets/message_store.py +999 -0
- deepagents_code/tui/widgets/messages.py +3846 -0
- deepagents_code/tui/widgets/model_selector.py +2343 -0
- deepagents_code/tui/widgets/notification_center.py +456 -0
- deepagents_code/tui/widgets/notification_detail.py +257 -0
- deepagents_code/tui/widgets/notification_settings.py +170 -0
- deepagents_code/tui/widgets/restart_prompt.py +158 -0
- deepagents_code/tui/widgets/skill_trust.py +131 -0
- deepagents_code/tui/widgets/startup_tip.py +91 -0
- deepagents_code/tui/widgets/status.py +781 -0
- deepagents_code/tui/widgets/subagent_panel.py +969 -0
- deepagents_code/tui/widgets/theme_selector.py +401 -0
- deepagents_code/tui/widgets/thread_selector.py +2564 -0
- deepagents_code/tui/widgets/tool_renderers.py +190 -0
- deepagents_code/tui/widgets/tool_widgets.py +304 -0
- deepagents_code/tui/widgets/update_available.py +311 -0
- deepagents_code/tui/widgets/update_confirm.py +184 -0
- deepagents_code/tui/widgets/update_progress.py +327 -0
- deepagents_code/tui/widgets/welcome.py +508 -0
- deepagents_code/turn_end_summary.py +522 -0
- deepagents_code/ui.py +858 -0
- deepagents_code/unicode_security.py +563 -0
- deepagents_code/update_check.py +3124 -0
- zjcode-0.0.1.data/data/deepagents_code/default_agent_prompt.md +12 -0
- zjcode-0.0.1.dist-info/METADATA +220 -0
- zjcode-0.0.1.dist-info/RECORD +163 -0
- zjcode-0.0.1.dist-info/WHEEL +4 -0
- zjcode-0.0.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""Formatting utilities for tool call display in the app.
|
|
2
|
+
|
|
3
|
+
This module handles rendering tool calls and tool messages for the TUI.
|
|
4
|
+
|
|
5
|
+
Imported at module level by `textual_adapter` (itself deferred from the startup
|
|
6
|
+
path). Heavy SDK dependencies (e.g., `backends`) are deferred to function bodies.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from contextlib import suppress
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from deepagents_code.config import MAX_ARG_LENGTH, get_glyphs
|
|
16
|
+
from deepagents_code.unicode_security import strip_dangerous_unicode
|
|
17
|
+
|
|
18
|
+
_HIDDEN_CHAR_MARKER = " [hidden chars removed]"
|
|
19
|
+
"""Marker appended to display values that had dangerous Unicode stripped, so
|
|
20
|
+
users know the value was modified for safety."""
|
|
21
|
+
|
|
22
|
+
JS_EVAL_HEADER_MAX_LENGTH = 120
|
|
23
|
+
"""Width at which the `js_eval` header truncates the first code line.
|
|
24
|
+
|
|
25
|
+
Shared with `messages.py` so the "header truncates the first line" cutoff and
|
|
26
|
+
the "offer a collapsible code block" threshold stay in lock-step from a single
|
|
27
|
+
source of truth.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
EXECUTE_HEADER_MAX_LENGTH = 120
|
|
31
|
+
"""Width at which the `execute` header truncates the shell command.
|
|
32
|
+
|
|
33
|
+
Shared with `messages.py` so the header cutoff and the "offer a collapsible
|
|
34
|
+
command block" threshold stay in lock-step from a single source of truth.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _format_timeout(seconds: int) -> str:
|
|
39
|
+
"""Format timeout in human-readable units (e.g., 300 -> '5m', 3600 -> '1h').
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
seconds: The timeout value in seconds to format.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Human-readable timeout string (e.g., '5m', '1h', '300s').
|
|
46
|
+
"""
|
|
47
|
+
if seconds < 60: # noqa: PLR2004 # Time unit boundary
|
|
48
|
+
return f"{seconds}s"
|
|
49
|
+
if seconds < 3600 and seconds % 60 == 0: # noqa: PLR2004 # Time unit boundaries
|
|
50
|
+
return f"{seconds // 60}m"
|
|
51
|
+
if seconds % 3600 == 0:
|
|
52
|
+
return f"{seconds // 3600}h"
|
|
53
|
+
# For odd values, just show seconds
|
|
54
|
+
return f"{seconds}s"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _coerce_timeout_seconds(timeout: int | str | None) -> int | None:
|
|
58
|
+
"""Normalize timeout values to seconds for display.
|
|
59
|
+
|
|
60
|
+
Accepts integer values and numeric strings. Returns `None` for invalid
|
|
61
|
+
values so display formatting never raises.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
timeout: Raw timeout value from tool arguments.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Integer timeout in seconds, or `None` if unavailable/invalid.
|
|
68
|
+
"""
|
|
69
|
+
if type(timeout) is int:
|
|
70
|
+
return timeout
|
|
71
|
+
if isinstance(timeout, str):
|
|
72
|
+
stripped = timeout.strip()
|
|
73
|
+
if not stripped:
|
|
74
|
+
return None
|
|
75
|
+
try:
|
|
76
|
+
return int(stripped)
|
|
77
|
+
except ValueError:
|
|
78
|
+
return None
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def truncate_value(value: str, max_length: int = MAX_ARG_LENGTH) -> str:
|
|
83
|
+
"""Truncate a string value if it exceeds max_length.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Truncated string with ellipsis suffix if exceeded, otherwise original.
|
|
87
|
+
"""
|
|
88
|
+
if len(value) > max_length:
|
|
89
|
+
return value[:max_length] + get_glyphs().ellipsis
|
|
90
|
+
return value
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _sanitize_display_value(value: object, *, max_length: int = MAX_ARG_LENGTH) -> str:
|
|
94
|
+
"""Sanitize a value for safe, compact terminal display.
|
|
95
|
+
|
|
96
|
+
Hidden/deceptive Unicode controls are stripped. When stripping occurs, a
|
|
97
|
+
marker is appended so users know the value changed for display safety.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
value: Any value to display.
|
|
101
|
+
max_length: Maximum display length before truncation.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Sanitized display string.
|
|
105
|
+
"""
|
|
106
|
+
raw = str(value)
|
|
107
|
+
sanitized = strip_dangerous_unicode(raw)
|
|
108
|
+
display = truncate_value(sanitized, max_length)
|
|
109
|
+
if sanitized != raw:
|
|
110
|
+
return display + _HIDDEN_CHAR_MARKER
|
|
111
|
+
return display
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _format_scope_path(
|
|
115
|
+
path_value: object,
|
|
116
|
+
abbreviate: Callable[[str], str],
|
|
117
|
+
) -> str:
|
|
118
|
+
"""Format a glob/grep `path` argument as a display suffix.
|
|
119
|
+
|
|
120
|
+
The glob tool defaults `path` to the backend root (`"/"`); the grep tool
|
|
121
|
+
defaults it to `None` (the backend's working directory). In either case the
|
|
122
|
+
default scope adds no information and is omitted. Only an explicit, non-root
|
|
123
|
+
path is rendered, so that two otherwise-identical calls scoped to different
|
|
124
|
+
directories are distinguishable in the UI. The rendered path is shortened
|
|
125
|
+
via the supplied `abbreviate` helper.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
path_value: The raw `path` argument, or `None` when not supplied.
|
|
129
|
+
abbreviate: Path-shortening helper from the calling scope.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
A suffix like ` in langchain`, or an empty string for the default scope.
|
|
133
|
+
"""
|
|
134
|
+
if path_value is None:
|
|
135
|
+
return ""
|
|
136
|
+
raw = str(path_value)
|
|
137
|
+
if raw in {"", "/"}:
|
|
138
|
+
return ""
|
|
139
|
+
sanitized = strip_dangerous_unicode(raw)
|
|
140
|
+
display = abbreviate(sanitized)
|
|
141
|
+
if sanitized != raw:
|
|
142
|
+
display += _HIDDEN_CHAR_MARKER
|
|
143
|
+
return f" in {display}"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def format_tool_display(tool_name: str, tool_args: dict) -> str:
|
|
147
|
+
"""Format tool calls for display with tool-specific smart formatting.
|
|
148
|
+
|
|
149
|
+
Shows the most relevant information for each tool type rather than all arguments.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
tool_name: Name of the tool being called
|
|
153
|
+
tool_args: Dictionary of tool arguments
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Formatted string for display (e.g., "(*) read_file(config.py)" in ASCII mode)
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
read_file(file_path="/long/path/file.py") → "<prefix> read_file(file.py)"
|
|
160
|
+
web_search(query="how to code") → '<prefix> web_search("how to code")'
|
|
161
|
+
execute(command="pip install foo") → '<prefix> execute("pip install foo")'
|
|
162
|
+
"""
|
|
163
|
+
prefix = get_glyphs().tool_prefix
|
|
164
|
+
|
|
165
|
+
def abbreviate_path(path_str: str, max_length: int = 60) -> str:
|
|
166
|
+
"""Abbreviate a file path intelligently - show basename or relative path.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Shortened path string suitable for display.
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
path = Path(path_str)
|
|
173
|
+
|
|
174
|
+
# If it's just a filename (no directory parts), return as-is
|
|
175
|
+
if len(path.parts) == 1:
|
|
176
|
+
return path_str
|
|
177
|
+
|
|
178
|
+
# Try to get relative path from current working directory
|
|
179
|
+
with suppress(
|
|
180
|
+
ValueError, # ValueError: path is not relative to cwd
|
|
181
|
+
OSError, # OSError: filesystem errors when resolving paths
|
|
182
|
+
):
|
|
183
|
+
rel_path = path.relative_to(Path.cwd())
|
|
184
|
+
rel_str = str(rel_path)
|
|
185
|
+
# Use relative if it's shorter and not too long
|
|
186
|
+
if len(rel_str) < len(path_str) and len(rel_str) <= max_length:
|
|
187
|
+
return rel_str
|
|
188
|
+
|
|
189
|
+
# If absolute path is reasonable length, use it
|
|
190
|
+
if len(path_str) <= max_length:
|
|
191
|
+
return path_str
|
|
192
|
+
except Exception: # noqa: BLE001 # Fallback to original string on any path resolution error
|
|
193
|
+
return truncate_value(path_str, max_length)
|
|
194
|
+
else:
|
|
195
|
+
# Otherwise, just show basename (filename only)
|
|
196
|
+
return path.name
|
|
197
|
+
|
|
198
|
+
# Tool-specific formatting - show the most important argument(s)
|
|
199
|
+
if tool_name in {"read_file", "write_file", "edit_file", "delete"}:
|
|
200
|
+
# File operations: show the primary file path argument (file_path or path)
|
|
201
|
+
path_value = tool_args.get("file_path")
|
|
202
|
+
if path_value is None:
|
|
203
|
+
path_value = tool_args.get("path")
|
|
204
|
+
if path_value is not None:
|
|
205
|
+
path_raw = strip_dangerous_unicode(str(path_value))
|
|
206
|
+
path = abbreviate_path(path_raw)
|
|
207
|
+
if path_raw != str(path_value):
|
|
208
|
+
path += _HIDDEN_CHAR_MARKER
|
|
209
|
+
return f"{prefix} {tool_name}({path})"
|
|
210
|
+
|
|
211
|
+
elif tool_name == "web_search":
|
|
212
|
+
# Web search: show the query string
|
|
213
|
+
if "query" in tool_args:
|
|
214
|
+
query = _sanitize_display_value(tool_args["query"], max_length=100)
|
|
215
|
+
return f'{prefix} {tool_name}("{query}")'
|
|
216
|
+
|
|
217
|
+
elif tool_name == "grep":
|
|
218
|
+
# Grep: show the search pattern, and the scoped path when non-default
|
|
219
|
+
if "pattern" in tool_args:
|
|
220
|
+
pattern = _sanitize_display_value(tool_args["pattern"], max_length=70)
|
|
221
|
+
scope = _format_scope_path(tool_args.get("path"), abbreviate_path)
|
|
222
|
+
return f'{prefix} {tool_name}("{pattern}"{scope})'
|
|
223
|
+
|
|
224
|
+
elif tool_name == "execute":
|
|
225
|
+
# Execute: show the command, and timeout only if non-default
|
|
226
|
+
if "command" in tool_args:
|
|
227
|
+
command = _sanitize_display_value(
|
|
228
|
+
tool_args["command"], max_length=EXECUTE_HEADER_MAX_LENGTH
|
|
229
|
+
)
|
|
230
|
+
timeout = _coerce_timeout_seconds(tool_args.get("timeout"))
|
|
231
|
+
from deepagents.backends import DEFAULT_EXECUTE_TIMEOUT
|
|
232
|
+
|
|
233
|
+
if timeout is not None and timeout != DEFAULT_EXECUTE_TIMEOUT:
|
|
234
|
+
timeout_str = _format_timeout(timeout)
|
|
235
|
+
return f'{prefix} {tool_name}("{command}", timeout={timeout_str})'
|
|
236
|
+
return f'{prefix} {tool_name}("{command}")'
|
|
237
|
+
|
|
238
|
+
elif tool_name == "js_eval":
|
|
239
|
+
# JS interpreter: show only the first non-blank line of the snippet so a
|
|
240
|
+
# multi-line program collapses to a single, scannable header line. The
|
|
241
|
+
# full code is available via the collapsible args block.
|
|
242
|
+
code = tool_args.get("code")
|
|
243
|
+
if isinstance(code, str) and code.strip():
|
|
244
|
+
first_line = next(
|
|
245
|
+
(line for line in code.splitlines() if line.strip()), ""
|
|
246
|
+
).strip()
|
|
247
|
+
multiline = sum(1 for line in code.splitlines() if line.strip()) > 1
|
|
248
|
+
snippet = _sanitize_display_value(
|
|
249
|
+
first_line, max_length=JS_EVAL_HEADER_MAX_LENGTH
|
|
250
|
+
)
|
|
251
|
+
ellipsis = get_glyphs().ellipsis if multiline else ""
|
|
252
|
+
return f'{prefix} {tool_name}("{snippet}{ellipsis}")'
|
|
253
|
+
return f"{prefix} {tool_name}()"
|
|
254
|
+
|
|
255
|
+
elif tool_name == "ls":
|
|
256
|
+
# ls: show directory, or empty if current directory
|
|
257
|
+
if tool_args.get("path"):
|
|
258
|
+
path_raw = strip_dangerous_unicode(str(tool_args["path"]))
|
|
259
|
+
path = abbreviate_path(path_raw)
|
|
260
|
+
if path_raw != str(tool_args["path"]):
|
|
261
|
+
path += _HIDDEN_CHAR_MARKER
|
|
262
|
+
return f"{prefix} {tool_name}({path})"
|
|
263
|
+
return f"{prefix} {tool_name}()"
|
|
264
|
+
|
|
265
|
+
elif tool_name == "glob":
|
|
266
|
+
# Glob: show the pattern, and the scoped path when non-default
|
|
267
|
+
if "pattern" in tool_args:
|
|
268
|
+
pattern = _sanitize_display_value(tool_args["pattern"], max_length=80)
|
|
269
|
+
scope = _format_scope_path(tool_args.get("path"), abbreviate_path)
|
|
270
|
+
return f'{prefix} {tool_name}("{pattern}"{scope})'
|
|
271
|
+
|
|
272
|
+
elif tool_name == "fetch_url":
|
|
273
|
+
# Fetch URL: show the URL being fetched
|
|
274
|
+
if "url" in tool_args:
|
|
275
|
+
url = _sanitize_display_value(tool_args["url"], max_length=80)
|
|
276
|
+
return f'{prefix} {tool_name}("{url}")'
|
|
277
|
+
|
|
278
|
+
elif tool_name == "task":
|
|
279
|
+
# Task: show subagent type badge
|
|
280
|
+
agent_type = tool_args.get("subagent_type", "")
|
|
281
|
+
if agent_type:
|
|
282
|
+
agent_type = _sanitize_display_value(agent_type, max_length=40)
|
|
283
|
+
return f"{prefix} {tool_name} [{agent_type}]"
|
|
284
|
+
return f"{prefix} {tool_name}"
|
|
285
|
+
|
|
286
|
+
elif tool_name == "ask_user":
|
|
287
|
+
if "questions" in tool_args and isinstance(tool_args["questions"], list):
|
|
288
|
+
count = len(tool_args["questions"])
|
|
289
|
+
label = "question" if count == 1 else "questions"
|
|
290
|
+
return f"{prefix} {tool_name}({count} {label})"
|
|
291
|
+
|
|
292
|
+
elif tool_name == "compact_conversation":
|
|
293
|
+
return f"{prefix} {tool_name}()"
|
|
294
|
+
|
|
295
|
+
elif tool_name == "write_todos":
|
|
296
|
+
if "todos" in tool_args and isinstance(tool_args["todos"], list):
|
|
297
|
+
count = len(tool_args["todos"])
|
|
298
|
+
return f"{prefix} {tool_name}({count} items)"
|
|
299
|
+
|
|
300
|
+
# Fallback: generic formatting for unknown tools
|
|
301
|
+
# Show all arguments in key=value format
|
|
302
|
+
args_str = ", ".join(
|
|
303
|
+
f"{_sanitize_display_value(k, max_length=30)}="
|
|
304
|
+
f"{_sanitize_display_value(v, max_length=50)}"
|
|
305
|
+
for k, v in tool_args.items()
|
|
306
|
+
)
|
|
307
|
+
return f"{prefix} {tool_name}({args_str})"
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _format_content_block(block: dict) -> str:
|
|
311
|
+
"""Format a single content block dict for display.
|
|
312
|
+
|
|
313
|
+
Replaces large binary payloads (e.g. base64 image/video data) with a
|
|
314
|
+
human-readable placeholder so they don't flood the terminal.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
block: An `ImageContentBlock`, `VideoContentBlock`, or `FileContentBlock`
|
|
318
|
+
dictionary.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
A display-friendly string for the block.
|
|
322
|
+
"""
|
|
323
|
+
if block.get("type") == "image" and isinstance(block.get("base64"), str):
|
|
324
|
+
b64 = block["base64"]
|
|
325
|
+
size_kb = len(b64) * 3 // 4 // 1024 # approximate decoded size
|
|
326
|
+
mime = block.get("mime_type", "image")
|
|
327
|
+
return f"[Image: {mime}, ~{size_kb}KB]"
|
|
328
|
+
if block.get("type") == "video" and isinstance(block.get("base64"), str):
|
|
329
|
+
b64 = block["base64"]
|
|
330
|
+
size_kb = len(b64) * 3 // 4 // 1024 # approximate decoded size
|
|
331
|
+
mime = block.get("mime_type", "video")
|
|
332
|
+
return f"[Video: {mime}, ~{size_kb}KB]"
|
|
333
|
+
if block.get("type") == "file" and isinstance(block.get("base64"), str):
|
|
334
|
+
b64 = block["base64"]
|
|
335
|
+
size_kb = len(b64) * 3 // 4 // 1024 # approximate decoded size
|
|
336
|
+
mime = block.get("mime_type", "file")
|
|
337
|
+
return f"[File: {mime}, ~{size_kb}KB]"
|
|
338
|
+
try:
|
|
339
|
+
# Preserve non-ASCII characters (CJK, emoji, etc.) instead of \uXXXX escapes
|
|
340
|
+
return json.dumps(block, ensure_ascii=False)
|
|
341
|
+
except (TypeError, ValueError):
|
|
342
|
+
return str(block)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def format_tool_message_content(content: Any) -> str: # noqa: ANN401 # Content can be str, list, or dict
|
|
346
|
+
"""Convert `ToolMessage` content into a printable string.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Formatted string representation of the tool message content.
|
|
350
|
+
"""
|
|
351
|
+
if content is None:
|
|
352
|
+
return ""
|
|
353
|
+
if isinstance(content, list):
|
|
354
|
+
parts = []
|
|
355
|
+
for item in content:
|
|
356
|
+
if isinstance(item, str):
|
|
357
|
+
parts.append(item)
|
|
358
|
+
elif isinstance(item, dict):
|
|
359
|
+
parts.append(_format_content_block(item))
|
|
360
|
+
else:
|
|
361
|
+
try:
|
|
362
|
+
# Preserve non-ASCII characters (CJK, emoji, etc.)
|
|
363
|
+
parts.append(json.dumps(item, ensure_ascii=False))
|
|
364
|
+
except (TypeError, ValueError):
|
|
365
|
+
parts.append(str(item))
|
|
366
|
+
return "\n".join(parts)
|
|
367
|
+
return str(content)
|