crewplus 0.2.97__tar.gz → 0.2.99__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.
- {crewplus-0.2.97 → crewplus-0.2.99}/PKG-INFO +2 -1
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/__init__.py +15 -1
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/azure_chat_model.py +24 -7
- crewplus-0.2.99/crewplus/services/capabilities/__init__.py +19 -0
- crewplus-0.2.99/crewplus/services/capabilities/base.py +179 -0
- crewplus-0.2.99/crewplus/services/capabilities/bash.py +210 -0
- crewplus-0.2.99/crewplus/services/capabilities/registry.py +225 -0
- crewplus-0.2.99/crewplus/services/capabilities/web_search.py +225 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/gemini_chat_model.py +5 -4
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/model_load_balancer.py +58 -0
- crewplus-0.2.99/crewplus/services/oai_claude_chat_model.py +473 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/pyproject.toml +2 -1
- crewplus-0.2.99/tests/test_bash_tools.py +532 -0
- crewplus-0.2.99/tests/test_claude_api.py +210 -0
- crewplus-0.2.99/tests/test_claude_langchain_openai.py +611 -0
- crewplus-0.2.99/tests/test_oai_claude_chat_model.py +549 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/LICENSE +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/README.md +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/__init__.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/callbacks/__init__.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/callbacks/async_langfuse_handler.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/claude_chat_model.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/feedback.md +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/feedback_manager.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/init_services.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/schemas/feedback.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/services/tracing_manager.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/__init__.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/schema_action.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/schema_document_updater.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/utils/tracing_util.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/__init__.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/milvus_schema_manager.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/schema_milvus.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/crewplus/vectorstores/milvus/vdb_service.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/docs/GeminiChatModel.md +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/docs/ModelLoadBalancer.md +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/docs/VDBService.md +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/docs/index.md +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/tests/README.md +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/tests/conftest.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_claude_custom_endpoint_integration.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_claude_taskflow_schema.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_custom_claude_endpoint.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_gemini3_pro_preview.py +0 -0
- {crewplus-0.2.97 → crewplus-0.2.99}/tests/test_gemini_bind_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: crewplus
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.99
|
|
4
4
|
Summary: Base services for CrewPlus AI applications
|
|
5
5
|
Author-Email: Tim Liu <tim@opsmateai.com>
|
|
6
6
|
License: MIT
|
|
@@ -15,6 +15,7 @@ Requires-Dist: google-genai>=1.21.1
|
|
|
15
15
|
Requires-Dist: anthropic[vertex]>=0.76.0
|
|
16
16
|
Requires-Dist: langchain-milvus<0.3.0,>=0.2.1
|
|
17
17
|
Requires-Dist: langfuse<4.0.0,>=3.1.3
|
|
18
|
+
Requires-Dist: pymilvus<=2.6.7,>=2.5.7
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
|
|
20
21
|
# CrewPlus
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
from .gemini_chat_model import GeminiChatModel
|
|
2
2
|
from .claude_chat_model import ClaudeChatModel
|
|
3
|
+
from .oai_claude_chat_model import OAIClaudeChatModel
|
|
3
4
|
from .init_services import init_load_balancer, get_model_balancer
|
|
4
5
|
from .model_load_balancer import ModelLoadBalancer
|
|
5
6
|
from .azure_chat_model import TracedAzureChatOpenAI
|
|
6
7
|
from .feedback_manager import LangfuseFeedbackManager
|
|
7
8
|
from .schemas.feedback import FeedbackIn, FeedbackUpdate, FeedbackOut
|
|
8
9
|
from .tracing_manager import TracingManager, TracingContext
|
|
10
|
+
from .capabilities import (
|
|
11
|
+
BaseCapability,
|
|
12
|
+
CapabilityConfig,
|
|
13
|
+
CapabilityRegistry,
|
|
14
|
+
TavilySearchCapability,
|
|
15
|
+
BashCapability,
|
|
16
|
+
)
|
|
9
17
|
|
|
10
18
|
__all__ = [
|
|
11
19
|
"GeminiChatModel",
|
|
12
20
|
"ClaudeChatModel",
|
|
21
|
+
"OAIClaudeChatModel",
|
|
13
22
|
"init_load_balancer",
|
|
14
23
|
"get_model_balancer",
|
|
15
24
|
"ModelLoadBalancer",
|
|
@@ -19,5 +28,10 @@ __all__ = [
|
|
|
19
28
|
"FeedbackUpdate",
|
|
20
29
|
"FeedbackOut",
|
|
21
30
|
"TracingManager",
|
|
22
|
-
"TracingContext"
|
|
31
|
+
"TracingContext",
|
|
32
|
+
"BaseCapability",
|
|
33
|
+
"CapabilityConfig",
|
|
34
|
+
"CapabilityRegistry",
|
|
35
|
+
"TavilySearchCapability",
|
|
36
|
+
"BashCapability",
|
|
23
37
|
]
|
|
@@ -26,13 +26,30 @@ class TracedAzureChatOpenAI(AzureChatOpenAI):
|
|
|
26
26
|
|
|
27
27
|
**Supported Capabilities:**
|
|
28
28
|
|
|
29
|
-
None. Azure OpenAI Chat Completions API does not have built-in
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
None. Azure OpenAI Chat Completions API does not have built-in web search
|
|
30
|
+
or similar capabilities.
|
|
31
|
+
|
|
32
|
+
Unlike Claude and Gemini which have platform-native web search:
|
|
33
|
+
- Claude: Built-in `web_search_20250305` tool, search executed by Anthropic
|
|
34
|
+
- Gemini: Built-in `GoogleSearch` tool, search executed by Google
|
|
35
|
+
- Azure OpenAI: **No built-in web search in Chat Completions API**
|
|
36
|
+
|
|
37
|
+
Microsoft does offer web search through other APIs, but none are compatible
|
|
38
|
+
with the Chat Completions API used here:
|
|
39
|
+
- Responses API `web_search_preview`: Only available on OpenAI direct, NOT on Azure
|
|
40
|
+
- Agents API `bing_grounding`: Requires `azure-ai-agents` SDK, incompatible with LangChain
|
|
41
|
+
- Chat Completions `data_sources`: Only supports Azure AI Search (your own index), not web search
|
|
42
|
+
|
|
43
|
+
If web search is needed for Azure models in the future, the feasible approach is
|
|
44
|
+
Function Calling + external search service (e.g. Bing Search API):
|
|
45
|
+
1. Inject a `web_search` function definition into the request
|
|
46
|
+
2. When the model returns a tool_call, execute the search externally
|
|
47
|
+
3. Feed results back to the model for final response generation
|
|
48
|
+
This is fundamentally different from Claude/Gemini where the platform handles
|
|
49
|
+
the search internally in a single API call.
|
|
50
|
+
|
|
51
|
+
Revisit this when Microsoft enables `web_search_preview` on Azure OpenAI.
|
|
52
|
+
Ref: https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/web-search
|
|
36
53
|
|
|
37
54
|
Attributes:
|
|
38
55
|
logger (Optional[logging.Logger]): An optional logger instance.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Capabilities module for custom tool support.
|
|
3
|
+
|
|
4
|
+
Provides a registry-based system for adding capabilities (web search, bash, etc.)
|
|
5
|
+
to chat models that don't have native support for these features.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base import BaseCapability, CapabilityConfig
|
|
9
|
+
from .registry import CapabilityRegistry
|
|
10
|
+
from .web_search import TavilySearchCapability
|
|
11
|
+
from .bash import BashCapability
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"BaseCapability",
|
|
15
|
+
"CapabilityConfig",
|
|
16
|
+
"CapabilityRegistry",
|
|
17
|
+
"TavilySearchCapability",
|
|
18
|
+
"BashCapability",
|
|
19
|
+
]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for capability system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any, Dict, Optional, List
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CapabilityConfig(BaseModel):
|
|
11
|
+
"""Configuration for a capability."""
|
|
12
|
+
|
|
13
|
+
enabled: bool = Field(default=True, description="Whether this capability is enabled")
|
|
14
|
+
type: str = Field(default="custom", description="Capability type: 'native' or 'custom'")
|
|
15
|
+
|
|
16
|
+
class Config:
|
|
17
|
+
extra = "allow" # Allow additional fields for specific capabilities
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseCapability(ABC):
|
|
21
|
+
"""
|
|
22
|
+
Abstract base class for all capabilities.
|
|
23
|
+
|
|
24
|
+
Capabilities are tools that can be added to chat models via function calling.
|
|
25
|
+
Each capability provides:
|
|
26
|
+
- A tool schema for the LLM to understand how to call it
|
|
27
|
+
- An optional execute method for actual execution (if needed)
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
class MyCapability(BaseCapability):
|
|
31
|
+
@property
|
|
32
|
+
def name(self) -> str:
|
|
33
|
+
return "my_tool"
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def description(self) -> str:
|
|
37
|
+
return "Does something useful"
|
|
38
|
+
|
|
39
|
+
def get_parameters_schema(self) -> Dict:
|
|
40
|
+
return {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"input": {"type": "string", "description": "The input"}
|
|
44
|
+
},
|
|
45
|
+
"required": ["input"]
|
|
46
|
+
}
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: Dict[str, Any]):
|
|
50
|
+
"""
|
|
51
|
+
Initialize the capability with configuration.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
config: Configuration dictionary for this capability
|
|
55
|
+
"""
|
|
56
|
+
self.config = CapabilityConfig(**config) if isinstance(config, dict) else config
|
|
57
|
+
self._raw_config = config
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def name(self) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Return the tool name for function calling.
|
|
64
|
+
|
|
65
|
+
Must match regex: ^[a-zA-Z0-9_-]{1,64}$
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def description(self) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Return a detailed description of what this tool does.
|
|
74
|
+
|
|
75
|
+
This helps the LLM understand when and how to use the tool.
|
|
76
|
+
"""
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def get_parameters_schema(self) -> Dict[str, Any]:
|
|
81
|
+
"""
|
|
82
|
+
Return the JSON Schema for the tool's parameters.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dict with "type": "object", "properties", and "required" keys
|
|
86
|
+
"""
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
def get_tool_schema(self) -> Dict[str, Any]:
|
|
90
|
+
"""
|
|
91
|
+
Return the complete OpenAI-compatible tool schema.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Tool definition in OpenAI function calling format
|
|
95
|
+
"""
|
|
96
|
+
return {
|
|
97
|
+
"type": "function",
|
|
98
|
+
"function": {
|
|
99
|
+
"name": self.name,
|
|
100
|
+
"description": self.description,
|
|
101
|
+
"parameters": self.get_parameters_schema()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def get_langchain_tool_schema(self) -> Dict[str, Any]:
|
|
106
|
+
"""
|
|
107
|
+
Return tool schema in LangChain/Claude format.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Tool definition compatible with Claude's API
|
|
111
|
+
"""
|
|
112
|
+
return {
|
|
113
|
+
"name": self.name,
|
|
114
|
+
"description": self.description,
|
|
115
|
+
"input_schema": self.get_parameters_schema()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async def execute(self, **kwargs) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Execute the capability with given arguments.
|
|
121
|
+
|
|
122
|
+
Override this method to provide actual execution logic.
|
|
123
|
+
Default implementation raises NotImplementedError.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
**kwargs: Arguments matching the parameters schema
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
String result of the execution
|
|
130
|
+
"""
|
|
131
|
+
raise NotImplementedError(
|
|
132
|
+
f"Capability '{self.name}' does not support execution. "
|
|
133
|
+
"Override execute() method to add execution support."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def execute_sync(self, **kwargs) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Synchronous version of execute.
|
|
139
|
+
|
|
140
|
+
Default implementation raises NotImplementedError.
|
|
141
|
+
"""
|
|
142
|
+
raise NotImplementedError(
|
|
143
|
+
f"Capability '{self.name}' does not support synchronous execution. "
|
|
144
|
+
"Override execute_sync() method to add execution support."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def is_enabled(self) -> bool:
|
|
148
|
+
"""Check if this capability is enabled."""
|
|
149
|
+
return self.config.enabled
|
|
150
|
+
|
|
151
|
+
def get_config_value(self, key: str, default: Any = None) -> Any:
|
|
152
|
+
"""Get a configuration value by key."""
|
|
153
|
+
return self._raw_config.get(key, default)
|
|
154
|
+
|
|
155
|
+
def update_config(self, updates: Dict[str, Any]) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Update configuration with new values.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
updates: Dictionary of config updates to apply
|
|
161
|
+
"""
|
|
162
|
+
self._raw_config.update(updates)
|
|
163
|
+
self.config = CapabilityConfig(**self._raw_config)
|
|
164
|
+
|
|
165
|
+
def clone_with_config(self, config_override: Dict[str, Any]) -> "BaseCapability":
|
|
166
|
+
"""
|
|
167
|
+
Create a new instance with merged configuration.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
config_override: Configuration values to override
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
New capability instance with merged config
|
|
174
|
+
"""
|
|
175
|
+
merged_config = {**self._raw_config, **config_override}
|
|
176
|
+
return self.__class__(merged_config)
|
|
177
|
+
|
|
178
|
+
def __repr__(self) -> str:
|
|
179
|
+
return f"{self.__class__.__name__}(name='{self.name}', enabled={self.is_enabled()})"
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bash command capability for function calling simulation.
|
|
3
|
+
|
|
4
|
+
This capability allows the LLM to express bash commands via function calling.
|
|
5
|
+
It does NOT actually execute commands - it's designed for testing and simulation
|
|
6
|
+
of the function calling workflow.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from .base import BaseCapability
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BashCapability(BaseCapability):
|
|
16
|
+
"""
|
|
17
|
+
Bash command capability for function calling simulation.
|
|
18
|
+
|
|
19
|
+
This capability provides the tool schema for bash commands, allowing the LLM
|
|
20
|
+
to generate proper function calls with command and restart parameters.
|
|
21
|
+
Execution is NOT performed - this is for function calling simulation only.
|
|
22
|
+
|
|
23
|
+
Configuration Options:
|
|
24
|
+
enabled (bool): Whether this capability is enabled (default: True)
|
|
25
|
+
safe_mode (bool): If True, hints to LLM to avoid dangerous commands (default: True)
|
|
26
|
+
allowed_commands (list): List of allowed command prefixes (optional)
|
|
27
|
+
timeout (int): Suggested timeout in seconds (default: 30)
|
|
28
|
+
working_directory (str): Suggested working directory (optional)
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
config = {
|
|
32
|
+
"enabled": True,
|
|
33
|
+
"safe_mode": True,
|
|
34
|
+
"allowed_commands": ["ls", "cat", "grep", "find", "echo"],
|
|
35
|
+
"timeout": 30
|
|
36
|
+
}
|
|
37
|
+
capability = BashCapability(config)
|
|
38
|
+
schema = capability.get_tool_schema()
|
|
39
|
+
|
|
40
|
+
Note:
|
|
41
|
+
This capability is designed for testing the function calling workflow.
|
|
42
|
+
It does NOT execute actual bash commands. For actual execution, the
|
|
43
|
+
caller must handle the tool_calls returned by the LLM and execute
|
|
44
|
+
them in a controlled environment.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, config: Dict[str, Any]):
|
|
48
|
+
super().__init__(config)
|
|
49
|
+
self.logger = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
# Configuration defaults
|
|
52
|
+
self._safe_mode = config.get("safe_mode", True)
|
|
53
|
+
self._allowed_commands = config.get("allowed_commands", [])
|
|
54
|
+
self._timeout = config.get("timeout", 30)
|
|
55
|
+
self._working_directory = config.get("working_directory")
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def name(self) -> str:
|
|
59
|
+
return "bash"
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def description(self) -> str:
|
|
63
|
+
base_desc = (
|
|
64
|
+
"Execute a bash command in a persistent shell session. "
|
|
65
|
+
"Use this for running system commands, scripts, and command-line operations. "
|
|
66
|
+
"The shell session maintains state between commands (working directory, "
|
|
67
|
+
"environment variables, created files)."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if self._safe_mode:
|
|
71
|
+
base_desc += (
|
|
72
|
+
" IMPORTANT: Avoid destructive commands like 'rm -rf', 'format', "
|
|
73
|
+
"or commands that could harm the system."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if self._allowed_commands:
|
|
77
|
+
allowed_str = ", ".join(self._allowed_commands)
|
|
78
|
+
base_desc += f" Preferred commands: {allowed_str}."
|
|
79
|
+
|
|
80
|
+
return base_desc
|
|
81
|
+
|
|
82
|
+
def get_parameters_schema(self) -> Dict[str, Any]:
|
|
83
|
+
return {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"properties": {
|
|
86
|
+
"command": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": (
|
|
89
|
+
"The bash command to execute. Use standard Unix commands. "
|
|
90
|
+
"For multi-step operations, chain commands with && or ;."
|
|
91
|
+
)
|
|
92
|
+
},
|
|
93
|
+
"restart": {
|
|
94
|
+
"type": "boolean",
|
|
95
|
+
"description": (
|
|
96
|
+
"Set to true to restart the bash session, clearing all state "
|
|
97
|
+
"(working directory resets, environment variables cleared)."
|
|
98
|
+
),
|
|
99
|
+
"default": False
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"required": [] # Neither is strictly required per Anthropic's bash tool spec
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def validate_command(self, command: str) -> tuple[bool, Optional[str]]:
|
|
106
|
+
"""
|
|
107
|
+
Validate a command against safety rules.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
command: The command to validate
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Tuple of (is_valid, error_message)
|
|
114
|
+
"""
|
|
115
|
+
if not command or not command.strip():
|
|
116
|
+
return True, None # Empty command is valid (might just be restart)
|
|
117
|
+
|
|
118
|
+
# Dangerous patterns to block
|
|
119
|
+
dangerous_patterns = [
|
|
120
|
+
"rm -rf /",
|
|
121
|
+
"rm -rf /*",
|
|
122
|
+
":(){:|:&};:", # Fork bomb
|
|
123
|
+
"mkfs",
|
|
124
|
+
"dd if=",
|
|
125
|
+
"> /dev/sd",
|
|
126
|
+
"chmod -R 777 /",
|
|
127
|
+
"wget | sh",
|
|
128
|
+
"curl | sh",
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if self._safe_mode:
|
|
132
|
+
command_lower = command.lower()
|
|
133
|
+
for pattern in dangerous_patterns:
|
|
134
|
+
if pattern in command_lower:
|
|
135
|
+
return False, f"Command blocked: contains dangerous pattern '{pattern}'"
|
|
136
|
+
|
|
137
|
+
# Check allowed commands if specified
|
|
138
|
+
if self._allowed_commands:
|
|
139
|
+
command_parts = command.strip().split()
|
|
140
|
+
if command_parts:
|
|
141
|
+
base_command = command_parts[0]
|
|
142
|
+
if base_command not in self._allowed_commands:
|
|
143
|
+
return False, (
|
|
144
|
+
f"Command '{base_command}' not in allowed list. "
|
|
145
|
+
f"Allowed: {self._allowed_commands}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return True, None
|
|
149
|
+
|
|
150
|
+
def execute_sync(self, command: str = "", restart: bool = False, **kwargs) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Simulate bash command execution (no actual execution).
|
|
153
|
+
|
|
154
|
+
This method validates the command and returns a simulation message.
|
|
155
|
+
It does NOT actually execute the command.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
command: The bash command (not executed)
|
|
159
|
+
restart: Whether to restart the session
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Simulation message describing what would happen
|
|
163
|
+
"""
|
|
164
|
+
if restart:
|
|
165
|
+
return "[SIMULATION] Bash session would be restarted. All state cleared."
|
|
166
|
+
|
|
167
|
+
if not command:
|
|
168
|
+
return "[SIMULATION] No command provided."
|
|
169
|
+
|
|
170
|
+
# Validate command
|
|
171
|
+
is_valid, error = self.validate_command(command)
|
|
172
|
+
if not is_valid:
|
|
173
|
+
return f"[SIMULATION] Command blocked: {error}"
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
f"[SIMULATION] Would execute command: {command}\n"
|
|
177
|
+
f"Note: Actual execution is disabled. This is a function calling simulation.\n"
|
|
178
|
+
f"To enable execution, handle the tool_call in your application code."
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
async def execute(self, command: str = "", restart: bool = False, **kwargs) -> str:
|
|
182
|
+
"""
|
|
183
|
+
Async version of execute (same simulation behavior).
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
command: The bash command (not executed)
|
|
187
|
+
restart: Whether to restart the session
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Simulation message describing what would happen
|
|
191
|
+
"""
|
|
192
|
+
return self.execute_sync(command=command, restart=restart, **kwargs)
|
|
193
|
+
|
|
194
|
+
def get_execution_hints(self) -> Dict[str, Any]:
|
|
195
|
+
"""
|
|
196
|
+
Get hints for actual command execution (for callers who want to execute).
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Dict with execution configuration hints
|
|
200
|
+
"""
|
|
201
|
+
return {
|
|
202
|
+
"safe_mode": self._safe_mode,
|
|
203
|
+
"allowed_commands": self._allowed_commands,
|
|
204
|
+
"timeout": self._timeout,
|
|
205
|
+
"working_directory": self._working_directory,
|
|
206
|
+
"dangerous_patterns": [
|
|
207
|
+
"rm -rf /", "mkfs", "dd if=", ":(){:|:&};:",
|
|
208
|
+
"chmod -R 777 /", "wget | sh", "curl | sh"
|
|
209
|
+
]
|
|
210
|
+
}
|