code-puppy 0.0.123__py3-none-any.whl → 0.0.125__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.
- code_puppy/agent.py +20 -4
- code_puppy/agents/__init__.py +25 -0
- code_puppy/{agent_prompts.py → agents/agent_code_puppy.py} +46 -13
- code_puppy/agents/agent_creator_agent.py +446 -0
- code_puppy/agents/agent_manager.py +211 -0
- code_puppy/agents/base_agent.py +60 -0
- code_puppy/agents/json_agent.py +129 -0
- code_puppy/callbacks.py +24 -0
- code_puppy/command_line/command_handler.py +126 -10
- code_puppy/config.py +68 -10
- code_puppy/main.py +13 -4
- code_puppy/message_history_processor.py +54 -7
- code_puppy/tools/__init__.py +60 -7
- code_puppy/tools/command_runner.py +100 -1
- code_puppy/tools/file_modifications.py +179 -11
- code_puppy/tools/file_operations.py +171 -1
- code_puppy/tui/app.py +22 -160
- code_puppy/tui/components/status_bar.py +4 -4
- code_puppy/tui/screens/settings.py +53 -18
- code_puppy/tui/tests/test_agent_command.py +72 -0
- code_puppy-0.0.125.dist-info/METADATA +634 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/RECORD +26 -20
- code_puppy-0.0.123.dist-info/METADATA +0 -192
- {code_puppy-0.0.123.data → code_puppy-0.0.125.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/licenses/LICENSE +0 -0
code_puppy/config.py
CHANGED
|
@@ -9,6 +9,7 @@ MCP_SERVERS_FILE = os.path.join(CONFIG_DIR, "mcp_servers.json")
|
|
|
9
9
|
COMMAND_HISTORY_FILE = os.path.join(CONFIG_DIR, "command_history.txt")
|
|
10
10
|
MODELS_FILE = os.path.join(CONFIG_DIR, "models.json")
|
|
11
11
|
EXTRA_MODELS_FILE = os.path.join(CONFIG_DIR, "extra_models.json")
|
|
12
|
+
AGENTS_DIR = os.path.join(CONFIG_DIR, "agents")
|
|
12
13
|
|
|
13
14
|
DEFAULT_SECTION = "puppy"
|
|
14
15
|
REQUIRED_KEYS = ["puppy_name", "owner_name"]
|
|
@@ -68,13 +69,33 @@ def get_owner_name():
|
|
|
68
69
|
# using get_protected_token_count() and get_summarization_threshold()
|
|
69
70
|
|
|
70
71
|
|
|
72
|
+
def get_model_context_length() -> int:
|
|
73
|
+
"""
|
|
74
|
+
Get the context length for the currently configured model from models.json
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
from code_puppy.model_factory import ModelFactory
|
|
78
|
+
|
|
79
|
+
model_configs = ModelFactory.load_config()
|
|
80
|
+
model_name = get_model_name()
|
|
81
|
+
|
|
82
|
+
# Get context length from model config
|
|
83
|
+
model_config = model_configs.get(model_name, {})
|
|
84
|
+
context_length = model_config.get("context_length", 128000) # Default value
|
|
85
|
+
|
|
86
|
+
return int(context_length)
|
|
87
|
+
except Exception:
|
|
88
|
+
# Fallback to default context length if anything goes wrong
|
|
89
|
+
return 128000
|
|
90
|
+
|
|
91
|
+
|
|
71
92
|
# --- CONFIG SETTER STARTS HERE ---
|
|
72
93
|
def get_config_keys():
|
|
73
94
|
"""
|
|
74
95
|
Returns the list of all config keys currently in puppy.cfg,
|
|
75
|
-
plus certain preset expected keys (e.g. "yolo_mode", "model").
|
|
96
|
+
plus certain preset expected keys (e.g. "yolo_mode", "model", "compaction_strategy").
|
|
76
97
|
"""
|
|
77
|
-
default_keys = ["yolo_mode", "model"]
|
|
98
|
+
default_keys = ["yolo_mode", "model", "compaction_strategy"]
|
|
78
99
|
config = configparser.ConfigParser()
|
|
79
100
|
config.read(CONFIG_FILE)
|
|
80
101
|
keys = set(config[DEFAULT_SECTION].keys()) if DEFAULT_SECTION in config else set()
|
|
@@ -283,6 +304,17 @@ def normalize_command_history():
|
|
|
283
304
|
direct_console.print(f"[bold red]{error_msg}[/bold red]")
|
|
284
305
|
|
|
285
306
|
|
|
307
|
+
def get_user_agents_directory() -> str:
|
|
308
|
+
"""Get the user's agents directory path.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Path to the user's Code Puppy agents directory.
|
|
312
|
+
"""
|
|
313
|
+
# Ensure the agents directory exists
|
|
314
|
+
os.makedirs(AGENTS_DIR, exist_ok=True)
|
|
315
|
+
return AGENTS_DIR
|
|
316
|
+
|
|
317
|
+
|
|
286
318
|
def initialize_command_history_file():
|
|
287
319
|
"""Create the command history file if it doesn't exist.
|
|
288
320
|
Handles migration from the old history file location for backward compatibility.
|
|
@@ -354,30 +386,56 @@ def get_protected_token_count():
|
|
|
354
386
|
This is the number of tokens in recent messages that won't be summarized.
|
|
355
387
|
Defaults to 50000 if unset or misconfigured.
|
|
356
388
|
Configurable by 'protected_token_count' key.
|
|
389
|
+
Enforces that protected tokens don't exceed 75% of model context length.
|
|
357
390
|
"""
|
|
358
391
|
val = get_value("protected_token_count")
|
|
359
392
|
try:
|
|
360
|
-
|
|
393
|
+
# Get the model context length to enforce the 75% limit
|
|
394
|
+
model_context_length = get_model_context_length()
|
|
395
|
+
max_protected_tokens = int(model_context_length * 0.75)
|
|
396
|
+
|
|
397
|
+
# Parse the configured value
|
|
398
|
+
configured_value = int(val) if val else 50000
|
|
399
|
+
|
|
400
|
+
# Apply constraints: minimum 1000, maximum 75% of context length
|
|
401
|
+
return max(1000, min(configured_value, max_protected_tokens))
|
|
361
402
|
except (ValueError, TypeError):
|
|
362
|
-
return
|
|
403
|
+
# If parsing fails, return a reasonable default that respects the 75% limit
|
|
404
|
+
model_context_length = get_model_context_length()
|
|
405
|
+
max_protected_tokens = int(model_context_length * 0.75)
|
|
406
|
+
return min(50000, max_protected_tokens)
|
|
363
407
|
|
|
364
408
|
|
|
365
|
-
def
|
|
409
|
+
def get_compaction_threshold():
|
|
366
410
|
"""
|
|
367
|
-
Returns the user-configured
|
|
368
|
-
This is the proportion of model context that triggers
|
|
411
|
+
Returns the user-configured compaction threshold as a float between 0.0 and 1.0.
|
|
412
|
+
This is the proportion of model context that triggers compaction.
|
|
369
413
|
Defaults to 0.85 (85%) if unset or misconfigured.
|
|
370
|
-
Configurable by '
|
|
414
|
+
Configurable by 'compaction_threshold' key.
|
|
371
415
|
"""
|
|
372
|
-
val = get_value("
|
|
416
|
+
val = get_value("compaction_threshold")
|
|
373
417
|
try:
|
|
374
418
|
threshold = float(val) if val else 0.85
|
|
375
419
|
# Clamp between reasonable bounds
|
|
376
|
-
return max(0.
|
|
420
|
+
return max(0.8, min(0.95, threshold))
|
|
377
421
|
except (ValueError, TypeError):
|
|
378
422
|
return 0.85
|
|
379
423
|
|
|
380
424
|
|
|
425
|
+
def get_compaction_strategy() -> str:
|
|
426
|
+
"""
|
|
427
|
+
Returns the user-configured compaction strategy.
|
|
428
|
+
Options are 'summarization' or 'truncation'.
|
|
429
|
+
Defaults to 'summarization' if not set or misconfigured.
|
|
430
|
+
Configurable by 'compaction_strategy' key.
|
|
431
|
+
"""
|
|
432
|
+
val = get_value("compaction_strategy")
|
|
433
|
+
if val and val.lower() in ["summarization", "truncation"]:
|
|
434
|
+
return val.lower()
|
|
435
|
+
# Default to summarization
|
|
436
|
+
return "summarization"
|
|
437
|
+
|
|
438
|
+
|
|
381
439
|
def save_command_to_history(command: str):
|
|
382
440
|
"""Save a command to the history file with an ISO format timestamp.
|
|
383
441
|
|
code_puppy/main.py
CHANGED
|
@@ -321,12 +321,14 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
321
321
|
initial_command, usage_limits=get_custom_usage_limits()
|
|
322
322
|
)
|
|
323
323
|
finally:
|
|
324
|
-
set_message_history(
|
|
324
|
+
set_message_history(
|
|
325
|
+
prune_interrupted_tool_calls(get_message_history())
|
|
326
|
+
)
|
|
325
327
|
|
|
326
328
|
agent_response = response.output
|
|
327
329
|
|
|
328
330
|
emit_system_message(
|
|
329
|
-
f"\n[bold purple]AGENT RESPONSE: [/bold purple]\n{agent_response
|
|
331
|
+
f"\n[bold purple]AGENT RESPONSE: [/bold purple]\n{agent_response}"
|
|
330
332
|
)
|
|
331
333
|
new_msgs = response.all_messages()
|
|
332
334
|
message_history_accumulator(new_msgs)
|
|
@@ -371,8 +373,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
371
373
|
|
|
372
374
|
while True:
|
|
373
375
|
from code_puppy.messaging import emit_info
|
|
376
|
+
from code_puppy.agents.agent_manager import get_current_agent_config
|
|
377
|
+
|
|
378
|
+
# Get the custom prompt from the current agent, or use default
|
|
379
|
+
current_agent = get_current_agent_config()
|
|
380
|
+
user_prompt = current_agent.get_user_prompt() or "Enter your coding task:"
|
|
374
381
|
|
|
375
|
-
emit_info("[bold blue]
|
|
382
|
+
emit_info(f"[bold blue]{user_prompt}[/bold blue]")
|
|
376
383
|
|
|
377
384
|
try:
|
|
378
385
|
# Use prompt_toolkit for enhanced input with path completion
|
|
@@ -466,7 +473,9 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
466
473
|
usage_limits=get_custom_usage_limits(),
|
|
467
474
|
)
|
|
468
475
|
finally:
|
|
469
|
-
set_message_history(
|
|
476
|
+
set_message_history(
|
|
477
|
+
prune_interrupted_tool_calls(get_message_history())
|
|
478
|
+
)
|
|
470
479
|
|
|
471
480
|
# Create the task
|
|
472
481
|
agent_task = asyncio.create_task(run_agent_task())
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import queue
|
|
2
3
|
from typing import Any, List, Set, Tuple
|
|
3
4
|
|
|
4
5
|
import pydantic
|
|
@@ -7,7 +8,8 @@ from pydantic_ai.messages import ModelMessage, ModelRequest, TextPart, ToolCallP
|
|
|
7
8
|
from code_puppy.config import (
|
|
8
9
|
get_model_name,
|
|
9
10
|
get_protected_token_count,
|
|
10
|
-
|
|
11
|
+
get_compaction_threshold,
|
|
12
|
+
get_compaction_strategy,
|
|
11
13
|
)
|
|
12
14
|
from code_puppy.messaging import emit_error, emit_info, emit_warning
|
|
13
15
|
from code_puppy.model_factory import ModelFactory
|
|
@@ -87,6 +89,12 @@ def estimate_tokens_for_message(message: ModelMessage) -> int:
|
|
|
87
89
|
return max(1, total_tokens)
|
|
88
90
|
|
|
89
91
|
|
|
92
|
+
def filter_huge_messages(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
93
|
+
filtered = [m for m in messages if estimate_tokens_for_message(m) < 50000]
|
|
94
|
+
pruned = prune_interrupted_tool_calls(filtered)
|
|
95
|
+
return pruned
|
|
96
|
+
|
|
97
|
+
|
|
90
98
|
def split_messages_for_protected_summarization(
|
|
91
99
|
messages: List[ModelMessage],
|
|
92
100
|
) -> Tuple[List[ModelMessage], List[ModelMessage]]:
|
|
@@ -306,7 +314,8 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
306
314
|
status_bar.update_token_info(
|
|
307
315
|
total_current_tokens, model_max, proportion_used
|
|
308
316
|
)
|
|
309
|
-
except Exception:
|
|
317
|
+
except Exception as e:
|
|
318
|
+
emit_error(e)
|
|
310
319
|
# Fallback to chat message if status bar update fails
|
|
311
320
|
emit_info(
|
|
312
321
|
f"\n[bold white on blue] Tokens in context: {total_current_tokens}, total model capacity: {model_max}, proportion used: {proportion_used:.2f} [/bold white on blue] \n",
|
|
@@ -323,12 +332,26 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
323
332
|
emit_info(
|
|
324
333
|
f"\n[bold white on blue] Tokens in context: {total_current_tokens}, total model capacity: {model_max}, proportion used: {proportion_used:.2f} [/bold white on blue] \n"
|
|
325
334
|
)
|
|
335
|
+
# Get the configured compaction threshold
|
|
336
|
+
compaction_threshold = get_compaction_threshold()
|
|
337
|
+
|
|
338
|
+
# Get the configured compaction strategy
|
|
339
|
+
compaction_strategy = get_compaction_strategy()
|
|
340
|
+
|
|
341
|
+
if proportion_used > compaction_threshold:
|
|
342
|
+
if compaction_strategy == "truncation":
|
|
343
|
+
# Use truncation instead of summarization
|
|
344
|
+
protected_tokens = get_protected_token_count()
|
|
345
|
+
result_messages = truncation(
|
|
346
|
+
filter_huge_messages(messages), protected_tokens
|
|
347
|
+
)
|
|
348
|
+
summarized_messages = [] # No summarization in truncation mode
|
|
349
|
+
else:
|
|
350
|
+
# Default to summarization
|
|
351
|
+
result_messages, summarized_messages = summarize_messages(
|
|
352
|
+
filter_huge_messages(messages)
|
|
353
|
+
)
|
|
326
354
|
|
|
327
|
-
# Get the configured summarization threshold
|
|
328
|
-
summarization_threshold = get_summarization_threshold()
|
|
329
|
-
|
|
330
|
-
if proportion_used > summarization_threshold:
|
|
331
|
-
result_messages, summarized_messages = summarize_messages(messages)
|
|
332
355
|
final_token_count = sum(
|
|
333
356
|
estimate_tokens_for_message(msg) for msg in result_messages
|
|
334
357
|
)
|
|
@@ -360,6 +383,30 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
360
383
|
return messages
|
|
361
384
|
|
|
362
385
|
|
|
386
|
+
def truncation(
|
|
387
|
+
messages: List[ModelMessage], protected_tokens: int
|
|
388
|
+
) -> List[ModelMessage]:
|
|
389
|
+
emit_info("Truncating message history to manage token usage")
|
|
390
|
+
result = [messages[0]] # Always keep the first message (system prompt)
|
|
391
|
+
num_tokens = 0
|
|
392
|
+
stack = queue.LifoQueue()
|
|
393
|
+
|
|
394
|
+
# Put messages in reverse order (most recent first) into the stack
|
|
395
|
+
# but break when we exceed protected_tokens
|
|
396
|
+
for idx, msg in enumerate(reversed(messages[1:])): # Skip the first message
|
|
397
|
+
num_tokens += estimate_tokens_for_message(msg)
|
|
398
|
+
if num_tokens > protected_tokens:
|
|
399
|
+
break
|
|
400
|
+
stack.put(msg)
|
|
401
|
+
|
|
402
|
+
# Pop messages from stack to get them in chronological order
|
|
403
|
+
while not stack.empty():
|
|
404
|
+
result.append(stack.get())
|
|
405
|
+
|
|
406
|
+
result = prune_interrupted_tool_calls(result)
|
|
407
|
+
return result
|
|
408
|
+
|
|
409
|
+
|
|
363
410
|
def message_history_accumulator(messages: List[Any]):
|
|
364
411
|
_message_history = get_message_history()
|
|
365
412
|
message_history_hashes = set([hash_message(m) for m in _message_history])
|
code_puppy/tools/__init__.py
CHANGED
|
@@ -1,10 +1,63 @@
|
|
|
1
|
-
from code_puppy.
|
|
2
|
-
from code_puppy.tools.
|
|
3
|
-
|
|
1
|
+
from code_puppy.messaging import emit_warning
|
|
2
|
+
from code_puppy.tools.command_runner import (
|
|
3
|
+
register_agent_run_shell_command,
|
|
4
|
+
register_agent_share_your_reasoning,
|
|
5
|
+
)
|
|
6
|
+
from code_puppy.tools.file_modifications import register_edit_file, register_delete_file
|
|
7
|
+
from code_puppy.tools.file_operations import (
|
|
8
|
+
register_list_files,
|
|
9
|
+
register_read_file,
|
|
10
|
+
register_grep,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Map of tool names to their individual registration functions
|
|
15
|
+
TOOL_REGISTRY = {
|
|
16
|
+
# File Operations
|
|
17
|
+
"list_files": register_list_files,
|
|
18
|
+
"read_file": register_read_file,
|
|
19
|
+
"grep": register_grep,
|
|
20
|
+
# File Modifications
|
|
21
|
+
"edit_file": register_edit_file,
|
|
22
|
+
"delete_file": register_delete_file,
|
|
23
|
+
# Command Runner
|
|
24
|
+
"agent_run_shell_command": register_agent_run_shell_command,
|
|
25
|
+
"agent_share_your_reasoning": register_agent_share_your_reasoning,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_tools_for_agent(agent, tool_names: list[str]):
|
|
30
|
+
"""Register specific tools for an agent based on tool names.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
agent: The agent to register tools to.
|
|
34
|
+
tool_names: List of tool names to register.
|
|
35
|
+
"""
|
|
36
|
+
for tool_name in tool_names:
|
|
37
|
+
if tool_name not in TOOL_REGISTRY:
|
|
38
|
+
# Skip unknown tools with a warning instead of failing
|
|
39
|
+
emit_warning(f"Warning: Unknown tool '{tool_name}' requested, skipping...")
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# Register the individual tool
|
|
43
|
+
register_func = TOOL_REGISTRY[tool_name]
|
|
44
|
+
register_func(agent)
|
|
4
45
|
|
|
5
46
|
|
|
6
47
|
def register_all_tools(agent):
|
|
7
|
-
"""Register all available tools to the provided agent.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
48
|
+
"""Register all available tools to the provided agent.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
agent: The agent to register tools to.
|
|
52
|
+
"""
|
|
53
|
+
all_tools = list(TOOL_REGISTRY.keys())
|
|
54
|
+
register_tools_for_agent(agent, all_tools)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_available_tool_names() -> list[str]:
|
|
58
|
+
"""Get list of all available tool names.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of all tool names that can be registered.
|
|
62
|
+
"""
|
|
63
|
+
return list(TOOL_REGISTRY.keys())
|
|
@@ -12,6 +12,7 @@ from pydantic_ai import RunContext
|
|
|
12
12
|
from rich.markdown import Markdown
|
|
13
13
|
from rich.text import Text
|
|
14
14
|
|
|
15
|
+
from code_puppy.callbacks import on_run_shell_command
|
|
15
16
|
from code_puppy.messaging import (
|
|
16
17
|
emit_divider,
|
|
17
18
|
emit_error,
|
|
@@ -543,7 +544,8 @@ def register_command_runner_tools(agent):
|
|
|
543
544
|
This tool can execute arbitrary shell commands. Exercise caution when
|
|
544
545
|
running untrusted commands, especially those that modify system state.
|
|
545
546
|
"""
|
|
546
|
-
|
|
547
|
+
result = run_shell_command(context, command, cwd, timeout)
|
|
548
|
+
on_run_shell_command(result)
|
|
547
549
|
|
|
548
550
|
@agent.tool
|
|
549
551
|
def agent_share_your_reasoning(
|
|
@@ -588,3 +590,100 @@ def register_command_runner_tools(agent):
|
|
|
588
590
|
- When encountering unexpected situations
|
|
589
591
|
"""
|
|
590
592
|
return share_your_reasoning(context, reasoning, next_steps)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def register_agent_run_shell_command(agent):
|
|
596
|
+
"""Register only the agent_run_shell_command tool."""
|
|
597
|
+
|
|
598
|
+
@agent.tool(strict=False)
|
|
599
|
+
def agent_run_shell_command(
|
|
600
|
+
context: RunContext, command: str = "", cwd: str = None, timeout: int = 60
|
|
601
|
+
) -> ShellCommandOutput:
|
|
602
|
+
"""Execute a shell command with comprehensive monitoring and safety features.
|
|
603
|
+
|
|
604
|
+
This tool provides robust shell command execution with streaming output,
|
|
605
|
+
timeout handling, user confirmation (when not in yolo mode), and proper
|
|
606
|
+
process lifecycle management. Commands are executed in a controlled
|
|
607
|
+
environment with cross-platform process group handling.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
command: The shell command to execute. Cannot be empty or whitespace-only.
|
|
611
|
+
cwd: Working directory for command execution. If None,
|
|
612
|
+
uses the current working directory. Defaults to None.
|
|
613
|
+
timeout: Inactivity timeout in seconds. If no output is
|
|
614
|
+
produced for this duration, the process will be terminated.
|
|
615
|
+
Defaults to 60 seconds.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
ShellCommandOutput: A structured response containing:
|
|
619
|
+
- success (bool): True if command executed successfully (exit code 0)
|
|
620
|
+
- command (str | None): The executed command string
|
|
621
|
+
- error (str | None): Error message if execution failed
|
|
622
|
+
- stdout (str | None): Standard output from the command (last 1000 lines)
|
|
623
|
+
- stderr (str | None): Standard error from the command (last 1000 lines)
|
|
624
|
+
- exit_code (int | None): Process exit code
|
|
625
|
+
- execution_time (float | None): Total execution time in seconds
|
|
626
|
+
- timeout (bool | None): True if command was terminated due to timeout
|
|
627
|
+
- user_interrupted (bool | None): True if user killed the process
|
|
628
|
+
|
|
629
|
+
Examples:
|
|
630
|
+
>>> # Basic command execution
|
|
631
|
+
>>> result = agent_run_shell_command(ctx, "ls -la")
|
|
632
|
+
>>> print(result.stdout)
|
|
633
|
+
|
|
634
|
+
>>> # Command with working directory
|
|
635
|
+
>>> result = agent_run_shell_command(ctx, "npm test", "/path/to/project")
|
|
636
|
+
>>> if result.success:
|
|
637
|
+
... print("Tests passed!")
|
|
638
|
+
|
|
639
|
+
>>> # Command with custom timeout
|
|
640
|
+
>>> result = agent_run_shell_command(ctx, "long_running_command", timeout=300)
|
|
641
|
+
>>> if result.timeout:
|
|
642
|
+
... print("Command timed out")
|
|
643
|
+
|
|
644
|
+
Warning:
|
|
645
|
+
This tool can execute arbitrary shell commands. Exercise caution when
|
|
646
|
+
running untrusted commands, especially those that modify system state.
|
|
647
|
+
"""
|
|
648
|
+
return run_shell_command(context, command, cwd, timeout)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def register_agent_share_your_reasoning(agent):
|
|
652
|
+
"""Register only the agent_share_your_reasoning tool."""
|
|
653
|
+
|
|
654
|
+
@agent.tool(strict=False)
|
|
655
|
+
def agent_share_your_reasoning(
|
|
656
|
+
context: RunContext, reasoning: str = "", next_steps: str | None = None
|
|
657
|
+
) -> ReasoningOutput:
|
|
658
|
+
"""Share the agent's current reasoning and planned next steps with the user.
|
|
659
|
+
|
|
660
|
+
This tool provides transparency into the agent's decision-making process
|
|
661
|
+
by displaying the current reasoning and upcoming actions in a formatted,
|
|
662
|
+
user-friendly manner. It's essential for building trust and understanding
|
|
663
|
+
between the agent and user.
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
reasoning: The agent's current thought process, analysis, or
|
|
667
|
+
reasoning for the current situation. This should be clear,
|
|
668
|
+
comprehensive, and explain the 'why' behind decisions.
|
|
669
|
+
next_steps: Planned upcoming actions or steps
|
|
670
|
+
the agent intends to take. Can be None if no specific next steps
|
|
671
|
+
are determined. Defaults to None.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
ReasoningOutput: A simple response object containing:
|
|
675
|
+
- success (bool): Always True, indicating the reasoning was shared
|
|
676
|
+
|
|
677
|
+
Examples:
|
|
678
|
+
>>> reasoning = "I need to analyze the codebase structure first"
|
|
679
|
+
>>> next_steps = "First, I'll list the directory contents, then read key files"
|
|
680
|
+
>>> result = agent_share_your_reasoning(ctx, reasoning, next_steps)
|
|
681
|
+
|
|
682
|
+
Best Practice:
|
|
683
|
+
Use this tool frequently to maintain transparency. Call it:
|
|
684
|
+
- Before starting complex operations
|
|
685
|
+
- When changing strategy or approach
|
|
686
|
+
- To explain why certain decisions are being made
|
|
687
|
+
- When encountering unexpected situations
|
|
688
|
+
"""
|
|
689
|
+
return share_your_reasoning(context, reasoning, next_steps)
|