cursor-agent-tools 0.1.7__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.
- agent/__init__.py +5 -0
- agent/base.py +174 -0
- agent/claude_agent.py +422 -0
- agent/factory.py +117 -0
- agent/interact.py +1008 -0
- agent/openai_agent.py +367 -0
- agent/permissions.py +200 -0
- agent/tools/__init__.py +18 -0
- agent/tools/file_tools.py +311 -0
- agent/tools/register_tools.py +222 -0
- agent/tools/search_tools.py +274 -0
- agent/tools/system_tools.py +85 -0
- cursor_agent/__init__.py +9 -0
- cursor_agent/agent/__init__.py +18 -0
- cursor_agent_tools-0.1.7.dist-info/METADATA +729 -0
- cursor_agent_tools-0.1.7.dist-info/RECORD +19 -0
- cursor_agent_tools-0.1.7.dist-info/WHEEL +5 -0
- cursor_agent_tools-0.1.7.dist-info/licenses/LICENSE +21 -0
- cursor_agent_tools-0.1.7.dist-info/top_level.txt +2 -0
agent/__init__.py
ADDED
agent/base.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from .permissions import PermissionManager, PermissionOptions, PermissionRequest, PermissionStatus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseAgent(ABC):
|
|
9
|
+
"""
|
|
10
|
+
Base abstract class for AI agents that use function calling capabilities.
|
|
11
|
+
This defines the common interface for all agents regardless of the underlying provider.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
api_key: Optional[str] = None,
|
|
17
|
+
model: Optional[str] = None,
|
|
18
|
+
permission_options: Optional[PermissionOptions] = None,
|
|
19
|
+
permission_callback: Optional[Callable[[PermissionRequest], PermissionStatus]] = None,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Initialize the agent.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
api_key: API key for the model provider. If not provided, will attempt to load from environment.
|
|
26
|
+
model: Model to use. If not provided, will use the default model.
|
|
27
|
+
permission_options: Configuration options for permissions
|
|
28
|
+
permission_callback: Optional callback for handling permission requests
|
|
29
|
+
"""
|
|
30
|
+
self.api_key: Optional[str] = api_key
|
|
31
|
+
self.model: Optional[str] = model
|
|
32
|
+
self.conversation_history: List[Dict[str, Any]] = []
|
|
33
|
+
self.available_tools: Dict[str, Dict[str, Any]] = {}
|
|
34
|
+
self.system_prompt: str = self._generate_system_prompt()
|
|
35
|
+
|
|
36
|
+
# Initialize permission manager with options and optional callback
|
|
37
|
+
self.permission_manager = PermissionManager(
|
|
38
|
+
options=permission_options or PermissionOptions(),
|
|
39
|
+
callback=permission_callback
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def _generate_system_prompt(self) -> str:
|
|
44
|
+
"""
|
|
45
|
+
Generate the system prompt that defines the agent's capabilities and behavior.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The system prompt as a string
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
async def chat(self, message: str, user_info: Optional[Dict[str, Any]] = None) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Send a message to the AI and get a response.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
message: The user's message
|
|
59
|
+
user_info: Optional dict containing info about the user's current state
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The AI's response
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def register_tool(
|
|
67
|
+
self, name: str, function: Callable, description: str, parameters: Dict[str, Any]
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Register a function that can be called by the AI.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
name: Name of the function
|
|
74
|
+
function: The actual function to call
|
|
75
|
+
description: Description of what the function does
|
|
76
|
+
parameters: Dict describing the parameters the function takes
|
|
77
|
+
"""
|
|
78
|
+
self.available_tools[name] = {
|
|
79
|
+
"function": function,
|
|
80
|
+
"schema": {"name": name, "description": description, "parameters": parameters},
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def _prepare_tools(self) -> Any:
|
|
85
|
+
"""
|
|
86
|
+
Format the registered tools into the format expected by the model's API.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tools in the format expected by the model
|
|
90
|
+
"""
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
@abstractmethod
|
|
94
|
+
def _execute_tool_calls(self, tool_calls: Any) -> List[Dict[str, Any]]:
|
|
95
|
+
"""
|
|
96
|
+
Execute the tool calls made by the AI.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
tool_calls: Tool calls in the format provided by the specific model
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of tool call results
|
|
103
|
+
"""
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
def request_permission(
|
|
107
|
+
self, operation_type: str, details: Dict[str, Any]
|
|
108
|
+
) -> bool:
|
|
109
|
+
"""
|
|
110
|
+
Request permission for an operation.
|
|
111
|
+
|
|
112
|
+
This method forwards the permission request to the permission manager.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
operation_type: Type of operation ('create_file', 'edit_file', 'delete_file', 'run_terminal_command', etc.)
|
|
116
|
+
details: Dictionary containing operation details
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
True if permission is granted, False otherwise
|
|
120
|
+
"""
|
|
121
|
+
return self.permission_manager.request_permission(operation_type, details)
|
|
122
|
+
|
|
123
|
+
def _permission_request_callback(self, permission_request: PermissionRequest) -> PermissionStatus:
|
|
124
|
+
"""
|
|
125
|
+
Default implementation of permission request callback.
|
|
126
|
+
|
|
127
|
+
This method can be overridden by subclasses to provide
|
|
128
|
+
appropriate user interaction for permission requests.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
permission_request: The permission request object
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
PermissionStatus indicating whether the request is granted, denied, or needs confirmation
|
|
135
|
+
"""
|
|
136
|
+
# Default implementation prompts the user for confirmation
|
|
137
|
+
print(f"\n🔒 Permission Request: {permission_request.operation}")
|
|
138
|
+
print(f"Details: {json.dumps(permission_request.details, indent=2)}")
|
|
139
|
+
|
|
140
|
+
while True:
|
|
141
|
+
response = input("Allow this operation? (y/n): ").strip().lower()
|
|
142
|
+
if response in ("y", "yes"):
|
|
143
|
+
return PermissionStatus.GRANTED
|
|
144
|
+
elif response in ("n", "no"):
|
|
145
|
+
return PermissionStatus.DENIED
|
|
146
|
+
else:
|
|
147
|
+
print("Please enter 'y' or 'n'")
|
|
148
|
+
|
|
149
|
+
def format_user_message(self, message: str, user_info: Optional[Dict[str, Any]] = None) -> str:
|
|
150
|
+
"""
|
|
151
|
+
Format the user message with user_info if provided.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
message: The user's message
|
|
155
|
+
user_info: Optional dict containing info about the user's current state
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Formatted message
|
|
159
|
+
"""
|
|
160
|
+
if user_info:
|
|
161
|
+
return f"<user_info>\n{json.dumps(user_info, indent=2)}\n</user_info>\n\n<user_query>\n{message}\n</user_query>"
|
|
162
|
+
else:
|
|
163
|
+
return f"<user_query>\n{message}\n</user_query>"
|
|
164
|
+
|
|
165
|
+
def register_default_tools(self) -> None:
|
|
166
|
+
"""
|
|
167
|
+
Register the default set of tools with the agent.
|
|
168
|
+
|
|
169
|
+
This method imports and calls the register_default_tools function
|
|
170
|
+
from the tools module, passing self as the agent.
|
|
171
|
+
"""
|
|
172
|
+
# Import here to avoid circular imports
|
|
173
|
+
from .tools.register_tools import register_default_tools
|
|
174
|
+
register_default_tools(self)
|
agent/claude_agent.py
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# mypy: ignore-errors
|
|
2
|
+
import json
|
|
3
|
+
from typing import Any, Dict, List, Optional, Callable
|
|
4
|
+
|
|
5
|
+
from anthropic import APIError, AsyncAnthropic, AuthenticationError, BadRequestError, RateLimitError
|
|
6
|
+
|
|
7
|
+
from .base import BaseAgent
|
|
8
|
+
from .permissions import PermissionOptions, PermissionRequest, PermissionStatus
|
|
9
|
+
from .tools.register_tools import register_default_tools
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ClaudeAgent(BaseAgent):
|
|
13
|
+
"""
|
|
14
|
+
Claude Agent that implements the BaseAgent interface using Anthropic's Claude models.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
api_key: str,
|
|
20
|
+
model: str = "claude-3-5-sonnet-latest",
|
|
21
|
+
temperature: float = 0.0,
|
|
22
|
+
timeout: int = 180,
|
|
23
|
+
permission_callback: Optional[Callable[[PermissionRequest], PermissionStatus]] = None,
|
|
24
|
+
permission_options: Optional[PermissionOptions] = None,
|
|
25
|
+
**kwargs
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize a Claude agent.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
api_key: Anthropic API key
|
|
32
|
+
model: Claude model to use, default is claude-3-opus
|
|
33
|
+
temperature: Temperature parameter for model (0.0 to 1.0)
|
|
34
|
+
timeout: Timeout in seconds for API requests
|
|
35
|
+
permission_callback: Optional callback for permission requests
|
|
36
|
+
permission_options: Permission configuration options
|
|
37
|
+
**kwargs: Additional parameters to pass to the model
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(
|
|
40
|
+
api_key=api_key,
|
|
41
|
+
model=model,
|
|
42
|
+
permission_options=permission_options,
|
|
43
|
+
permission_callback=permission_callback
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.temperature = temperature
|
|
47
|
+
self.timeout = timeout
|
|
48
|
+
self.extra_kwargs = kwargs
|
|
49
|
+
|
|
50
|
+
# Initialize Anthropic client
|
|
51
|
+
self.client = AsyncAnthropic(api_key=api_key)
|
|
52
|
+
|
|
53
|
+
self.conversation_history = []
|
|
54
|
+
self.available_tools = {}
|
|
55
|
+
self.system_prompt = self._generate_system_prompt()
|
|
56
|
+
|
|
57
|
+
def _is_valid_api_key(self, api_key: str) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Validate the format of the Anthropic API key.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
api_key: The API key to validate
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if the key is a valid format, False otherwise
|
|
66
|
+
"""
|
|
67
|
+
if not api_key or not isinstance(api_key, str):
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
# Allow dummy keys in test environments
|
|
71
|
+
if api_key == "sk-ant-dummy" or "dummy" in api_key:
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
# Anthropic keys should start with sk-ant- or sk-
|
|
75
|
+
valid_prefix = api_key.startswith(("sk-ant-", "sk-"))
|
|
76
|
+
|
|
77
|
+
# Keys should be fairly long and not contain spaces
|
|
78
|
+
valid_length = len(api_key) >= 20 and " " not in api_key
|
|
79
|
+
|
|
80
|
+
return valid_prefix and valid_length
|
|
81
|
+
|
|
82
|
+
def _generate_system_prompt(self) -> str:
|
|
83
|
+
"""
|
|
84
|
+
Generate the system prompt that defines Claude's capabilities and behavior.
|
|
85
|
+
This is an extensive prompt that replicates Claude's behavior in Cursor.
|
|
86
|
+
"""
|
|
87
|
+
return """
|
|
88
|
+
You are a powerful agentic AI coding assistant, powered by Claude 3.7 Sonnet. You operate exclusively in Cursor, the world's best IDE.
|
|
89
|
+
|
|
90
|
+
You are pair programming with a USER to solve their coding task.
|
|
91
|
+
The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.
|
|
92
|
+
Each time the USER sends a message, we may automatically attach some information about their current state, such as what files they have open, where their cursor is, recently viewed files, edit history in their session so far, linter errors, and more.
|
|
93
|
+
This information may or may not be relevant to the coding task, it is up for you to decide.
|
|
94
|
+
Your main goal is to follow the USER's instructions at each message, denoted by the <user_query> tag.
|
|
95
|
+
|
|
96
|
+
<tool_calling>
|
|
97
|
+
You have tools at your disposal to solve the coding task. Follow these rules regarding tool calls:
|
|
98
|
+
1. ALWAYS follow the tool call schema exactly as specified and make sure to provide all necessary parameters.
|
|
99
|
+
2. The conversation may reference tools that are no longer available. NEVER call tools that are not explicitly provided.
|
|
100
|
+
3. **NEVER refer to tool names when speaking to the USER.** For example, instead of saying 'I need to use the edit_file tool to edit your file', just say 'I will edit your file'.
|
|
101
|
+
4. Only calls tools when they are necessary. If the USER's task is general or you already know the answer, just respond without calling tools.
|
|
102
|
+
5. Before calling each tool, first explain to the USER why you are calling it.
|
|
103
|
+
</tool_calling>
|
|
104
|
+
|
|
105
|
+
<making_code_changes>
|
|
106
|
+
When making code changes, NEVER output code to the USER, unless requested. Instead use one of the code edit tools to implement the change.
|
|
107
|
+
Use the code edit tools at most once per turn.
|
|
108
|
+
It is *EXTREMELY* important that your generated code can be run immediately by the USER. To ensure this, follow these instructions carefully:
|
|
109
|
+
1. Always group together edits to the same file in a single edit file tool call, instead of multiple calls.
|
|
110
|
+
2. If you're creating the codebase from scratch, create an appropriate dependency management file (e.g. requirements.txt) with package versions and a helpful README.
|
|
111
|
+
3. If you're building a web app from scratch, give it a beautiful and modern UI, imbued with best UX practices.
|
|
112
|
+
4. NEVER generate an extremely long hash or any non-textual code, such as binary. These are not helpful to the USER and are very expensive.
|
|
113
|
+
5. Unless you are appending some small easy to apply edit to a file, or creating a new file, you MUST read the the contents or section of what you're editing before editing it.
|
|
114
|
+
6. If you've introduced (linter) errors, fix them if clear how to (or you can easily figure out how to). Do not make uneducated guesses. And DO NOT loop more than 3 times on fixing linter errors on the same file. On the third time, you should stop and ask the user what to do next.
|
|
115
|
+
7. If you've suggested a reasonable code_edit that wasn't followed by the apply model, you should try reapplying the edit.
|
|
116
|
+
</making_code_changes>
|
|
117
|
+
|
|
118
|
+
<searching_and_reading>
|
|
119
|
+
You have tools to search the codebase and read files. Follow these rules regarding tool calls:
|
|
120
|
+
1. If available, heavily prefer the semantic search tool to grep search, file search, and list dir tools.
|
|
121
|
+
2. If you need to read a file, prefer to read larger sections of the file at once over multiple smaller calls.
|
|
122
|
+
3. If you have found a reasonable place to edit or answer, do not continue calling tools. Edit or answer from the information you have found.
|
|
123
|
+
</searching_and_reading>
|
|
124
|
+
|
|
125
|
+
Answer the user's request using the relevant tool(s), if they are available. Check that all the required parameters for each tool call are provided or can reasonably be inferred from context. IF there are no relevant tools or there are missing values for required parameters, ask the user to supply these values; otherwise proceed with the tool calls. If the user provides a specific value for a parameter (for example provided in quotes), make sure to use that value EXACTLY. DO NOT make up values for or ask about optional parameters. Carefully analyze descriptive terms in the request as they may indicate required parameter values that should be included even if not explicitly quoted.
|
|
126
|
+
|
|
127
|
+
You MUST use the following format when citing code regions or blocks:
|
|
128
|
+
```12:15:app/components/Todo.tsx
|
|
129
|
+
// ... existing code ...
|
|
130
|
+
```
|
|
131
|
+
This is the ONLY acceptable format for code citations. The format is ```startLine:endLine:filepath where startLine and endLine are line numbers.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def _prepare_tools(self) -> Optional[List[Dict[str, Any]]]:
|
|
135
|
+
"""
|
|
136
|
+
Prepare the registered tools for Claude API.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of tools in the format expected by Claude API, or None if no tools are registered
|
|
140
|
+
"""
|
|
141
|
+
if not self.available_tools:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
tools = []
|
|
145
|
+
for name, tool_data in self.available_tools.items():
|
|
146
|
+
# Claude tools format:
|
|
147
|
+
# {
|
|
148
|
+
# "name": "tool_name",
|
|
149
|
+
# "description": "Tool description",
|
|
150
|
+
# "input_schema": {
|
|
151
|
+
# "type": "object",
|
|
152
|
+
# "properties": {
|
|
153
|
+
# "property_name": {
|
|
154
|
+
# "type": "string",
|
|
155
|
+
# "description": "Property description"
|
|
156
|
+
# },
|
|
157
|
+
# ...
|
|
158
|
+
# },
|
|
159
|
+
# "required": ["property_name", ...]
|
|
160
|
+
# }
|
|
161
|
+
# }
|
|
162
|
+
|
|
163
|
+
tool = {
|
|
164
|
+
"name": name,
|
|
165
|
+
"description": tool_data["schema"]["description"],
|
|
166
|
+
"input_schema": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"properties": tool_data["schema"]["parameters"]["properties"],
|
|
169
|
+
"required": tool_data["schema"]["parameters"].get("required", []),
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
tools.append(tool)
|
|
173
|
+
return tools if tools else None
|
|
174
|
+
|
|
175
|
+
def _execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
176
|
+
"""
|
|
177
|
+
Execute the tool calls made by Claude.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
tool_calls: List of tool calls to execute
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
List of tool call results formatted for the Claude API
|
|
184
|
+
"""
|
|
185
|
+
tool_results = []
|
|
186
|
+
|
|
187
|
+
for call in tool_calls:
|
|
188
|
+
tool_name = call["name"]
|
|
189
|
+
tool_id = call.get("id")
|
|
190
|
+
arguments = call.get("input", {})
|
|
191
|
+
|
|
192
|
+
# Format for user message with tool_result as required by the Claude API
|
|
193
|
+
result_message = {"role": "user", "content": []}
|
|
194
|
+
|
|
195
|
+
if tool_name not in self.available_tools:
|
|
196
|
+
# Add error result
|
|
197
|
+
error_msg = f"Tool '{tool_name}' not found. Error: Tool not available."
|
|
198
|
+
result_message["content"].append(
|
|
199
|
+
{
|
|
200
|
+
"type": "tool_result",
|
|
201
|
+
"tool_use_id": tool_id,
|
|
202
|
+
"is_error": True,
|
|
203
|
+
"content": error_msg,
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
try:
|
|
208
|
+
function = self.available_tools[tool_name]["function"]
|
|
209
|
+
# Convert input to the expected format for the function
|
|
210
|
+
result = function(**arguments)
|
|
211
|
+
|
|
212
|
+
# Format the result based on whether it's a string or a JSON-serializable object
|
|
213
|
+
content = result if isinstance(result, str) else json.dumps(result)
|
|
214
|
+
|
|
215
|
+
# Add tool result
|
|
216
|
+
result_message["content"].append(
|
|
217
|
+
{"type": "tool_result", "tool_use_id": tool_id, "content": content}
|
|
218
|
+
)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
error_msg = f"Error executing tool {tool_name}: {str(e)}"
|
|
221
|
+
result_message["content"].append(
|
|
222
|
+
{
|
|
223
|
+
"type": "tool_result",
|
|
224
|
+
"tool_use_id": tool_id,
|
|
225
|
+
"is_error": True,
|
|
226
|
+
"content": error_msg,
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Only add messages with non-empty content
|
|
231
|
+
if result_message["content"]:
|
|
232
|
+
tool_results.append(result_message)
|
|
233
|
+
|
|
234
|
+
return tool_results
|
|
235
|
+
|
|
236
|
+
async def chat(self, message: str, user_info: Optional[Dict[str, Any]] = None) -> str:
|
|
237
|
+
"""
|
|
238
|
+
Send a message to Claude and get a response.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
message: The user's message
|
|
242
|
+
user_info: Optional dict containing info about the user's current state
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Claude's response
|
|
246
|
+
"""
|
|
247
|
+
# Format the user message with user_info if provided
|
|
248
|
+
formatted_message = self.format_user_message(message, user_info)
|
|
249
|
+
|
|
250
|
+
# Add the user message to the conversation history
|
|
251
|
+
self.conversation_history.append({"role": "user", "content": formatted_message})
|
|
252
|
+
|
|
253
|
+
# Prepare the messages for the API call - exclude system message from the conversation history
|
|
254
|
+
# because Anthropic API requires system prompt as a separate parameter
|
|
255
|
+
messages = []
|
|
256
|
+
for msg in self.conversation_history:
|
|
257
|
+
if msg["role"] != "system" and msg.get("content"): # Ensure content is not empty
|
|
258
|
+
messages.append(msg)
|
|
259
|
+
|
|
260
|
+
# Always enable tools regardless of message content
|
|
261
|
+
use_tools = True
|
|
262
|
+
# Note: Previous code disabled tools for file-related operations, but this was causing issues
|
|
263
|
+
# with the file_tools_demo and other demos that need to use file tools
|
|
264
|
+
|
|
265
|
+
# Prepare tools if needed
|
|
266
|
+
tools = None
|
|
267
|
+
if use_tools:
|
|
268
|
+
tools = self._prepare_tools()
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
# Make the API call
|
|
272
|
+
api_params = {
|
|
273
|
+
"model": self.model if self.model else "claude-3-5-sonnet-latest",
|
|
274
|
+
"max_tokens": 4096,
|
|
275
|
+
"temperature": self.temperature,
|
|
276
|
+
"system": self.system_prompt, # System prompt as a separate parameter
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# Add properly typed messages
|
|
280
|
+
typed_messages = []
|
|
281
|
+
for msg in messages:
|
|
282
|
+
if isinstance(msg["content"], str):
|
|
283
|
+
typed_messages.append({"role": msg["role"], "content": msg["content"]})
|
|
284
|
+
else:
|
|
285
|
+
# Handle content that's not a string (e.g., structured content)
|
|
286
|
+
typed_messages.append(msg)
|
|
287
|
+
|
|
288
|
+
api_params["messages"] = typed_messages
|
|
289
|
+
|
|
290
|
+
# Only include tools parameter if we have tools registered and are using tools
|
|
291
|
+
if tools and use_tools:
|
|
292
|
+
api_params["tools"] = tools
|
|
293
|
+
|
|
294
|
+
# Make the API call
|
|
295
|
+
response = await self.client.messages.create(**api_params) # type: ignore
|
|
296
|
+
|
|
297
|
+
# Process any tool calls
|
|
298
|
+
if response.content and any(block.type == "tool_use" for block in response.content):
|
|
299
|
+
# Add assistant message with tool calls to conversation history
|
|
300
|
+
assistant_content = []
|
|
301
|
+
for block in response.content:
|
|
302
|
+
if hasattr(block, "text") and block.text is not None:
|
|
303
|
+
assistant_content.append({"type": "text", "text": block.text}) # type: ignore
|
|
304
|
+
elif block.type == "tool_use":
|
|
305
|
+
assistant_content.append({ # type: ignore
|
|
306
|
+
"type": "tool_use",
|
|
307
|
+
"id": block.id,
|
|
308
|
+
"name": block.name,
|
|
309
|
+
"input": block.input
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
self.conversation_history.append({"role": "assistant", "content": assistant_content})
|
|
313
|
+
|
|
314
|
+
# Extract tool calls
|
|
315
|
+
tool_calls = []
|
|
316
|
+
for block in response.content:
|
|
317
|
+
if block.type == "tool_use":
|
|
318
|
+
tool_calls.append(
|
|
319
|
+
{"name": block.name, "id": block.id, "input": block.input}
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Execute tool calls
|
|
323
|
+
tool_results = self._execute_tool_calls(tool_calls)
|
|
324
|
+
|
|
325
|
+
# Add tool results to conversation history and prepare follow-up messages
|
|
326
|
+
if tool_results:
|
|
327
|
+
for result in tool_results:
|
|
328
|
+
self.conversation_history.append(result)
|
|
329
|
+
|
|
330
|
+
# Make a follow-up API call with the tool results
|
|
331
|
+
follow_up_messages = []
|
|
332
|
+
for msg in self.conversation_history:
|
|
333
|
+
if msg["role"] != "system" and msg.get(
|
|
334
|
+
"content"
|
|
335
|
+
): # Ensure content is not empty
|
|
336
|
+
follow_up_messages.append(msg)
|
|
337
|
+
|
|
338
|
+
# Make a follow-up API call with the tool results
|
|
339
|
+
follow_up_response = await self.client.messages.create( # type: ignore
|
|
340
|
+
model=self.model if self.model else "claude-3-5-sonnet-latest",
|
|
341
|
+
system=self.system_prompt, # System prompt as a separate parameter
|
|
342
|
+
messages=follow_up_messages,
|
|
343
|
+
max_tokens=4096,
|
|
344
|
+
temperature=self.temperature,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# Add the assistant's follow-up response to the conversation history
|
|
348
|
+
self.conversation_history.append(
|
|
349
|
+
{"role": "assistant", "content": follow_up_response.content}
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Extract text from the response
|
|
353
|
+
response_text = "".join(
|
|
354
|
+
block.text for block in follow_up_response.content if block.type == "text"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
return response_text
|
|
358
|
+
else:
|
|
359
|
+
# No valid tool results were generated
|
|
360
|
+
response_text = (
|
|
361
|
+
"Error: Failed to execute tool calls. Please try a different query."
|
|
362
|
+
)
|
|
363
|
+
return response_text
|
|
364
|
+
else:
|
|
365
|
+
# Extract text from the response
|
|
366
|
+
response_text = "".join(
|
|
367
|
+
block.text for block in response.content if block.type == "text"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Add the assistant's response to the conversation history
|
|
371
|
+
self.conversation_history.append({"role": "assistant", "content": response.content})
|
|
372
|
+
|
|
373
|
+
return response_text
|
|
374
|
+
|
|
375
|
+
except AuthenticationError as e:
|
|
376
|
+
return f"Error: Authentication failed. Please check your Anthropic API key. Details: {str(e)}"
|
|
377
|
+
except BadRequestError as e:
|
|
378
|
+
# Provide more detailed information about the bad request
|
|
379
|
+
request_info = ""
|
|
380
|
+
if hasattr(e, "request"):
|
|
381
|
+
request_info = f"\nRequest information: {e.request}"
|
|
382
|
+
return f"Error: Bad request to the Anthropic API. Details: {str(e)}{request_info}"
|
|
383
|
+
except RateLimitError as e:
|
|
384
|
+
return f"Error: Rate limit exceeded. Please try again later. Details: {str(e)}"
|
|
385
|
+
except APIError as e:
|
|
386
|
+
return f"Error: Anthropic API error. Details: {str(e)}"
|
|
387
|
+
except Exception as e:
|
|
388
|
+
return f"Error: An unexpected error occurred. Details: {type(e).__name__}: {str(e)}"
|
|
389
|
+
|
|
390
|
+
def register_default_tools(self) -> None:
|
|
391
|
+
"""
|
|
392
|
+
Register all the default tools available to the agent.
|
|
393
|
+
"""
|
|
394
|
+
# Use the centralized tool registration function
|
|
395
|
+
register_default_tools(self)
|
|
396
|
+
|
|
397
|
+
def _permission_request_callback(self, permission_request: PermissionRequest) -> PermissionStatus:
|
|
398
|
+
"""
|
|
399
|
+
Implementation of permission request callback for Claude agent.
|
|
400
|
+
|
|
401
|
+
In a real application, this would interact with the user to get permission.
|
|
402
|
+
For now, we'll default to a console-based interaction.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
permission_request: The permission request object
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
PermissionStatus indicating whether the request is granted or denied
|
|
409
|
+
"""
|
|
410
|
+
# If yolo mode is enabled, check is already done in PermissionManager
|
|
411
|
+
if self.permission_manager.options.yolo_mode:
|
|
412
|
+
return PermissionStatus.GRANTED
|
|
413
|
+
|
|
414
|
+
# Default implementation asks on console
|
|
415
|
+
print(f"\n[PERMISSION REQUEST] {permission_request.operation}")
|
|
416
|
+
print(f"Details: {json.dumps(permission_request.details, indent=2)}")
|
|
417
|
+
response = input("Allow this operation? (y/n): ").strip().lower()
|
|
418
|
+
|
|
419
|
+
if response == 'y' or response == 'yes':
|
|
420
|
+
return PermissionStatus.GRANTED
|
|
421
|
+
else:
|
|
422
|
+
return PermissionStatus.DENIED
|