tinyagent-py 0.0.17rc1__tar.gz → 0.0.19__tar.gz
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.
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/PKG-INFO +2 -2
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/pyproject.toml +2 -2
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/tiny_code_agent.py +474 -298
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/mcp_client.py +27 -3
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/tiny_agent.py +30 -4
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent_py.egg-info/PKG-INFO +2 -2
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/LICENSE +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/README.md +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/setup.cfg +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/__init__.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/__init__.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/example.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/helper.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/modal_sandbox.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/providers/__init__.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/providers/base.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/providers/modal_provider.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/providers/seatbelt_provider.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/safety.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/tools/__init__.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/tools/example_tools.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/utils.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/hooks/__init__.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/hooks/gradio_callback.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/hooks/jupyter_notebook_callback.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/hooks/logging_manager.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/hooks/rich_code_ui_callback.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/hooks/rich_ui_callback.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/hooks/token_tracker.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/memory_manager.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/prompts/code_agent.yaml +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/prompts/summarize.yaml +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/prompts/truncation.yaml +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/storage/__init__.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/storage/base.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/storage/json_file_storage.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/storage/postgres_storage.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/storage/redis_storage.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/storage/sqlite_storage.py +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent_py.egg-info/SOURCES.txt +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent_py.egg-info/dependency_links.txt +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent_py.egg-info/requires.txt +0 -0
- {tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent_py.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: tinyagent-py
|
3
|
-
Version: 0.0.
|
4
|
-
Summary: TinyAgent with MCP Client,
|
3
|
+
Version: 0.0.19
|
4
|
+
Summary: TinyAgent with MCP Client, CodeAgent (Thinking, Planning, Interactive Python and Shell with high variaety of sandboxing(seatbelt, Modal, E2B, docker, etc) ), and Extendable Hooks, Tiny but powerful
|
5
5
|
Author-email: Mahdi Golchin <golchin@askdev.ai>
|
6
6
|
Project-URL: Homepage, https://github.com/askbudi/tinyagent
|
7
7
|
Project-URL: Bug Tracker, https://github.com/askbudi/tinyagent/issues
|
@@ -12,8 +12,8 @@ tinyagent = ["prompts/*.yaml"]
|
|
12
12
|
|
13
13
|
[project]
|
14
14
|
name = "tinyagent-py"
|
15
|
-
version = "0.0.
|
16
|
-
description = "TinyAgent with MCP Client,
|
15
|
+
version = "0.0.19"
|
16
|
+
description = "TinyAgent with MCP Client, CodeAgent (Thinking, Planning, Interactive Python and Shell with high variaety of sandboxing(seatbelt, Modal, E2B, docker, etc) ), and Extendable Hooks, Tiny but powerful"
|
17
17
|
readme = "README.md"
|
18
18
|
authors = [
|
19
19
|
{name="Mahdi Golchin", email="golchin@askdev.ai"}
|
@@ -31,7 +31,7 @@ DEFAULT_SUMMARY_SYSTEM_PROMPT = (
|
|
31
31
|
|
32
32
|
)
|
33
33
|
|
34
|
-
class TinyCodeAgent:
|
34
|
+
class TinyCodeAgent(TinyAgent):
|
35
35
|
"""
|
36
36
|
A TinyAgent specialized for code execution tasks.
|
37
37
|
|
@@ -69,6 +69,8 @@ class TinyCodeAgent:
|
|
69
69
|
ui: Optional[str] = None,
|
70
70
|
truncation_config: Optional[Dict[str, Any]] = None,
|
71
71
|
auto_git_checkpoint: bool = False,
|
72
|
+
enable_python_tool: bool = True,
|
73
|
+
enable_shell_tool: bool = True,
|
72
74
|
**agent_kwargs
|
73
75
|
):
|
74
76
|
"""
|
@@ -95,6 +97,8 @@ class TinyCodeAgent:
|
|
95
97
|
ui: The user interface callback to use ('rich', 'jupyter', or None).
|
96
98
|
truncation_config: Configuration for output truncation (max_tokens, max_lines)
|
97
99
|
auto_git_checkpoint: If True, automatically create git checkpoints after each successful shell command
|
100
|
+
enable_python_tool: If True (default), enable the run_python tool for Python code execution
|
101
|
+
enable_shell_tool: If True (default), enable the bash tool for shell command execution
|
98
102
|
**agent_kwargs: Additional arguments passed to TinyAgent
|
99
103
|
|
100
104
|
Provider Config Options:
|
@@ -136,6 +140,10 @@ class TinyCodeAgent:
|
|
136
140
|
self.default_workdir = default_workdir or os.getcwd() # Default to current working directory if not specified
|
137
141
|
self.auto_git_checkpoint = auto_git_checkpoint # Enable/disable automatic git checkpoints
|
138
142
|
|
143
|
+
# Store tool enablement flags
|
144
|
+
self._python_tool_enabled = enable_python_tool
|
145
|
+
self._shell_tool_enabled = enable_shell_tool
|
146
|
+
|
139
147
|
# Set up truncation configuration with defaults
|
140
148
|
default_truncation = {
|
141
149
|
"max_tokens": 3000,
|
@@ -158,8 +166,8 @@ class TinyCodeAgent:
|
|
158
166
|
|
159
167
|
self.summary_config = summary_config or {}
|
160
168
|
|
161
|
-
#
|
162
|
-
|
169
|
+
# Initialize the parent TinyAgent with the built system prompt
|
170
|
+
super().__init__(
|
163
171
|
model=model,
|
164
172
|
api_key=api_key,
|
165
173
|
system_prompt=self.system_prompt,
|
@@ -173,7 +181,7 @@ class TinyCodeAgent:
|
|
173
181
|
|
174
182
|
# Add LLM tools (not code tools - those go to the provider)
|
175
183
|
if self.tools:
|
176
|
-
self.
|
184
|
+
self.add_tools(self.tools)
|
177
185
|
|
178
186
|
# Add the selected UI callback
|
179
187
|
if ui:
|
@@ -394,221 +402,237 @@ class TinyCodeAgent:
|
|
394
402
|
|
395
403
|
def _setup_code_execution_tools(self):
|
396
404
|
"""Set up the code execution tools using the code provider."""
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
405
|
+
# Clear existing default tools to avoid duplicates
|
406
|
+
# Remove existing default tools by name if they exist
|
407
|
+
if hasattr(self, 'available_tools'):
|
408
|
+
tools_to_remove = []
|
409
|
+
for tool_dict in self.available_tools:
|
410
|
+
if 'function' in tool_dict and 'name' in tool_dict['function']:
|
411
|
+
if tool_dict['function']['name'] in ['run_python', 'bash']:
|
412
|
+
tools_to_remove.append(tool_dict)
|
413
|
+
|
414
|
+
# Remove the tools from available_tools
|
415
|
+
for tool_dict in tools_to_remove:
|
416
|
+
self.available_tools.remove(tool_dict)
|
417
|
+
|
418
|
+
if self._python_tool_enabled:
|
419
|
+
@tool(name="run_python", description=dedent("""
|
420
|
+
This tool receives Python code and executes it in a sandboxed environment.
|
421
|
+
During each intermediate step, you can use 'print()' to save important information.
|
422
|
+
These print outputs will appear in the 'Observation:' field for the next step.
|
401
423
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
424
|
+
Args:
|
425
|
+
code_lines: list[str]: The Python code to execute as a list of strings.
|
426
|
+
Your code should include all necessary steps for successful execution,
|
427
|
+
cover edge cases, and include error handling.
|
428
|
+
Each line should be an independent line of code.
|
407
429
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
430
|
+
Returns:
|
431
|
+
Status of code execution or error message.
|
432
|
+
"""))
|
433
|
+
async def run_python(code_lines: List[str], timeout: int = 120) -> str:
|
434
|
+
"""Execute Python code using the configured provider."""
|
435
|
+
try:
|
436
|
+
# Before execution, ensure provider has the latest user variables
|
437
|
+
if self.user_variables:
|
438
|
+
self.code_provider.set_user_variables(self.user_variables)
|
439
|
+
|
440
|
+
result = await self.code_provider.execute_python(code_lines, timeout)
|
417
441
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
# This ensures they stay in sync
|
422
|
-
self.user_variables = self.code_provider.get_user_variables()
|
423
|
-
|
424
|
-
# Apply truncation if enabled
|
425
|
-
if self.truncation_config["enabled"] and "printed_output" in result:
|
426
|
-
truncated_output, is_truncated, original_tokens, original_lines = truncate_output(
|
427
|
-
result["printed_output"],
|
428
|
-
max_tokens=self.truncation_config["max_tokens"],
|
429
|
-
max_lines=self.truncation_config["max_lines"]
|
430
|
-
)
|
442
|
+
# After execution, update TinyCodeAgent's user_variables from the provider
|
443
|
+
# This ensures they stay in sync
|
444
|
+
self.user_variables = self.code_provider.get_user_variables()
|
431
445
|
|
432
|
-
if
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
self.truncation_config["max_lines"],
|
439
|
-
"python_output"
|
446
|
+
# Apply truncation if enabled
|
447
|
+
if self.truncation_config["enabled"] and "printed_output" in result:
|
448
|
+
truncated_output, is_truncated, original_tokens, original_lines = truncate_output(
|
449
|
+
result["printed_output"],
|
450
|
+
max_tokens=self.truncation_config["max_tokens"],
|
451
|
+
max_lines=self.truncation_config["max_lines"]
|
440
452
|
)
|
453
|
+
|
454
|
+
if is_truncated:
|
455
|
+
result["printed_output"] = format_truncation_message(
|
456
|
+
truncated_output,
|
457
|
+
is_truncated,
|
458
|
+
original_tokens,
|
459
|
+
original_lines,
|
460
|
+
self.truncation_config["max_lines"],
|
461
|
+
"python_output"
|
462
|
+
)
|
463
|
+
|
464
|
+
return json.dumps(result)
|
465
|
+
except Exception as e:
|
466
|
+
print("!"*100)
|
467
|
+
COLOR = {
|
468
|
+
"RED": "\033[91m",
|
469
|
+
"ENDC": "\033[0m",
|
470
|
+
}
|
471
|
+
print(f"{COLOR['RED']}{str(e)}{COLOR['ENDC']}")
|
472
|
+
print(f"{COLOR['RED']}{traceback.format_exc()}{COLOR['ENDC']}")
|
473
|
+
print("!"*100)
|
474
|
+
|
475
|
+
# Even after an exception, update user_variables from the provider
|
476
|
+
# This ensures any variables that were successfully created/modified are preserved
|
477
|
+
self.user_variables = self.code_provider.get_user_variables()
|
478
|
+
|
479
|
+
return json.dumps({"error": f"Error executing code: {str(e)}"})
|
480
|
+
|
481
|
+
self.add_tool(run_python)
|
482
|
+
|
483
|
+
if self._shell_tool_enabled:
|
484
|
+
@tool(name="bash", description=dedent("""
|
485
|
+
This tool executes shell commands securely in a sandboxed environment.
|
486
|
+
Only a limited set of safe commands are allowed for security reasons.
|
487
|
+
Before executing the command, please follow these steps:
|
488
|
+
|
489
|
+
1. Directory Verification:
|
490
|
+
- If the command will create new directories or files, first use ls to verify the parent directory exists and is the correct location
|
491
|
+
- For example, before running "mkdir foo/bar", first use ls to check that "foo" exists and is the intended parent directory
|
492
|
+
|
493
|
+
2. Command Execution:
|
494
|
+
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
495
|
+
- Examples of proper quoting:
|
496
|
+
- cd "/Users/name/My Documents" (correct)
|
497
|
+
- cd /Users/name/My Documents (incorrect - will fail)
|
498
|
+
- python "/path/with spaces/script.py" (correct)
|
499
|
+
- python /path/with spaces/script.py (incorrect - will fail)
|
500
|
+
- After ensuring proper quoting, execute the command.
|
501
|
+
- Capture the output of the command.
|
502
|
+
|
503
|
+
Usage notes:
|
504
|
+
- The command argument is required.
|
505
|
+
- You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance.
|
506
|
+
- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 120000ms (2 minutes).
|
507
|
+
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
|
508
|
+
- If the output is too large, it will be truncated before being returned to you.
|
441
509
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
print("!"*100)
|
452
|
-
|
453
|
-
# Even after an exception, update user_variables from the provider
|
454
|
-
# This ensures any variables that were successfully created/modified are preserved
|
455
|
-
self.user_variables = self.code_provider.get_user_variables()
|
456
|
-
|
457
|
-
return json.dumps({"error": f"Error executing code: {str(e)}"})
|
458
|
-
|
459
|
-
@tool(name="bash", description=dedent("""
|
460
|
-
This tool executes shell commands securely in a sandboxed environment.
|
461
|
-
Only a limited set of safe commands are allowed for security reasons.
|
462
|
-
Before executing the command, please follow these steps:
|
463
|
-
|
464
|
-
1. Directory Verification:
|
465
|
-
- If the command will create new directories or files, first use ls to verify the parent directory exists and is the correct location
|
466
|
-
- For example, before running "mkdir foo/bar", first use ls to check that "foo" exists and is the intended parent directory
|
467
|
-
|
468
|
-
2. Command Execution:
|
469
|
-
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
|
470
|
-
- Examples of proper quoting:
|
471
|
-
- cd "/Users/name/My Documents" (correct)
|
472
|
-
- cd /Users/name/My Documents (incorrect - will fail)
|
473
|
-
- python "/path/with spaces/script.py" (correct)
|
474
|
-
- python /path/with spaces/script.py (incorrect - will fail)
|
475
|
-
- After ensuring proper quoting, execute the command.
|
476
|
-
- Capture the output of the command.
|
477
|
-
|
478
|
-
Usage notes:
|
479
|
-
- The command argument is required.
|
480
|
-
- You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance.
|
481
|
-
- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 120000ms (2 minutes).
|
482
|
-
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
|
483
|
-
- If the output is too large, it will be truncated before being returned to you.
|
510
|
+
- If you _still_ need to run `grep`, STOP. ALWAYS USE ripgrep at `rg` first, which all ${PRODUCT_NAME} users have pre-installed.
|
511
|
+
" - When issuing multiple commands, use the ; or && operator to separate them. DO NOT use newlines (newlines are ok in quoted strings).\n" +
|
512
|
+
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly requests it.
|
513
|
+
<good-example>
|
514
|
+
pytest /foo/bar/tests
|
515
|
+
</good-example>
|
516
|
+
<bad-example>
|
517
|
+
cd /foo/bar && pytest tests
|
518
|
+
</bad-example>
|
484
519
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
command=["sh", "-c", "echo 'Your content' > filename.txt"],
|
532
|
-
absolute_workdir="/working/directory",
|
533
|
-
description="Write content to file"
|
534
|
-
)
|
535
|
-
```
|
536
|
-
|
537
|
-
**Never:**
|
538
|
-
- Call bash() without all required parameters
|
539
|
-
- Use complex nested quotes without testing
|
540
|
-
- Try to create large files in a single command (break into parts)
|
520
|
+
## IMPORTANT: Bash Tool Usage
|
521
|
+
|
522
|
+
When using the bash tool, you MUST provide all required parameters:
|
523
|
+
|
524
|
+
**Correct Usage:**
|
525
|
+
```
|
526
|
+
bash(
|
527
|
+
command=["ls", "-la"],
|
528
|
+
absolute_workdir="/path/to/directory",
|
529
|
+
description="List files in directory"
|
530
|
+
)
|
531
|
+
```
|
532
|
+
|
533
|
+
**For creating files with content, use these safe patterns:**
|
534
|
+
|
535
|
+
1. **Simple file creation:**
|
536
|
+
```
|
537
|
+
bash(
|
538
|
+
command=["touch", "filename.txt"],
|
539
|
+
absolute_workdir="/working/directory",
|
540
|
+
description="Create empty file"
|
541
|
+
)
|
542
|
+
```
|
543
|
+
|
544
|
+
2. **Write content using cat and heredoc:**
|
545
|
+
```
|
546
|
+
bash(
|
547
|
+
command=["sh", "-c", "cat > filename.txt << 'EOF'\nYour content here\nEOF"],
|
548
|
+
absolute_workdir="/working/directory",
|
549
|
+
description="Create file with content"
|
550
|
+
)
|
551
|
+
```
|
552
|
+
|
553
|
+
3. **Write content using echo:**
|
554
|
+
```
|
555
|
+
bash(
|
556
|
+
command=["sh", "-c", "echo 'Your content' > filename.txt"],
|
557
|
+
absolute_workdir="/working/directory",
|
558
|
+
description="Write content to file"
|
559
|
+
)
|
560
|
+
```
|
561
|
+
|
562
|
+
**Never:**
|
563
|
+
- Call bash() without all required parameters
|
564
|
+
- Use complex nested quotes without testing
|
565
|
+
- Try to create large files in a single command (break into parts)
|
541
566
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
absolute_workdir: str: could be presented workdir in the system prompt or one of the subdirectories of the workdir. This is the only allowed path, and accessing else will result in an error.
|
546
|
-
description: str: A clear, concise description of what this command does in 5-10 words.
|
547
|
-
timeout: int: Maximum execution time in seconds (default: 60).
|
548
|
-
Returns:
|
549
|
-
Dictionary with stdout, stderr, and exit_code from the command execution.
|
550
|
-
If the command is rejected for security reasons, stderr will contain the reason.
|
551
|
-
The stdout will include information about which working directory was used.
|
552
|
-
"""))
|
553
|
-
async def run_shell(command: List[str], absolute_workdir: str, description: str, timeout: int = 60) -> str:
|
554
|
-
"""Execute shell commands securely using the configured provider."""
|
555
|
-
try:
|
556
|
-
# Use the default working directory if none is specified
|
557
|
-
effective_workdir = absolute_workdir or self.default_workdir
|
558
|
-
print(f" {command} to {description}")
|
559
|
-
# Verify that the working directory exists
|
560
|
-
if effective_workdir and not os.path.exists(effective_workdir):
|
561
|
-
return json.dumps({
|
562
|
-
"stdout": "",
|
563
|
-
"stderr": f"Working directory does not exist: {effective_workdir}",
|
564
|
-
"exit_code": 1
|
565
|
-
})
|
566
|
-
|
567
|
-
if effective_workdir and not os.path.isdir(effective_workdir):
|
568
|
-
return json.dumps({
|
569
|
-
"stdout": "",
|
570
|
-
"stderr": f"Path is not a directory: {effective_workdir}",
|
571
|
-
"exit_code": 1
|
572
|
-
})
|
573
|
-
|
574
|
-
result = await self.code_provider.execute_shell(command, timeout, effective_workdir)
|
575
|
-
|
576
|
-
# Apply truncation if enabled
|
577
|
-
if self.truncation_config["enabled"] and "stdout" in result and result["stdout"]:
|
578
|
-
truncated_output, is_truncated, original_tokens, original_lines = truncate_output(
|
579
|
-
result["stdout"],
|
580
|
-
max_tokens=self.truncation_config["max_tokens"],
|
581
|
-
max_lines=self.truncation_config["max_lines"]
|
582
|
-
)
|
567
|
+
Args:
|
568
|
+
command: list[str]: The shell command to execute as a list of strings. Example: ["ls", "-la"] or ["cat", "file.txt"]
|
583
569
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
570
|
+
absolute_workdir: str: could be presented workdir in the system prompt or one of the subdirectories of the workdir. This is the only allowed path, and accessing else will result in an error.
|
571
|
+
description: str: A clear, concise description of what this command does in 5-10 words.
|
572
|
+
timeout: int: Maximum execution time in seconds (default: 60).
|
573
|
+
Returns:
|
574
|
+
Dictionary with stdout, stderr, and exit_code from the command execution.
|
575
|
+
If the command is rejected for security reasons, stderr will contain the reason.
|
576
|
+
The stdout will include information about which working directory was used.
|
577
|
+
"""))
|
578
|
+
async def run_shell(command: List[str], absolute_workdir: str, description: str, timeout: int = 60) -> str:
|
579
|
+
"""Execute shell commands securely using the configured provider."""
|
580
|
+
try:
|
581
|
+
# Use the default working directory if none is specified
|
582
|
+
effective_workdir = absolute_workdir or self.default_workdir
|
583
|
+
print(f" {command} to {description}")
|
584
|
+
# Verify that the working directory exists
|
585
|
+
if effective_workdir and not os.path.exists(effective_workdir):
|
586
|
+
return json.dumps({
|
587
|
+
"stdout": "",
|
588
|
+
"stderr": f"Working directory does not exist: {effective_workdir}",
|
589
|
+
"exit_code": 1
|
590
|
+
})
|
591
|
+
|
592
|
+
if effective_workdir and not os.path.isdir(effective_workdir):
|
593
|
+
return json.dumps({
|
594
|
+
"stdout": "",
|
595
|
+
"stderr": f"Path is not a directory: {effective_workdir}",
|
596
|
+
"exit_code": 1
|
597
|
+
})
|
598
|
+
|
599
|
+
result = await self.code_provider.execute_shell(command, timeout, effective_workdir)
|
600
|
+
|
601
|
+
# Apply truncation if enabled
|
602
|
+
if self.truncation_config["enabled"] and "stdout" in result and result["stdout"]:
|
603
|
+
truncated_output, is_truncated, original_tokens, original_lines = truncate_output(
|
604
|
+
result["stdout"],
|
605
|
+
max_tokens=self.truncation_config["max_tokens"],
|
606
|
+
max_lines=self.truncation_config["max_lines"]
|
592
607
|
)
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
608
|
+
|
609
|
+
if is_truncated:
|
610
|
+
result["stdout"] = format_truncation_message(
|
611
|
+
truncated_output,
|
612
|
+
is_truncated,
|
613
|
+
original_tokens,
|
614
|
+
original_lines,
|
615
|
+
self.truncation_config["max_lines"],
|
616
|
+
"bash_output"
|
617
|
+
)
|
618
|
+
|
619
|
+
# Create a git checkpoint if auto_git_checkpoint is enabled
|
620
|
+
if self.auto_git_checkpoint and result.get("exit_code", 1) == 0:
|
621
|
+
checkpoint_result = await self._create_git_checkpoint(command, description, effective_workdir)
|
622
|
+
self.log_manager.get_logger(__name__).info(f"Git checkpoint {effective_workdir} result: {checkpoint_result}")
|
623
|
+
|
624
|
+
return json.dumps(result)
|
625
|
+
except Exception as e:
|
626
|
+
COLOR = {
|
627
|
+
"RED": "\033[91m",
|
628
|
+
"ENDC": "\033[0m",
|
629
|
+
}
|
630
|
+
print(f"{COLOR['RED']}{str(e)}{COLOR['ENDC']}")
|
631
|
+
print(f"{COLOR['RED']}{traceback.format_exc()}{COLOR['ENDC']}")
|
632
|
+
|
633
|
+
return json.dumps({"error": f"Error executing shell command: {str(e)}"})
|
634
|
+
|
635
|
+
self.add_tool(run_shell)
|
612
636
|
|
613
637
|
async def _create_git_checkpoint(self, command: List[str], description: str, workdir: str) -> Dict[str, Any]:
|
614
638
|
"""
|
@@ -706,50 +730,17 @@ class TinyCodeAgent:
|
|
706
730
|
"""
|
707
731
|
return self.default_workdir
|
708
732
|
|
709
|
-
|
710
|
-
"""
|
711
|
-
Run the code agent with the given input.
|
712
|
-
|
713
|
-
Args:
|
714
|
-
user_input: The user's request or question
|
715
|
-
max_turns: Maximum number of conversation turns
|
716
|
-
|
717
|
-
Returns:
|
718
|
-
The agent's response
|
719
|
-
"""
|
720
|
-
return await self.agent.run(user_input, max_turns)
|
733
|
+
|
721
734
|
|
722
|
-
|
723
|
-
"""
|
724
|
-
Resume the conversation without adding a new user message.
|
725
|
-
|
726
|
-
This method continues the conversation from the current state,
|
727
|
-
allowing the agent to process the existing conversation history
|
728
|
-
and potentially take additional actions.
|
729
|
-
|
730
|
-
Args:
|
731
|
-
max_turns: Maximum number of conversation turns
|
732
|
-
|
733
|
-
Returns:
|
734
|
-
The agent's response
|
735
|
-
"""
|
736
|
-
return await self.agent.resume(max_turns)
|
735
|
+
|
737
736
|
|
738
|
-
|
739
|
-
"""Connect to an MCP server."""
|
740
|
-
return await self.agent.connect_to_server(command, args, **kwargs)
|
737
|
+
|
741
738
|
|
742
|
-
|
743
|
-
"""Add a callback to the agent."""
|
744
|
-
self.agent.add_callback(callback)
|
739
|
+
|
745
740
|
|
746
|
-
|
747
|
-
"""Add a tool to the agent (LLM tool)."""
|
748
|
-
self.agent.add_tool(tool)
|
741
|
+
|
749
742
|
|
750
|
-
|
751
|
-
"""Add multiple tools to the agent (LLM tools)."""
|
752
|
-
self.agent.add_tools(tools)
|
743
|
+
|
753
744
|
|
754
745
|
def add_code_tool(self, tool):
|
755
746
|
"""
|
@@ -763,8 +754,8 @@ class TinyCodeAgent:
|
|
763
754
|
self.code_provider.set_code_tools(self.code_tools)
|
764
755
|
# Rebuild system prompt to include new code tools info
|
765
756
|
self.system_prompt = self._build_system_prompt()
|
766
|
-
# Update the
|
767
|
-
self.
|
757
|
+
# Update the system prompt in messages
|
758
|
+
self._update_system_prompt()
|
768
759
|
|
769
760
|
def add_code_tools(self, tools: List[Any]):
|
770
761
|
"""
|
@@ -778,8 +769,8 @@ class TinyCodeAgent:
|
|
778
769
|
self.code_provider.set_code_tools(self.code_tools)
|
779
770
|
# Rebuild system prompt to include new code tools info
|
780
771
|
self.system_prompt = self._build_system_prompt()
|
781
|
-
# Update the
|
782
|
-
self.
|
772
|
+
# Update the system prompt in messages
|
773
|
+
self._update_system_prompt()
|
783
774
|
|
784
775
|
def remove_code_tool(self, tool_name: str):
|
785
776
|
"""
|
@@ -795,8 +786,8 @@ class TinyCodeAgent:
|
|
795
786
|
self.code_provider.set_code_tools(self.code_tools)
|
796
787
|
# Rebuild system prompt
|
797
788
|
self.system_prompt = self._build_system_prompt()
|
798
|
-
# Update the
|
799
|
-
self.
|
789
|
+
# Update the system prompt in messages
|
790
|
+
self._update_system_prompt()
|
800
791
|
|
801
792
|
def get_code_tools(self) -> List[Any]:
|
802
793
|
"""
|
@@ -827,8 +818,8 @@ class TinyCodeAgent:
|
|
827
818
|
self.code_provider.set_user_variables(self.user_variables)
|
828
819
|
# Rebuild system prompt to include new variables info
|
829
820
|
self.system_prompt = self._build_system_prompt()
|
830
|
-
# Update the
|
831
|
-
self.
|
821
|
+
# Update the system prompt in messages
|
822
|
+
self._update_system_prompt()
|
832
823
|
|
833
824
|
def add_user_variable(self, name: str, value: Any):
|
834
825
|
"""
|
@@ -843,7 +834,7 @@ class TinyCodeAgent:
|
|
843
834
|
# Rebuild system prompt to include new variables info
|
844
835
|
self.system_prompt = self._build_system_prompt()
|
845
836
|
# Update the agent's system prompt
|
846
|
-
self.
|
837
|
+
self._update_system_prompt()
|
847
838
|
|
848
839
|
def remove_user_variable(self, name: str):
|
849
840
|
"""
|
@@ -858,7 +849,7 @@ class TinyCodeAgent:
|
|
858
849
|
# Rebuild system prompt
|
859
850
|
self.system_prompt = self._build_system_prompt()
|
860
851
|
# Update the agent's system prompt
|
861
|
-
self.
|
852
|
+
self._update_system_prompt()
|
862
853
|
|
863
854
|
def get_user_variables(self) -> Dict[str, Any]:
|
864
855
|
"""
|
@@ -926,7 +917,7 @@ class TinyCodeAgent:
|
|
926
917
|
# Rebuild system prompt to include new authorized imports
|
927
918
|
self.system_prompt = self._build_system_prompt()
|
928
919
|
# Update the agent's system prompt
|
929
|
-
self.
|
920
|
+
self._update_system_prompt()
|
930
921
|
|
931
922
|
def get_authorized_imports(self) -> List[str]:
|
932
923
|
"""
|
@@ -973,27 +964,22 @@ class TinyCodeAgent:
|
|
973
964
|
# Rebuild system prompt to reflect updated authorized imports
|
974
965
|
self.system_prompt = self._build_system_prompt()
|
975
966
|
# Update the agent's system prompt
|
976
|
-
self.
|
967
|
+
self._update_system_prompt()
|
977
968
|
|
978
969
|
async def close(self):
|
979
970
|
"""Clean up resources."""
|
980
971
|
await self.code_provider.cleanup()
|
981
|
-
await
|
972
|
+
await super().close()
|
982
973
|
|
983
|
-
|
984
|
-
"""Clear the conversation history."""
|
985
|
-
self.agent.clear_conversation()
|
986
|
-
|
987
|
-
@property
|
988
|
-
def messages(self):
|
989
|
-
"""Get the conversation messages."""
|
990
|
-
return self.agent.messages
|
974
|
+
|
991
975
|
|
992
|
-
|
993
|
-
def session_id(self):
|
994
|
-
"""Get the session ID."""
|
995
|
-
return self.agent.session_id
|
976
|
+
|
996
977
|
|
978
|
+
def _update_system_prompt(self):
|
979
|
+
"""Update the system prompt in the messages array."""
|
980
|
+
if self.messages and len(self.messages) > 0:
|
981
|
+
self.messages[0]["content"] = self.system_prompt
|
982
|
+
|
997
983
|
def set_check_string_obfuscation(self, enabled: bool):
|
998
984
|
"""
|
999
985
|
Enable or disable string obfuscation detection.
|
@@ -1008,32 +994,9 @@ class TinyCodeAgent:
|
|
1008
994
|
if hasattr(self.code_provider, 'check_string_obfuscation'):
|
1009
995
|
self.code_provider.check_string_obfuscation = enabled
|
1010
996
|
|
1011
|
-
|
1012
|
-
"""
|
1013
|
-
Generate a summary of the current conversation history.
|
1014
|
-
|
1015
|
-
Args:
|
1016
|
-
Returns:
|
1017
|
-
A string containing the conversation summary
|
1018
|
-
"""
|
1019
|
-
# Use the underlying TinyAgent's summarize_conversation method
|
1020
|
-
return await self.agent.summarize()
|
1021
|
-
|
1022
|
-
async def compact(self) -> bool:
|
1023
|
-
"""
|
1024
|
-
Compact the conversation history by replacing it with a summary.
|
1025
|
-
|
1026
|
-
This method delegates to the underlying TinyAgent's compact method,
|
1027
|
-
which:
|
1028
|
-
1. Generates a summary of the current conversation
|
1029
|
-
2. If successful, replaces the conversation with just [system, user] messages
|
1030
|
-
where the user message contains the summary
|
1031
|
-
3. Returns True if compaction was successful, False otherwise
|
997
|
+
|
1032
998
|
|
1033
|
-
|
1034
|
-
Boolean indicating whether the compaction was successful
|
1035
|
-
"""
|
1036
|
-
return await self.agent.compact()
|
999
|
+
|
1037
1000
|
|
1038
1001
|
def add_ui_callback(self, ui_type: str, optimized: bool = True):
|
1039
1002
|
"""
|
@@ -1122,6 +1085,48 @@ class TinyCodeAgent:
|
|
1122
1085
|
"""
|
1123
1086
|
return self.auto_git_checkpoint
|
1124
1087
|
|
1088
|
+
def enable_python_tool(self, enabled: bool = True):
|
1089
|
+
"""
|
1090
|
+
Enable or disable the Python code execution tool.
|
1091
|
+
|
1092
|
+
Args:
|
1093
|
+
enabled: If True, enable the run_python tool. If False, disable it.
|
1094
|
+
"""
|
1095
|
+
if enabled != self._python_tool_enabled:
|
1096
|
+
self._python_tool_enabled = enabled
|
1097
|
+
# Re-setup tools to reflect the change
|
1098
|
+
self._setup_code_execution_tools()
|
1099
|
+
|
1100
|
+
def enable_shell_tool(self, enabled: bool = True):
|
1101
|
+
"""
|
1102
|
+
Enable or disable the shell command execution tool.
|
1103
|
+
|
1104
|
+
Args:
|
1105
|
+
enabled: If True, enable the bash tool. If False, disable it.
|
1106
|
+
"""
|
1107
|
+
if enabled != self._shell_tool_enabled:
|
1108
|
+
self._shell_tool_enabled = enabled
|
1109
|
+
# Re-setup tools to reflect the change
|
1110
|
+
self._setup_code_execution_tools()
|
1111
|
+
|
1112
|
+
def get_python_tool_status(self) -> bool:
|
1113
|
+
"""
|
1114
|
+
Get the current status of the Python tool.
|
1115
|
+
|
1116
|
+
Returns:
|
1117
|
+
True if the run_python tool is enabled, False otherwise.
|
1118
|
+
"""
|
1119
|
+
return self._python_tool_enabled
|
1120
|
+
|
1121
|
+
def get_shell_tool_status(self) -> bool:
|
1122
|
+
"""
|
1123
|
+
Get the current status of the shell tool.
|
1124
|
+
|
1125
|
+
Returns:
|
1126
|
+
True if the bash tool is enabled, False otherwise.
|
1127
|
+
"""
|
1128
|
+
return self._shell_tool_enabled
|
1129
|
+
|
1125
1130
|
def set_environment_variables(self, env_vars: Dict[str, str]):
|
1126
1131
|
"""
|
1127
1132
|
Set environment variables for the code execution provider.
|
@@ -1534,6 +1539,35 @@ async def run_example():
|
|
1534
1539
|
await agent_seatbelt.connect_to_server("npx", ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"])
|
1535
1540
|
await agent_seatbelt.connect_to_server("npx", ["-y", "@modelcontextprotocol/server-sequential-thinking"])
|
1536
1541
|
|
1542
|
+
# Example: connecting with environment variables
|
1543
|
+
env_vars = {
|
1544
|
+
"MCP_DEBUG": "true",
|
1545
|
+
"RATE_LIMIT": "100",
|
1546
|
+
"CUSTOM_CONFIG": "seatbelt_mode"
|
1547
|
+
}
|
1548
|
+
|
1549
|
+
# Create a simple Modal agent to demonstrate environment variable usage
|
1550
|
+
agent_modal = TinyCodeAgent(
|
1551
|
+
model="gpt-4.1-mini",
|
1552
|
+
tools=[search_web],
|
1553
|
+
code_tools=[data_processor],
|
1554
|
+
provider="modal",
|
1555
|
+
local_execution=False,
|
1556
|
+
api_key=api_key
|
1557
|
+
)
|
1558
|
+
|
1559
|
+
try:
|
1560
|
+
await agent_modal.connect_to_server(
|
1561
|
+
"npx",
|
1562
|
+
["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"],
|
1563
|
+
env=env_vars
|
1564
|
+
)
|
1565
|
+
logger.info("Successfully connected Modal agent with environment variables")
|
1566
|
+
except Exception as e:
|
1567
|
+
logger.warning(f"Environment variable example failed: {e}")
|
1568
|
+
finally:
|
1569
|
+
await agent_modal.close()
|
1570
|
+
|
1537
1571
|
# Test the seatbelt agent
|
1538
1572
|
response_seatbelt = await agent_seatbelt.run("""
|
1539
1573
|
I have some sample data. Please use the data_processor tool in Python to analyze my sample_data
|
@@ -1645,6 +1679,148 @@ async def run_example():
|
|
1645
1679
|
print("\n" + "="*80)
|
1646
1680
|
print("⚠️ Seatbelt provider is not supported on this system. Skipping seatbelt tests.")
|
1647
1681
|
|
1682
|
+
# Test optional tool functionality
|
1683
|
+
print("\n" + "="*80)
|
1684
|
+
print("🔧 Testing optional tool functionality")
|
1685
|
+
|
1686
|
+
# Create an agent with only Python tool enabled (no shell tool)
|
1687
|
+
print("Creating agent with only Python tool enabled...")
|
1688
|
+
agent_python_only = TinyCodeAgent(
|
1689
|
+
model="gpt-4.1-mini",
|
1690
|
+
tools=[search_web],
|
1691
|
+
code_tools=[data_processor],
|
1692
|
+
user_variables={"test_data": [1, 2, 3, 4, 5]},
|
1693
|
+
enable_python_tool=True,
|
1694
|
+
enable_shell_tool=False, # Disable shell tool
|
1695
|
+
local_execution=True
|
1696
|
+
)
|
1697
|
+
|
1698
|
+
# Connect to MCP servers
|
1699
|
+
await agent_python_only.connect_to_server("npx", ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"])
|
1700
|
+
await agent_python_only.connect_to_server("npx", ["-y", "@modelcontextprotocol/server-sequential-thinking"])
|
1701
|
+
|
1702
|
+
# Check tool status
|
1703
|
+
print(f"Python tool enabled: {agent_python_only.get_python_tool_status()}")
|
1704
|
+
print(f"Shell tool enabled: {agent_python_only.get_shell_tool_status()}")
|
1705
|
+
|
1706
|
+
# Test Python execution (should work)
|
1707
|
+
python_response = await agent_python_only.run("""
|
1708
|
+
Use the data_processor tool to analyze the test_data and show me the results.
|
1709
|
+
""")
|
1710
|
+
print("Python Tool Test (should work):")
|
1711
|
+
print(python_response)
|
1712
|
+
|
1713
|
+
# Test shell execution (should not work - tool disabled)
|
1714
|
+
shell_response = await agent_python_only.run("""
|
1715
|
+
Run 'ls -la' to list files in the current directory.
|
1716
|
+
""")
|
1717
|
+
print("Shell Tool Test (should not work - tool disabled):")
|
1718
|
+
print(shell_response)
|
1719
|
+
|
1720
|
+
# Now enable the shell tool dynamically
|
1721
|
+
print("\nEnabling shell tool dynamically...")
|
1722
|
+
agent_python_only.enable_shell_tool(True)
|
1723
|
+
print(f"Shell tool enabled: {agent_python_only.get_shell_tool_status()}")
|
1724
|
+
|
1725
|
+
# Test shell execution again (should work now)
|
1726
|
+
shell_response2 = await agent_python_only.run("""
|
1727
|
+
Run 'ls -la' to list files in the current directory.
|
1728
|
+
""")
|
1729
|
+
print("Shell Tool Test (should work now - tool enabled):")
|
1730
|
+
print(shell_response2)
|
1731
|
+
|
1732
|
+
# Create an agent with only shell tool enabled (no Python tool)
|
1733
|
+
print("\nCreating agent with only shell tool enabled...")
|
1734
|
+
agent_shell_only = TinyCodeAgent(
|
1735
|
+
model="gpt-4.1-mini",
|
1736
|
+
tools=[search_web],
|
1737
|
+
code_tools=[data_processor],
|
1738
|
+
user_variables={"test_data": [1, 2, 3, 4, 5]},
|
1739
|
+
enable_python_tool=False, # Disable Python tool
|
1740
|
+
enable_shell_tool=True,
|
1741
|
+
local_execution=True
|
1742
|
+
)
|
1743
|
+
|
1744
|
+
# Connect to MCP servers
|
1745
|
+
await agent_shell_only.connect_to_server("npx", ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"])
|
1746
|
+
await agent_shell_only.connect_to_server("npx", ["-y", "@modelcontextprotocol/server-sequential-thinking"])
|
1747
|
+
|
1748
|
+
# Check tool status
|
1749
|
+
print(f"Python tool enabled: {agent_shell_only.get_python_tool_status()}")
|
1750
|
+
print(f"Shell tool enabled: {agent_shell_only.get_shell_tool_status()}")
|
1751
|
+
|
1752
|
+
# Test shell execution (should work)
|
1753
|
+
shell_response3 = await agent_shell_only.run("""
|
1754
|
+
Run 'pwd' to show the current working directory.
|
1755
|
+
""")
|
1756
|
+
print("Shell Tool Test (should work):")
|
1757
|
+
print(shell_response3)
|
1758
|
+
|
1759
|
+
# Test Python execution (should not work - tool disabled)
|
1760
|
+
python_response2 = await agent_shell_only.run("""
|
1761
|
+
Use the data_processor tool to analyze the test_data and show me the results.
|
1762
|
+
""")
|
1763
|
+
print("Python Tool Test (should not work - tool disabled):")
|
1764
|
+
print(python_response2)
|
1765
|
+
|
1766
|
+
# Now enable the Python tool dynamically
|
1767
|
+
print("\nEnabling Python tool dynamically...")
|
1768
|
+
agent_shell_only.enable_python_tool(True)
|
1769
|
+
print(f"Python tool enabled: {agent_shell_only.get_python_tool_status()}")
|
1770
|
+
|
1771
|
+
# Test Python execution again (should work now)
|
1772
|
+
python_response3 = await agent_shell_only.run("""
|
1773
|
+
Use the data_processor tool to analyze the test_data and show me the results.
|
1774
|
+
""")
|
1775
|
+
print("Python Tool Test (should work now - tool enabled):")
|
1776
|
+
print(python_response3)
|
1777
|
+
|
1778
|
+
# Create an agent with both tools disabled
|
1779
|
+
print("\nCreating agent with both tools disabled...")
|
1780
|
+
agent_no_tools = TinyCodeAgent(
|
1781
|
+
model="gpt-4.1-mini",
|
1782
|
+
tools=[search_web],
|
1783
|
+
code_tools=[data_processor],
|
1784
|
+
user_variables={"test_data": [1, 2, 3, 4, 5]},
|
1785
|
+
enable_python_tool=False, # Disable Python tool
|
1786
|
+
enable_shell_tool=False, # Disable shell tool
|
1787
|
+
local_execution=True
|
1788
|
+
)
|
1789
|
+
|
1790
|
+
# Connect to MCP servers
|
1791
|
+
await agent_no_tools.connect_to_server("npx", ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"])
|
1792
|
+
await agent_no_tools.connect_to_server("npx", ["-y", "@modelcontextprotocol/server-sequential-thinking"])
|
1793
|
+
|
1794
|
+
# Check tool status
|
1795
|
+
print(f"Python tool enabled: {agent_no_tools.get_python_tool_status()}")
|
1796
|
+
print(f"Shell tool enabled: {agent_no_tools.get_shell_tool_status()}")
|
1797
|
+
|
1798
|
+
# Test both tools (should not work - both disabled)
|
1799
|
+
no_tools_response = await agent_no_tools.run("""
|
1800
|
+
Try to use both Python and shell tools to analyze the test_data and list files.
|
1801
|
+
""")
|
1802
|
+
print("Both Tools Test (should not work - both disabled):")
|
1803
|
+
print(no_tools_response)
|
1804
|
+
|
1805
|
+
# Enable both tools dynamically
|
1806
|
+
print("\nEnabling both tools dynamically...")
|
1807
|
+
agent_no_tools.enable_python_tool(True)
|
1808
|
+
agent_no_tools.enable_shell_tool(True)
|
1809
|
+
print(f"Python tool enabled: {agent_no_tools.get_python_tool_status()}")
|
1810
|
+
print(f"Shell tool enabled: {agent_no_tools.get_shell_tool_status()}")
|
1811
|
+
|
1812
|
+
# Test both tools again (should work now)
|
1813
|
+
both_tools_response = await agent_no_tools.run("""
|
1814
|
+
Use both Python and shell tools: first analyze the test_data with data_processor, then list files with ls.
|
1815
|
+
""")
|
1816
|
+
print("Both Tools Test (should work now - both enabled):")
|
1817
|
+
print(both_tools_response)
|
1818
|
+
|
1819
|
+
# Clean up
|
1820
|
+
await agent_python_only.close()
|
1821
|
+
await agent_shell_only.close()
|
1822
|
+
await agent_no_tools.close()
|
1823
|
+
|
1648
1824
|
await agent_remote.close()
|
1649
1825
|
await agent_local.close()
|
1650
1826
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import json
|
3
3
|
import logging
|
4
|
+
import traceback
|
4
5
|
from typing import Dict, List, Optional, Any, Tuple, Callable
|
5
6
|
|
6
7
|
# Keep your MCPClient implementation unchanged
|
@@ -57,16 +58,17 @@ class MCPClient:
|
|
57
58
|
logger.debug(f"Callback is a regular function")
|
58
59
|
callback(event_name, self, **kwargs)
|
59
60
|
except Exception as e:
|
60
|
-
logger.error(f"Error in callback for {event_name}: {str(e)}")
|
61
|
+
logger.error(f"Error in callback for {event_name}: {str(e)} {traceback.format_exc()}")
|
61
62
|
|
62
|
-
async def connect(self, command: str, args: list[str]):
|
63
|
+
async def connect(self, command: str, args: list[str], env: dict[str, str] = None):
|
63
64
|
"""
|
64
65
|
Launches the MCP server subprocess and initializes the client session.
|
65
66
|
:param command: e.g. "python" or "node"
|
66
67
|
:param args: list of args to pass, e.g. ["my_server.py"] or ["build/index.js"]
|
68
|
+
:param env: dictionary of environment variables to pass to the subprocess
|
67
69
|
"""
|
68
70
|
# Prepare stdio transport parameters
|
69
|
-
params = StdioServerParameters(command=command, args=args)
|
71
|
+
params = StdioServerParameters(command=command, args=args, env=env)
|
70
72
|
# Open the stdio client transport
|
71
73
|
self.stdio, self.sock_write = await self.exit_stack.enter_async_context(
|
72
74
|
stdio_client(params)
|
@@ -156,6 +158,28 @@ async def run_example():
|
|
156
158
|
result = await client.call_tool("echo", {"message": "Hello, MCP!"})
|
157
159
|
mcp_logger.info(f"Echo result: {result}")
|
158
160
|
|
161
|
+
# Example with environment variables
|
162
|
+
mcp_logger.info("Testing with environment variables...")
|
163
|
+
client_with_env = MCPClient(logger=mcp_logger)
|
164
|
+
|
165
|
+
# Example: connecting with environment variables
|
166
|
+
env_vars = {
|
167
|
+
"DEBUG": "true",
|
168
|
+
"LOG_LEVEL": "info",
|
169
|
+
"CUSTOM_VAR": "example_value"
|
170
|
+
}
|
171
|
+
|
172
|
+
try:
|
173
|
+
await client_with_env.connect(
|
174
|
+
"python",
|
175
|
+
["-m", "mcp.examples.echo_server"],
|
176
|
+
env=env_vars
|
177
|
+
)
|
178
|
+
mcp_logger.info("Successfully connected with environment variables")
|
179
|
+
await client_with_env.close()
|
180
|
+
except Exception as e:
|
181
|
+
mcp_logger.warning(f"Environment variable example failed (expected): {e}")
|
182
|
+
|
159
183
|
finally:
|
160
184
|
# Clean up
|
161
185
|
await client.close()
|
@@ -705,11 +705,12 @@ class TinyAgent:
|
|
705
705
|
self.logger.debug(f"Callback is a regular function")
|
706
706
|
callback(event_name, self, **kwargs)
|
707
707
|
except Exception as e:
|
708
|
-
self.logger.error(f"Error in callback for {event_name}: {str(e)}")
|
708
|
+
self.logger.error(f"Error in callback for {event_name}: {str(e)} {traceback.format_exc()}")
|
709
709
|
|
710
710
|
async def connect_to_server(self, command: str, args: List[str],
|
711
711
|
include_tools: Optional[List[str]] = None,
|
712
|
-
exclude_tools: Optional[List[str]] = None
|
712
|
+
exclude_tools: Optional[List[str]] = None,
|
713
|
+
env: Optional[Dict[str, str]] = None) -> None:
|
713
714
|
"""
|
714
715
|
Connect to an MCP server and fetch available tools.
|
715
716
|
|
@@ -718,6 +719,7 @@ class TinyAgent:
|
|
718
719
|
args: List of arguments for the server
|
719
720
|
include_tools: Optional list of tool name patterns to include (if provided, only matching tools will be added)
|
720
721
|
exclude_tools: Optional list of tool name patterns to exclude (matching tools will be skipped)
|
722
|
+
env: Optional dictionary of environment variables to pass to the subprocess
|
721
723
|
"""
|
722
724
|
# 1) Create and connect a brand-new client
|
723
725
|
client = MCPClient()
|
@@ -726,7 +728,7 @@ class TinyAgent:
|
|
726
728
|
for callback in self.callbacks:
|
727
729
|
client.add_callback(callback)
|
728
730
|
|
729
|
-
await client.connect(command, args)
|
731
|
+
await client.connect(command, args, env)
|
730
732
|
self.mcp_clients.append(client)
|
731
733
|
|
732
734
|
# 2) List tools on *this* server
|
@@ -1717,7 +1719,21 @@ async def run_example():
|
|
1717
1719
|
|
1718
1720
|
# Connect to MCP servers for additional tools
|
1719
1721
|
try:
|
1722
|
+
# Example: connecting without environment variables (existing behavior)
|
1720
1723
|
await agent1.connect_to_server("npx", ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"])
|
1724
|
+
|
1725
|
+
# Example: connecting with environment variables
|
1726
|
+
env_vars = {
|
1727
|
+
"DEBUG": "true",
|
1728
|
+
"LOG_LEVEL": "info",
|
1729
|
+
"API_TIMEOUT": "30"
|
1730
|
+
}
|
1731
|
+
await agent1.connect_to_server(
|
1732
|
+
"npx",
|
1733
|
+
["-y", "@modelcontextprotocol/server-sequential-thinking"],
|
1734
|
+
env=env_vars
|
1735
|
+
)
|
1736
|
+
agent_logger.info("Successfully connected to MCP servers with environment variables")
|
1721
1737
|
except Exception as e:
|
1722
1738
|
agent_logger.error(f"Failed to connect to MCP servers: {e}")
|
1723
1739
|
|
@@ -1747,7 +1763,17 @@ async def run_example():
|
|
1747
1763
|
|
1748
1764
|
# Connect to the same MCP server
|
1749
1765
|
try:
|
1750
|
-
|
1766
|
+
# Example with environment variables for o4-mini model
|
1767
|
+
env_vars = {
|
1768
|
+
"NODE_ENV": "production",
|
1769
|
+
"CACHE_ENABLED": "false"
|
1770
|
+
}
|
1771
|
+
await agent2.connect_to_server(
|
1772
|
+
"npx",
|
1773
|
+
["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"],
|
1774
|
+
env=env_vars
|
1775
|
+
)
|
1776
|
+
agent_logger.info("Successfully connected o4-mini agent with environment variables")
|
1751
1777
|
except Exception as e:
|
1752
1778
|
agent_logger.error(f"Failed to connect to MCP servers: {e}")
|
1753
1779
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: tinyagent-py
|
3
|
-
Version: 0.0.
|
4
|
-
Summary: TinyAgent with MCP Client,
|
3
|
+
Version: 0.0.19
|
4
|
+
Summary: TinyAgent with MCP Client, CodeAgent (Thinking, Planning, Interactive Python and Shell with high variaety of sandboxing(seatbelt, Modal, E2B, docker, etc) ), and Extendable Hooks, Tiny but powerful
|
5
5
|
Author-email: Mahdi Golchin <golchin@askdev.ai>
|
6
6
|
Project-URL: Homepage, https://github.com/askbudi/tinyagent
|
7
7
|
Project-URL: Bug Tracker, https://github.com/askbudi/tinyagent/issues
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/providers/modal_provider.py
RENAMED
File without changes
|
{tinyagent_py-0.0.17rc1 → tinyagent_py-0.0.19}/tinyagent/code_agent/providers/seatbelt_provider.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|